src: Add h2load, benchmarking tool for HTTP/2 and SPDY
This commit is contained in:
parent
c1e1a1be5a
commit
1337fa8bfe
|
@ -51,7 +51,7 @@ LDADD = \
|
|||
|
||||
if ENABLE_APP
|
||||
|
||||
bin_PROGRAMS += nghttp nghttpd nghttpx
|
||||
bin_PROGRAMS += nghttp nghttpd nghttpx h2load
|
||||
|
||||
HELPER_OBJECTS = util.cc http2.cc timegm.c app_helper.cc
|
||||
HELPER_HFILES = util.h http2.h timegm.h app_helper.h nghttp2_config.h
|
||||
|
@ -69,6 +69,14 @@ nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \
|
|||
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
|
||||
HttpServer.cc HttpServer.h
|
||||
|
||||
h2load_SOURCES = util.cc util.h http2.cc http2.h h2load.cc h2load.h \
|
||||
h2load_session.h \
|
||||
h2load_http2_session.cc h2load_http2_session.h
|
||||
|
||||
if HAVE_SPDYLAY
|
||||
h2load_SOURCES += h2load_spdy_session.cc h2load_spdy_session.h
|
||||
endif # HAVE_SPDYLAY
|
||||
|
||||
NGHTTPX_SRCS = \
|
||||
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
|
||||
app_helper.cc app_helper.h \
|
||||
|
|
|
@ -0,0 +1,797 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include "h2load.h"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
#ifdef HAVE_SPDYLAY
|
||||
#include <spdylay/spdylay.h>
|
||||
#endif // HAVE_SPDYLAY
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "h2load_http2_session.h"
|
||||
#ifdef HAVE_SPDYLAY
|
||||
#include "h2load_spdy_session.h"
|
||||
#endif // HAVE_SPDYLAY
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace h2load {
|
||||
|
||||
Config::Config()
|
||||
: addrs(nullptr),
|
||||
nreqs(1),
|
||||
nclients(1),
|
||||
nworkers(1),
|
||||
max_concurrent_streams(1),
|
||||
window_bits(16),
|
||||
connection_window_bits(16),
|
||||
port(0),
|
||||
verbose(false)
|
||||
{}
|
||||
|
||||
Config::~Config()
|
||||
{
|
||||
freeaddrinfo(addrs);
|
||||
}
|
||||
|
||||
Config config;
|
||||
|
||||
namespace {
|
||||
void eventcb(bufferevent *bev, short events, void *ptr);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void readcb(bufferevent *bev, void *ptr);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void writecb(bufferevent *bev, void *ptr);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void debug(const char *format, ...)
|
||||
{
|
||||
if(config.verbose) {
|
||||
fprintf(stderr, "[DEBUG] ");
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Stream::Stream()
|
||||
: status_success(-1)
|
||||
{}
|
||||
|
||||
Client::Client(Worker *worker)
|
||||
: worker(worker),
|
||||
ssl(nullptr),
|
||||
bev(nullptr),
|
||||
next_addr(config.addrs),
|
||||
state(CLIENT_IDLE)
|
||||
{}
|
||||
|
||||
Client::~Client()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
int Client::connect()
|
||||
{
|
||||
if(config.scheme == "https") {
|
||||
ssl = SSL_new(worker->ssl_ctx);
|
||||
bev = bufferevent_openssl_socket_new(worker->evbase, -1, ssl,
|
||||
BUFFEREVENT_SSL_CONNECTING,
|
||||
BEV_OPT_DEFER_CALLBACKS);
|
||||
} else {
|
||||
bev = bufferevent_socket_new(worker->evbase, -1,
|
||||
BEV_OPT_DEFER_CALLBACKS);
|
||||
}
|
||||
|
||||
int rv = -1;
|
||||
while(next_addr) {
|
||||
rv = bufferevent_socket_connect(bev, next_addr->ai_addr,
|
||||
next_addr->ai_addrlen);
|
||||
next_addr = next_addr->ai_next;
|
||||
if(rv == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
bufferevent_enable(bev, EV_READ);
|
||||
bufferevent_setcb(bev, readcb, writecb, eventcb, this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Client::disconnect()
|
||||
{
|
||||
process_abandoned_streams();
|
||||
if(worker->stats.req_done == worker->stats.req_todo) {
|
||||
worker->schedule_terminate();
|
||||
}
|
||||
int fd = -1;
|
||||
streams.clear();
|
||||
session.reset();
|
||||
state = CLIENT_IDLE;
|
||||
if(ssl) {
|
||||
fd = SSL_get_fd(ssl);
|
||||
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
|
||||
SSL_shutdown(ssl);
|
||||
}
|
||||
if(bev) {
|
||||
bufferevent_disable(bev, EV_READ | EV_WRITE);
|
||||
bufferevent_free(bev);
|
||||
bev = nullptr;
|
||||
}
|
||||
if(ssl) {
|
||||
SSL_free(ssl);
|
||||
ssl = nullptr;
|
||||
}
|
||||
if(fd != -1) {
|
||||
shutdown(fd, SHUT_WR);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::submit_request()
|
||||
{
|
||||
session->submit_request();
|
||||
++worker->stats.req_started;
|
||||
}
|
||||
|
||||
void Client::process_abandoned_streams()
|
||||
{
|
||||
worker->stats.req_failed += streams.size();
|
||||
worker->stats.req_error += streams.size();
|
||||
worker->stats.req_done += streams.size();
|
||||
}
|
||||
|
||||
void Client::report_progress()
|
||||
{
|
||||
if(worker->stats.req_done % worker->progress_interval == 0) {
|
||||
std::cout << "progress: "
|
||||
<< worker->stats.req_done * 100 / worker->stats.req_todo
|
||||
<< "% done"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::terminate_session()
|
||||
{
|
||||
session->terminate();
|
||||
}
|
||||
|
||||
void Client::on_request(int32_t stream_id)
|
||||
{
|
||||
streams[stream_id] = Stream();
|
||||
}
|
||||
|
||||
void Client::on_header(int32_t stream_id,
|
||||
const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen)
|
||||
{
|
||||
auto itr = streams.find(stream_id);
|
||||
if(itr == std::end(streams)) {
|
||||
return;
|
||||
}
|
||||
auto& stream = (*itr).second;
|
||||
if(stream.status_success == -1 &&
|
||||
namelen == 7 && util::streq(":status", 7, name, namelen)) {
|
||||
int status = 0;
|
||||
for(size_t i = 0; i < valuelen; ++i) {
|
||||
if('0' <= value[i] && value[i] <= '9') {
|
||||
status *= 10;
|
||||
status += value[i] - '0';
|
||||
if(status > 999) {
|
||||
stream.status_success = 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(status >= 200 && status < 300) {
|
||||
++worker->stats.status[2];
|
||||
stream.status_success = 1;
|
||||
} else if(status < 400) {
|
||||
++worker->stats.status[3];
|
||||
stream.status_success = 1;
|
||||
} else if(status < 600) {
|
||||
++worker->stats.status[status / 100];
|
||||
stream.status_success = 0;
|
||||
} else {
|
||||
stream.status_success = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::on_stream_close(int32_t stream_id, bool success)
|
||||
{
|
||||
++worker->stats.req_done;
|
||||
if(success && streams[stream_id].status_success == 1) {
|
||||
++worker->stats.req_success;
|
||||
} else {
|
||||
++worker->stats.req_failed;
|
||||
}
|
||||
report_progress();
|
||||
streams.erase(stream_id);
|
||||
if(worker->stats.req_done == worker->stats.req_todo) {
|
||||
worker->schedule_terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
if(worker->stats.req_started < worker->stats.req_todo) {
|
||||
submit_request();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int Client::on_connect()
|
||||
{
|
||||
session->on_connect();
|
||||
|
||||
auto nreq = std::min(worker->stats.req_todo - worker->stats.req_started,
|
||||
std::min(worker->stats.req_todo / worker->clients.size(),
|
||||
config.max_concurrent_streams));
|
||||
for(; nreq > 0; --nreq) {
|
||||
submit_request();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Client::on_read()
|
||||
{
|
||||
ssize_t rv = session->on_read();
|
||||
if(rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
worker->stats.bytes_total += rv;
|
||||
|
||||
return on_write();
|
||||
}
|
||||
|
||||
int Client::on_write()
|
||||
{
|
||||
return session->on_write();
|
||||
}
|
||||
|
||||
Worker::Worker(SSL_CTX *ssl_ctx, Config *config)
|
||||
: stats{0}, evbase(event_base_new()), ssl_ctx(ssl_ctx), config(config),
|
||||
term_timer_started(false)
|
||||
{
|
||||
stats.req_todo = config->nreqs;
|
||||
progress_interval = std::max((size_t)1, config->nreqs / 10);
|
||||
for(size_t i = 0; i < config->nclients; ++i) {
|
||||
clients.push_back(util::make_unique<Client>(this));
|
||||
}
|
||||
}
|
||||
|
||||
Worker::~Worker()
|
||||
{
|
||||
event_base_free(evbase);
|
||||
}
|
||||
|
||||
void Worker::run()
|
||||
{
|
||||
for(auto& client : clients) {
|
||||
if(client->connect() != 0) {
|
||||
std::cerr << "client could not connect to host" << std::endl;
|
||||
client->disconnect();
|
||||
}
|
||||
}
|
||||
event_base_loop(evbase, 0);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void term_timeout_cb(evutil_socket_t fd, short what, void *arg)
|
||||
{
|
||||
auto worker = static_cast<Worker*>(arg);
|
||||
worker->terminate_session();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Worker::schedule_terminate()
|
||||
{
|
||||
if(term_timer_started) {
|
||||
return;
|
||||
}
|
||||
term_timer_started = true;
|
||||
auto term_timer = evtimer_new(evbase, term_timeout_cb, this);
|
||||
timeval timeout = { 0, 0 };
|
||||
evtimer_add(term_timer, &timeout);
|
||||
}
|
||||
|
||||
void Worker::terminate_session()
|
||||
{
|
||||
for(auto& client : clients) {
|
||||
if(client->session == nullptr) {
|
||||
client->disconnect();
|
||||
continue;
|
||||
}
|
||||
client->terminate_session();
|
||||
if(client->on_write() != 0) {
|
||||
client->disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void debug_nextproto_error()
|
||||
{
|
||||
#ifdef HAVE_SPDYLAY
|
||||
debug("no supported protocol was negotiated, expected: %s, "
|
||||
"spdy/2, spdy/3, spdy/3.1\n", NGHTTP2_PROTO_VERSION_ID);
|
||||
#else // !HAVE_SPDYLAY
|
||||
debug("no supported protocol was negotiated, expected: %s\n",
|
||||
NGHTTP2_PROTO_VERSION_ID);
|
||||
#endif // !HAVE_SPDYLAY
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void eventcb(bufferevent *bev, short events, void *ptr)
|
||||
{
|
||||
int rv;
|
||||
auto client = static_cast<Client*>(ptr);
|
||||
if(events & BEV_EVENT_CONNECTED) {
|
||||
if(client->ssl) {
|
||||
const unsigned char *next_proto = nullptr;
|
||||
unsigned int next_proto_len;
|
||||
SSL_get0_next_proto_negotiated(client->ssl,
|
||||
&next_proto, &next_proto_len);
|
||||
|
||||
if(!next_proto) {
|
||||
debug_nextproto_error();
|
||||
client->disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if(next_proto_len == NGHTTP2_PROTO_VERSION_ID_LEN &&
|
||||
memcmp(NGHTTP2_PROTO_VERSION_ID, next_proto, next_proto_len) == 0) {
|
||||
client->session = util::make_unique<Http2Session>(client);
|
||||
} else {
|
||||
#ifdef HAVE_SPDYLAY
|
||||
auto spdy_version = spdylay_npn_get_version(next_proto,
|
||||
next_proto_len);
|
||||
if(spdy_version) {
|
||||
client->session = util::make_unique<SpdySession>(client,
|
||||
spdy_version);
|
||||
} else {
|
||||
debug_nextproto_error();
|
||||
client->disconnect();
|
||||
return;
|
||||
}
|
||||
#else // !HAVE_SPDYLAY
|
||||
debug_nextproto_error();
|
||||
client->disconnect();
|
||||
return;
|
||||
#endif // !HAVE_SPDYLAY
|
||||
}
|
||||
} else {
|
||||
client->session = util::make_unique<Http2Session>(client);
|
||||
}
|
||||
int fd = bufferevent_getfd(bev);
|
||||
int val = 1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<char *>(&val), sizeof(val));
|
||||
client->state = CLIENT_CONNECTED;
|
||||
client->on_connect();
|
||||
return;
|
||||
}
|
||||
if(events & BEV_EVENT_EOF) {
|
||||
client->disconnect();
|
||||
return;
|
||||
}
|
||||
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
|
||||
if(client->state == CLIENT_IDLE) {
|
||||
client->disconnect();
|
||||
rv = client->connect();
|
||||
if(rv == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
debug("error/eof\n");
|
||||
client->disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void readcb(bufferevent *bev, void *ptr)
|
||||
{
|
||||
int rv;
|
||||
auto client = static_cast<Client*>(ptr);
|
||||
rv = client->on_read();
|
||||
if(rv != 0) {
|
||||
client->disconnect();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void writecb(bufferevent *bev, void *ptr)
|
||||
{
|
||||
if(evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
||||
return;
|
||||
}
|
||||
int rv;
|
||||
auto client = static_cast<Client*>(ptr);
|
||||
rv = client->on_write();
|
||||
if(rv != 0) {
|
||||
client->disconnect();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void resolve_host()
|
||||
{
|
||||
int rv;
|
||||
addrinfo hints, *res;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = 0;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
|
||||
rv = getaddrinfo(config.host.c_str(), util::utos(config.port).c_str(),
|
||||
&hints, &res);
|
||||
if(rv != 0) {
|
||||
std::cerr << "getaddrinfo() failed: "
|
||||
<< gai_strerror(rv) << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if(res == nullptr) {
|
||||
std::cerr << "No address returned" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config.addrs = res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int client_select_next_proto_cb(SSL* ssl,
|
||||
unsigned char **out, unsigned char *outlen,
|
||||
const unsigned char *in, unsigned int inlen,
|
||||
void *arg)
|
||||
{
|
||||
if(nghttp2_select_next_protocol(out, outlen, in, inlen) > 0) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
#ifdef HAVE_SPDYLAY
|
||||
else if(spdylay_select_next_protocol(out, outlen, in, inlen) > 0) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
#endif
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void print_version(std::ostream& out)
|
||||
{
|
||||
out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void print_usage(std::ostream& out)
|
||||
{
|
||||
out << "Usage: h2load [OPTIONS]... <URI>\n"
|
||||
<< "benchmarking tool for HTTP/2 and SPDY server" << std::endl;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void print_help(std::ostream& out)
|
||||
{
|
||||
print_usage(out);
|
||||
out << "\n"
|
||||
<< " <URI> Specify URI to access.\n"
|
||||
<< "Options:\n"
|
||||
<< " -n, --requests=<N> Number of requests. Default: "
|
||||
<< config.nreqs << "\n"
|
||||
<< " -c, --clients=<N> Number of concurrent clients. Default: "
|
||||
<< config.nclients << "\n"
|
||||
<< " -m, --max-concurrent-streams=<N>\n"
|
||||
<< " Max concurrent streams to issue per session. \n"
|
||||
<< " Default: "
|
||||
<< config.max_concurrent_streams << "\n"
|
||||
<< " -w, --window-bits=<N>\n"
|
||||
<< " Sets the stream level initial window size\n"
|
||||
<< " to (2**<N>)-1. For SPDY, 2**<N> is used\n"
|
||||
<< " instead.\n"
|
||||
<< " -W, --connection-window-bits=<N>\n"
|
||||
<< " Sets the connection level initial window\n"
|
||||
<< " size to 2**<N>-1. This option does not work\n"
|
||||
<< " with SPDY.\n"
|
||||
<< " instead.\n"
|
||||
<< " -v, --verbose Output debug information.\n"
|
||||
<< " --version Display version information and exit.\n"
|
||||
<< " -h, --help Display this help and exit.\n"
|
||||
<< std::endl;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
while(1) {
|
||||
int flag = 0;
|
||||
static option long_options[] = {
|
||||
{"requests", required_argument, nullptr, 'n'},
|
||||
{"clients", required_argument, nullptr, 'c'},
|
||||
{"workers", required_argument, nullptr, 't'},
|
||||
{"max-concurrent-streams", required_argument, nullptr, 'm'},
|
||||
{"window-bits", required_argument, nullptr, 'w'},
|
||||
{"connection-window-bits", required_argument, nullptr, 'W'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"version", no_argument, &flag, 1},
|
||||
{nullptr, 0, nullptr, 0 }
|
||||
};
|
||||
int option_index = 0;
|
||||
auto c = getopt_long(argc, argv, "hvW:c:m:n:t:w:", long_options,
|
||||
&option_index);
|
||||
if(c == -1) {
|
||||
break;
|
||||
}
|
||||
switch(c) {
|
||||
case 'n':
|
||||
config.nreqs = strtoul(optarg, nullptr, 10);
|
||||
break;
|
||||
case 'c':
|
||||
config.nclients = strtoul(optarg, nullptr, 10);
|
||||
break;
|
||||
case 't':
|
||||
config.nworkers = strtoul(optarg, nullptr, 10);
|
||||
break;
|
||||
case 'm':
|
||||
config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
||||
break;
|
||||
case 'w':
|
||||
case 'W': {
|
||||
errno = 0;
|
||||
char *endptr = nullptr;
|
||||
auto n = strtoul(optarg, &endptr, 10);
|
||||
if(errno == 0 && *endptr == '\0' && n < 31) {
|
||||
if(c == 'w') {
|
||||
config.window_bits = n;
|
||||
} else {
|
||||
config.connection_window_bits = n;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "-" << static_cast<char>(c)
|
||||
<< ": specify the integer in the range [0, 30], inclusive"
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'v':
|
||||
config.verbose = true;
|
||||
break;
|
||||
case 'h':
|
||||
print_help(std::cout);
|
||||
exit(EXIT_SUCCESS);
|
||||
case '?':
|
||||
util::show_candidates(argv[optind - 1], long_options);
|
||||
exit(EXIT_FAILURE);
|
||||
case 0:
|
||||
switch(flag) {
|
||||
case 1:
|
||||
// version option
|
||||
print_version(std::cout);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(argc == optind) {
|
||||
std::cerr << "no URI given" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(config.nreqs == 0) {
|
||||
std::cerr << "-n: the number of requests must be strictly greater than 0."
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(config.max_concurrent_streams == 0) {
|
||||
std::cerr << "-m: the max concurrent streams must be strictly greater "
|
||||
<< "than 0."
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(config.nreqs < config.nclients) {
|
||||
std::cerr << "-n, -c: the number of requests must be greater than or "
|
||||
<< "equal to the concurrent clients."
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(struct sigaction));
|
||||
act.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &act, nullptr);
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
|
||||
http_parser_url u;
|
||||
memset(&u, 0, sizeof(u));
|
||||
auto uri = argv[optind];
|
||||
if(http_parser_parse_url(uri, strlen(uri), 0, &u) != 0 ||
|
||||
!util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
|
||||
std::cerr << "invalid URI: " << uri << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
config.scheme = util::get_uri_field(uri, u, UF_SCHEMA);
|
||||
config.host = util::get_uri_field(uri, u, UF_HOST);
|
||||
if(util::has_uri_field(u, UF_PORT)) {
|
||||
config.port = u.port;
|
||||
} else {
|
||||
config.port = util::get_default_port(uri, u);
|
||||
}
|
||||
if(util::has_uri_field(u, UF_PATH)) {
|
||||
config.path = util::get_uri_field(uri, u, UF_PATH);
|
||||
} else {
|
||||
config.path = "/";
|
||||
}
|
||||
|
||||
auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
if(!ssl_ctx) {
|
||||
std::cerr << "Failed to create SSL_CTX: "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx,
|
||||
client_select_next_proto_cb, nullptr);
|
||||
// For nghttp2
|
||||
Headers nva;
|
||||
nva.emplace_back(":scheme", config.scheme);
|
||||
if(config.port != util::get_default_port(uri, u)) {
|
||||
nva.emplace_back(":authority",
|
||||
config.host + ":" + util::utos(config.port));
|
||||
} else {
|
||||
nva.emplace_back(":authority", config.host);
|
||||
}
|
||||
nva.emplace_back(":path", config.path);
|
||||
nva.emplace_back(":method", "GET");
|
||||
|
||||
for(auto& nv : nva) {
|
||||
config.nva.push_back(http2::make_nv(nv.first, nv.second));
|
||||
}
|
||||
|
||||
// For spdylay
|
||||
for(auto& nv : nva) {
|
||||
if(nv.first == ":authority") {
|
||||
config.nv.push_back(":host");
|
||||
} else {
|
||||
config.nv.push_back(nv.first.c_str());
|
||||
}
|
||||
config.nv.push_back(nv.second.c_str());
|
||||
}
|
||||
config.nv.push_back(":version");
|
||||
config.nv.push_back("HTTP/1.1");
|
||||
config.nv.push_back(nullptr);
|
||||
|
||||
resolve_host();
|
||||
|
||||
Worker worker(ssl_ctx, &config);
|
||||
|
||||
std::cout << "starting benchmark..." << std::endl;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
worker.run();
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
||||
|
||||
// Requests which have not been issued due to connection errors, are
|
||||
// counted towards req_failed and req_error.
|
||||
auto req_not_issued = worker.stats.req_todo
|
||||
- worker.stats.req_success - worker.stats.req_failed;
|
||||
worker.stats.req_failed += req_not_issued;
|
||||
worker.stats.req_error += req_not_issued;
|
||||
|
||||
// UI is heavily inspired by weighttp
|
||||
// https://github.com/lighttpd/weighttp
|
||||
size_t rps;
|
||||
int64_t kbps;
|
||||
if(duration > 0) {
|
||||
auto secd = static_cast<double>(duration) / (1000 * 1000);
|
||||
rps = worker.stats.req_todo / secd;
|
||||
kbps = (worker.stats.bytes_head + worker.stats.bytes_body) / secd / 1024;
|
||||
} else {
|
||||
rps = 0;
|
||||
kbps = 0;
|
||||
}
|
||||
|
||||
auto sec = duration / (1000 * 1000);
|
||||
auto millisec = (duration / 1000) % 1000;
|
||||
auto microsec = duration % 1000;
|
||||
|
||||
std::cout << "\n"
|
||||
<< "finished in "
|
||||
<< sec << " sec, "
|
||||
<< millisec << " millisec and "
|
||||
<< microsec << " microsec, "
|
||||
<< rps << " req/s, "
|
||||
<< kbps << " kbytes/s\n"
|
||||
<< "requests: "
|
||||
<< worker.stats.req_todo << " total, "
|
||||
<< worker.stats.req_started << " started, "
|
||||
<< worker.stats.req_done << " done, "
|
||||
<< worker.stats.req_success << " succeeded, "
|
||||
<< worker.stats.req_failed << " failed, "
|
||||
<< worker.stats.req_error << " errored\n"
|
||||
<< "status codes: "
|
||||
<< worker.stats.status[2] << " 2xx, "
|
||||
<< worker.stats.status[3] << " 3xx, "
|
||||
<< worker.stats.status[4] << " 4xx, "
|
||||
<< worker.stats.status[5] << " 5xx\n"
|
||||
<< "traffic: "
|
||||
<< worker.stats.bytes_total << " bytes total, "
|
||||
<< worker.stats.bytes_head << " bytes headers, "
|
||||
<< worker.stats.bytes_body << " bytes data"
|
||||
<< std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace h2load
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
return h2load::main(argc, argv);
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef H2LOAD_H
|
||||
#define H2LOAD_H
|
||||
|
||||
#include "nghttp2_config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
class Session;
|
||||
|
||||
struct Config {
|
||||
std::vector<nghttp2_nv> nva;
|
||||
std::vector<const char*> nv;
|
||||
std::string scheme;
|
||||
std::string host;
|
||||
std::string path;
|
||||
addrinfo *addrs;
|
||||
size_t nreqs;
|
||||
size_t nclients;
|
||||
size_t nworkers;
|
||||
// The maximum number of concurrent streams per session.
|
||||
size_t max_concurrent_streams;
|
||||
size_t window_bits;
|
||||
size_t connection_window_bits;
|
||||
uint16_t port;
|
||||
bool verbose;
|
||||
|
||||
Config();
|
||||
~Config();
|
||||
};
|
||||
|
||||
struct Stats {
|
||||
// The total number of requests
|
||||
size_t req_todo;
|
||||
// The number of requests issued so far
|
||||
size_t req_started;
|
||||
// The number of requests finished
|
||||
size_t req_done;
|
||||
// The number of requests marked as success. This is subset of
|
||||
// req_done.
|
||||
size_t req_success;
|
||||
// The number of requests failed. This is subset of req_done.
|
||||
size_t req_failed;
|
||||
// The number of requests failed due to network errors. This is
|
||||
// subset of req_failed.
|
||||
size_t req_error;
|
||||
// The number of bytes received on the "wire". If SSL/TLS is used,
|
||||
// this is the number of decrypted bytes the application received.
|
||||
int64_t bytes_total;
|
||||
// The number of bytes received in HEADERS frame payload.
|
||||
int64_t bytes_head;
|
||||
// The number of bytes received in DATA frame.
|
||||
int64_t bytes_body;
|
||||
// The number of each HTTP status category, status[i] is status code
|
||||
// in the range [i*100, (i+1)*100).
|
||||
size_t status[6];
|
||||
};
|
||||
|
||||
enum ClientState {
|
||||
CLIENT_IDLE,
|
||||
CLIENT_CONNECTED
|
||||
};
|
||||
|
||||
struct Client;
|
||||
|
||||
struct Worker {
|
||||
std::vector<std::unique_ptr<Client>> clients;
|
||||
Stats stats;
|
||||
event_base *evbase;
|
||||
SSL_CTX *ssl_ctx;
|
||||
Config *config;
|
||||
size_t progress_interval;
|
||||
bool term_timer_started;
|
||||
|
||||
Worker(SSL_CTX *ssl_ctx, Config *config);
|
||||
~Worker();
|
||||
void run();
|
||||
void schedule_terminate();
|
||||
void terminate_session();
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
int status_success;
|
||||
Stream();
|
||||
};
|
||||
|
||||
struct Client {
|
||||
std::unordered_map<int32_t, Stream> streams;
|
||||
std::unique_ptr<Session> session;
|
||||
Worker *worker;
|
||||
SSL *ssl;
|
||||
bufferevent *bev;
|
||||
addrinfo *next_addr;
|
||||
ClientState state;
|
||||
|
||||
Client(Worker *worker);
|
||||
~Client();
|
||||
int connect();
|
||||
void disconnect();
|
||||
void submit_request();
|
||||
void process_abandoned_streams();
|
||||
void report_progress();
|
||||
void terminate_session();
|
||||
int on_connect();
|
||||
int on_read();
|
||||
int on_write();
|
||||
void on_request(int32_t stream_id);
|
||||
void on_header(int32_t stream_id,
|
||||
const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen);
|
||||
void on_stream_close(int32_t stream_id, bool success);
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
||||
#endif // H2LOAD_H
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include "h2load_http2_session.h"
|
||||
|
||||
#include "h2load.h"
|
||||
|
||||
namespace h2load {
|
||||
|
||||
Http2Session::Http2Session(Client *client)
|
||||
: client_(client),
|
||||
session_(nullptr)
|
||||
{}
|
||||
|
||||
Http2Session::~Http2Session()
|
||||
{
|
||||
nghttp2_session_del(session_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int before_frame_send_callback
|
||||
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
if(frame->hd.type == NGHTTP2_HEADERS &&
|
||||
frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
||||
client->on_request(frame->hd.stream_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_header_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame,
|
||||
const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
if(frame->hd.type != NGHTTP2_HEADERS ||
|
||||
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
|
||||
return 0;
|
||||
}
|
||||
client->on_header(frame->hd.stream_id, name, namelen, value, valuelen);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_frame_recv_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
if(frame->hd.type != NGHTTP2_HEADERS ||
|
||||
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
|
||||
return 0;
|
||||
}
|
||||
client->worker->stats.bytes_head += frame->hd.length;
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_data_chunk_recv_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id,
|
||||
const uint8_t *data, size_t len, void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
client->worker->stats.bytes_body += len;
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_stream_close_callback
|
||||
(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Http2Session::on_connect()
|
||||
{
|
||||
nghttp2_session_callbacks callbacks = {0};
|
||||
callbacks.before_frame_send_callback = before_frame_send_callback;
|
||||
callbacks.on_frame_recv_callback = on_frame_recv_callback;
|
||||
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
|
||||
callbacks.on_stream_close_callback = on_stream_close_callback;
|
||||
callbacks.on_header_callback = on_header_callback;
|
||||
|
||||
nghttp2_session_client_new(&session_, &callbacks, client_);
|
||||
|
||||
nghttp2_settings_entry iv[2];
|
||||
iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
||||
iv[0].value = 0;
|
||||
iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
iv[1].value = (1 << client_->worker->config->window_bits) - 1;
|
||||
nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv,
|
||||
sizeof(iv) / sizeof(iv[0]));
|
||||
|
||||
auto extra_connection_window =
|
||||
(1 << client_->worker->config->connection_window_bits) - 1
|
||||
- NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
|
||||
if(extra_connection_window != 0) {
|
||||
nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0,
|
||||
extra_connection_window);
|
||||
}
|
||||
|
||||
bufferevent_write(client_->bev, NGHTTP2_CLIENT_CONNECTION_HEADER,
|
||||
NGHTTP2_CLIENT_CONNECTION_HEADER_LEN);
|
||||
}
|
||||
|
||||
void Http2Session::submit_request()
|
||||
{
|
||||
nghttp2_submit_request(session_, 0,
|
||||
client_->worker->config->nva.data(),
|
||||
client_->worker->config->nva.size(),
|
||||
nullptr, nullptr);
|
||||
}
|
||||
|
||||
ssize_t Http2Session::on_read()
|
||||
{
|
||||
int rv;
|
||||
auto input = bufferevent_get_input(client_->bev);
|
||||
auto inputlen = evbuffer_get_length(input);
|
||||
auto mem = evbuffer_pullup(input, -1);
|
||||
|
||||
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
|
||||
if(rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
evbuffer_drain(input, rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int Http2Session::on_write()
|
||||
{
|
||||
int rv;
|
||||
auto output = bufferevent_get_output(client_->bev);
|
||||
for(;;) {
|
||||
const uint8_t *data;
|
||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
||||
|
||||
if(datalen < 0) {
|
||||
return -1;
|
||||
}
|
||||
if(datalen == 0) {
|
||||
break;
|
||||
}
|
||||
rv = evbuffer_add(output, data, datalen);
|
||||
if(rv == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(nghttp2_session_want_read(session_) == 0 &&
|
||||
nghttp2_session_want_write(session_) == 0 &&
|
||||
evbuffer_get_length(output) == 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Http2Session::terminate()
|
||||
{
|
||||
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
|
||||
}
|
||||
|
||||
} // namespace h2load
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef H2LOAD_HTTP2_SESSION_H
|
||||
#define H2LOAD_HTTP2_SESSION_H
|
||||
|
||||
#include "h2load_session.h"
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
class Client;
|
||||
|
||||
class Http2Session : public Session {
|
||||
public:
|
||||
Http2Session(Client *client);
|
||||
virtual ~Http2Session();
|
||||
virtual void on_connect();
|
||||
virtual void submit_request();
|
||||
virtual ssize_t on_read();
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
private:
|
||||
Client *client_;
|
||||
nghttp2_session *session_;
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
||||
#endif // H2LOAD_HTTP2_SESSION_H
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef H2LOAD_SESSION_H
|
||||
#define H2LOAD_SESSION_H
|
||||
|
||||
#include "nghttp2_config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
class Session {
|
||||
public:
|
||||
virtual ~Session() {}
|
||||
// Called when the connection was made.
|
||||
virtual void on_connect() = 0;
|
||||
// Called when one request must be issued.
|
||||
virtual void submit_request() = 0;
|
||||
// Called when incoming bytes are available. The subclass has to
|
||||
// return the number of bytes read.
|
||||
virtual ssize_t on_read() = 0;
|
||||
// Called when write is available. Returns 0 on success, otherwise
|
||||
// return -1.
|
||||
virtual int on_write() = 0;
|
||||
// Called when the underlying session must be terminated.
|
||||
virtual void terminate() = 0;
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
||||
#endif // H2LOAD_SESSION_H
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include "h2load_spdy_session.h"
|
||||
|
||||
#include "h2load.h"
|
||||
|
||||
namespace h2load {
|
||||
|
||||
SpdySession::SpdySession(Client *client, uint16_t spdy_version)
|
||||
: client_(client),
|
||||
session_(nullptr),
|
||||
spdy_version_(spdy_version)
|
||||
{}
|
||||
|
||||
SpdySession::~SpdySession()
|
||||
{
|
||||
spdylay_session_del(session_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void before_ctrl_send_callback
|
||||
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
if(type != SPDYLAY_SYN_STREAM) {
|
||||
return;
|
||||
}
|
||||
client->on_request(frame->syn_stream.stream_id);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void on_ctrl_recv_callback(spdylay_session *session,
|
||||
spdylay_frame_type type,
|
||||
spdylay_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
if(type != SPDYLAY_SYN_REPLY) {
|
||||
return;
|
||||
}
|
||||
for(auto p = frame->syn_reply.nv; *p; p += 2) {
|
||||
auto name = *p;
|
||||
auto value = *(p + 1);
|
||||
client->on_header(frame->syn_reply.stream_id,
|
||||
reinterpret_cast<const uint8_t*>(name),
|
||||
strlen(name),
|
||||
reinterpret_cast<const uint8_t*>(value),
|
||||
strlen(value));
|
||||
}
|
||||
client->worker->stats.bytes_head += frame->syn_reply.hd.length;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void on_data_chunk_recv_callback
|
||||
(spdylay_session *session, uint8_t flags, int32_t stream_id,
|
||||
const uint8_t *data, size_t len, void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
client->worker->stats.bytes_body += len;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void on_stream_close_callback
|
||||
(spdylay_session *session, int32_t stream_id, spdylay_status_code status_code,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
client->on_stream_close(stream_id, status_code == SPDYLAY_OK);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t send_callback(spdylay_session *session,
|
||||
const uint8_t *data, size_t length, int flags,
|
||||
void *user_data)
|
||||
{
|
||||
auto client = static_cast<Client*>(user_data);
|
||||
auto output = bufferevent_get_output(client->bev);
|
||||
evbuffer_add(output, data, length);
|
||||
return length;
|
||||
}
|
||||
} //namespace
|
||||
|
||||
void SpdySession::on_connect()
|
||||
{
|
||||
spdylay_session_callbacks callbacks = {0};
|
||||
callbacks.send_callback = send_callback;
|
||||
callbacks.before_ctrl_send_callback = before_ctrl_send_callback;
|
||||
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
|
||||
callbacks.on_stream_close_callback = on_stream_close_callback;
|
||||
callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback;
|
||||
|
||||
spdylay_session_client_new(&session_, spdy_version_, &callbacks, client_);
|
||||
|
||||
spdylay_settings_entry iv[1];
|
||||
iv[0].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
iv[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
|
||||
iv[0].value = (1 << client_->worker->config->window_bits);
|
||||
spdylay_submit_settings(session_, SPDYLAY_FLAG_SETTINGS_NONE, iv,
|
||||
sizeof(iv) / sizeof(iv[0]));
|
||||
}
|
||||
|
||||
void SpdySession::submit_request()
|
||||
{
|
||||
spdylay_submit_request(session_, 0, client_->worker->config->nv.data(),
|
||||
nullptr, nullptr);
|
||||
}
|
||||
|
||||
ssize_t SpdySession::on_read()
|
||||
{
|
||||
int rv;
|
||||
auto input = bufferevent_get_input(client_->bev);
|
||||
auto inputlen = evbuffer_get_length(input);
|
||||
auto mem = evbuffer_pullup(input, -1);
|
||||
|
||||
rv = spdylay_session_mem_recv(session_, mem, inputlen);
|
||||
if(rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
evbuffer_drain(input, rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int SpdySession::on_write()
|
||||
{
|
||||
int rv;
|
||||
rv = spdylay_session_send(session_);
|
||||
if(rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
if(spdylay_session_want_read(session_) == 0 &&
|
||||
spdylay_session_want_write(session_) == 0 &&
|
||||
evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SpdySession::terminate()
|
||||
{
|
||||
spdylay_session_fail_session(session_, SPDYLAY_OK);
|
||||
}
|
||||
|
||||
} // namespace h2load
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2.0 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef H2LOAD_SPDY_SESSION_H
|
||||
#define H2LOAD_SPDY_SESSION_H
|
||||
|
||||
#include "h2load_session.h"
|
||||
|
||||
#include <spdylay/spdylay.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
class Client;
|
||||
|
||||
class SpdySession : public Session {
|
||||
public:
|
||||
SpdySession(Client *client, uint16_t spdy_version);
|
||||
virtual ~SpdySession();
|
||||
virtual void on_connect();
|
||||
virtual void submit_request();
|
||||
virtual ssize_t on_read();
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
private:
|
||||
Client *client_;
|
||||
spdylay_session *session_;
|
||||
uint16_t spdy_version_;
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
||||
#endif // H2LOAD_SPDY_SESSION_H
|
Loading…
Reference in New Issue