h2load: Support multiple URIs
Supplying multiple URIs can simulate more real life situation on server side. For example, we can supply URIs of html, css and js and benchmark the server. The -m option is updated so that it defaults to the number of supplied URIs.
This commit is contained in:
parent
5b81f7c713
commit
843ecd8cc1
135
src/h2load.cc
135
src/h2load.cc
|
@ -63,7 +63,7 @@ Config::Config()
|
||||||
nreqs(1),
|
nreqs(1),
|
||||||
nclients(1),
|
nclients(1),
|
||||||
nthreads(1),
|
nthreads(1),
|
||||||
max_concurrent_streams(1),
|
max_concurrent_streams(-1),
|
||||||
window_bits(16),
|
window_bits(16),
|
||||||
connection_window_bits(16),
|
connection_window_bits(16),
|
||||||
port(0),
|
port(0),
|
||||||
|
@ -111,6 +111,7 @@ Client::Client(Worker *worker)
|
||||||
ssl(nullptr),
|
ssl(nullptr),
|
||||||
bev(nullptr),
|
bev(nullptr),
|
||||||
next_addr(config.addrs),
|
next_addr(config.addrs),
|
||||||
|
reqidx(0),
|
||||||
state(CLIENT_IDLE)
|
state(CLIENT_IDLE)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -279,7 +280,7 @@ int Client::on_connect()
|
||||||
|
|
||||||
auto nreq = std::min(worker->stats.req_todo - worker->stats.req_started,
|
auto nreq = std::min(worker->stats.req_todo - worker->stats.req_started,
|
||||||
std::min(worker->stats.req_todo / worker->clients.size(),
|
std::min(worker->stats.req_todo / worker->clients.size(),
|
||||||
config.max_concurrent_streams));
|
(size_t)config.max_concurrent_streams));
|
||||||
for(; nreq > 0; --nreq) {
|
for(; nreq > 0; --nreq) {
|
||||||
submit_request();
|
submit_request();
|
||||||
}
|
}
|
||||||
|
@ -499,6 +500,26 @@ void resolve_host()
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string get_reqline(const char *uri, const http_parser_url& u)
|
||||||
|
{
|
||||||
|
std::string reqline;
|
||||||
|
|
||||||
|
if(util::has_uri_field(u, UF_PATH)) {
|
||||||
|
reqline = util::get_uri_field(uri, u, UF_PATH);
|
||||||
|
} else {
|
||||||
|
reqline = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(util::has_uri_field(u, UF_QUERY)) {
|
||||||
|
reqline += "?";
|
||||||
|
reqline += util::get_uri_field(uri, u, UF_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reqline;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int client_select_next_proto_cb(SSL* ssl,
|
int client_select_next_proto_cb(SSL* ssl,
|
||||||
unsigned char **out, unsigned char *outlen,
|
unsigned char **out, unsigned char *outlen,
|
||||||
|
@ -527,7 +548,7 @@ void print_version(std::ostream& out)
|
||||||
namespace {
|
namespace {
|
||||||
void print_usage(std::ostream& out)
|
void print_usage(std::ostream& out)
|
||||||
{
|
{
|
||||||
out << R"(Usage: h2load [OPTIONS]... <URI>
|
out << R"(Usage: h2load [OPTIONS]... <URI>...
|
||||||
benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
|
benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -538,7 +559,13 @@ void print_help(std::ostream& out)
|
||||||
print_usage(out);
|
print_usage(out);
|
||||||
|
|
||||||
out << R"(
|
out << R"(
|
||||||
<URI> Specify URI to access.
|
<URI> Specify URI to access. Multiple URIs can be
|
||||||
|
specified. URIs are used in this order for each
|
||||||
|
client. All URIs are used, then first URI is
|
||||||
|
used and then 2nd URI, and so on. The scheme,
|
||||||
|
host and port in the subsequent URIs, if present,
|
||||||
|
are ignored. Those in the first URI are used
|
||||||
|
solely.
|
||||||
Options:
|
Options:
|
||||||
-n, --requests=<N> Number of requests. Default: )"
|
-n, --requests=<N> Number of requests. Default: )"
|
||||||
<< config.nreqs << R"(
|
<< config.nreqs << R"(
|
||||||
|
@ -546,10 +573,10 @@ Options:
|
||||||
<< config.nclients << R"(
|
<< config.nclients << R"(
|
||||||
-t, --threads=<N> Number of native threads. Default: )"
|
-t, --threads=<N> Number of native threads. Default: )"
|
||||||
<< config.nthreads << R"(
|
<< config.nthreads << R"(
|
||||||
-m, --max-concurrent-streams=<N>
|
-m, --max-concurrent-streams=(auto|<N>)
|
||||||
Max concurrent streams to issue per session.
|
Max concurrent streams to issue per session. If
|
||||||
Default: )"
|
"auto" is given, the number of given URIs is
|
||||||
<< config.max_concurrent_streams << R"(
|
used. Default: auto
|
||||||
-w, --window-bits=<N>
|
-w, --window-bits=<N>
|
||||||
Sets the stream level initial window size to
|
Sets the stream level initial window size to
|
||||||
(2**<N>)-1. For SPDY, 2**<N> is used instead.
|
(2**<N>)-1. For SPDY, 2**<N> is used instead.
|
||||||
|
@ -598,7 +625,11 @@ int main(int argc, char **argv)
|
||||||
config.nthreads = strtoul(optarg, nullptr, 10);
|
config.nthreads = strtoul(optarg, nullptr, 10);
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
|
if(util::strieq("auto", optarg)) {
|
||||||
|
config.max_concurrent_streams = -1;
|
||||||
|
} else {
|
||||||
config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'w':
|
case 'w':
|
||||||
case 'W': {
|
case 'W': {
|
||||||
|
@ -687,6 +718,17 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
ssl::LibsslGlobalLock();
|
ssl::LibsslGlobalLock();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// First URI is treated specially. We use scheme, host and port of
|
||||||
|
// this URI and ignore those in the remaining URIs if present.
|
||||||
http_parser_url u;
|
http_parser_url u;
|
||||||
memset(&u, 0, sizeof(u));
|
memset(&u, 0, sizeof(u));
|
||||||
auto uri = argv[optind];
|
auto uri = argv[optind];
|
||||||
|
@ -703,48 +745,71 @@ int main(int argc, char **argv)
|
||||||
} else {
|
} else {
|
||||||
config.port = util::get_default_port(uri, u);
|
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());
|
std::vector<std::string> reqlines;
|
||||||
if(!ssl_ctx) {
|
|
||||||
std::cerr << "Failed to create SSL_CTX: "
|
reqlines.push_back(get_reqline(uri, u));
|
||||||
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
|
||||||
|
++optind;
|
||||||
|
for(int i = optind; i < argc; ++i) {
|
||||||
|
memset(&u, 0, sizeof(u));
|
||||||
|
|
||||||
|
auto uri = argv[i];
|
||||||
|
|
||||||
|
if(http_parser_parse_url(uri, strlen(uri), 0, &u) != 0) {
|
||||||
|
std::cerr << "invalid URI: " << uri << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx,
|
|
||||||
client_select_next_proto_cb, nullptr);
|
reqlines.push_back(get_reqline(uri, u));
|
||||||
// For nghttp2
|
}
|
||||||
Headers nva;
|
|
||||||
nva.emplace_back(":scheme", config.scheme);
|
if(config.max_concurrent_streams == -1) {
|
||||||
|
config.max_concurrent_streams = reqlines.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Headers shared_nva;
|
||||||
|
shared_nva.emplace_back(":scheme", config.scheme);
|
||||||
if(config.port != util::get_default_port(uri, u)) {
|
if(config.port != util::get_default_port(uri, u)) {
|
||||||
nva.emplace_back(":authority",
|
shared_nva.emplace_back(":authority",
|
||||||
config.host + ":" + util::utos(config.port));
|
config.host + ":" + util::utos(config.port));
|
||||||
} else {
|
} else {
|
||||||
nva.emplace_back(":authority", config.host);
|
shared_nva.emplace_back(":authority", config.host);
|
||||||
}
|
}
|
||||||
nva.emplace_back(":path", config.path);
|
shared_nva.emplace_back(":method", "GET");
|
||||||
nva.emplace_back(":method", "GET");
|
|
||||||
|
|
||||||
for(auto& nv : nva) {
|
for(auto& req : reqlines) {
|
||||||
config.nva.push_back(http2::make_nv(nv.first, nv.second));
|
// For nghttp2
|
||||||
|
std::vector<nghttp2_nv> nva;
|
||||||
|
|
||||||
|
nva.push_back(http2::make_nv_ls(":path", req));
|
||||||
|
|
||||||
|
for(auto& nv : shared_nva) {
|
||||||
|
nva.push_back(http2::make_nv(nv.first, nv.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.nva.push_back(std::move(nva));
|
||||||
|
|
||||||
// For spdylay
|
// For spdylay
|
||||||
for(auto& nv : nva) {
|
std::vector<const char*> cva;
|
||||||
|
|
||||||
|
cva.push_back(":path");
|
||||||
|
cva.push_back(req.c_str());
|
||||||
|
|
||||||
|
for(auto& nv : shared_nva) {
|
||||||
if(nv.first == ":authority") {
|
if(nv.first == ":authority") {
|
||||||
config.nv.push_back(":host");
|
cva.push_back(":host");
|
||||||
} else {
|
} else {
|
||||||
config.nv.push_back(nv.first.c_str());
|
cva.push_back(nv.first.c_str());
|
||||||
}
|
}
|
||||||
config.nv.push_back(nv.second.c_str());
|
cva.push_back(nv.second.c_str());
|
||||||
|
}
|
||||||
|
cva.push_back(":version");
|
||||||
|
cva.push_back("HTTP/1.1");
|
||||||
|
cva.push_back(nullptr);
|
||||||
|
|
||||||
|
config.nv.push_back(std::move(cva));
|
||||||
}
|
}
|
||||||
config.nv.push_back(":version");
|
|
||||||
config.nv.push_back("HTTP/1.1");
|
|
||||||
config.nv.push_back(nullptr);
|
|
||||||
|
|
||||||
resolve_host();
|
resolve_host();
|
||||||
|
|
||||||
|
|
|
@ -48,17 +48,16 @@ namespace h2load {
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
std::vector<nghttp2_nv> nva;
|
std::vector<std::vector<nghttp2_nv>> nva;
|
||||||
std::vector<const char*> nv;
|
std::vector<std::vector<const char*>> nv;
|
||||||
std::string scheme;
|
std::string scheme;
|
||||||
std::string host;
|
std::string host;
|
||||||
std::string path;
|
|
||||||
addrinfo *addrs;
|
addrinfo *addrs;
|
||||||
size_t nreqs;
|
size_t nreqs;
|
||||||
size_t nclients;
|
size_t nclients;
|
||||||
size_t nthreads;
|
size_t nthreads;
|
||||||
// The maximum number of concurrent streams per session.
|
// The maximum number of concurrent streams per session.
|
||||||
size_t max_concurrent_streams;
|
ssize_t max_concurrent_streams;
|
||||||
size_t window_bits;
|
size_t window_bits;
|
||||||
size_t connection_window_bits;
|
size_t connection_window_bits;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
|
@ -132,6 +131,7 @@ struct Client {
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
bufferevent *bev;
|
bufferevent *bev;
|
||||||
addrinfo *next_addr;
|
addrinfo *next_addr;
|
||||||
|
size_t reqidx;
|
||||||
ClientState state;
|
ClientState state;
|
||||||
|
|
||||||
Client(Worker *worker);
|
Client(Worker *worker);
|
||||||
|
|
|
@ -141,9 +141,14 @@ void Http2Session::on_connect()
|
||||||
|
|
||||||
void Http2Session::submit_request()
|
void Http2Session::submit_request()
|
||||||
{
|
{
|
||||||
nghttp2_submit_request(session_, 0,
|
auto config = client_->worker->config;
|
||||||
client_->worker->config->nva.data(),
|
auto& nva = config->nva[client_->reqidx++];
|
||||||
client_->worker->config->nva.size(),
|
|
||||||
|
if(client_->reqidx == config->nva.size()) {
|
||||||
|
client_->reqidx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp2_submit_request(session_, 0, nva.data(), nva.size(),
|
||||||
nullptr, nullptr);
|
nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,8 +150,14 @@ void SpdySession::on_connect()
|
||||||
|
|
||||||
void SpdySession::submit_request()
|
void SpdySession::submit_request()
|
||||||
{
|
{
|
||||||
spdylay_submit_request(session_, 0, client_->worker->config->nv.data(),
|
auto config = client_->worker->config;
|
||||||
nullptr, nullptr);
|
auto& nv = config->nv[client_->reqidx++];
|
||||||
|
|
||||||
|
if(client_->reqidx == config->nv.size()) {
|
||||||
|
client_->reqidx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdylay_submit_request(session_, 0, nv.data(), nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t SpdySession::on_read()
|
ssize_t SpdySession::on_read()
|
||||||
|
|
Loading…
Reference in New Issue