diff --git a/src/h2load.cc b/src/h2load.cc index 078f20d1..47b0d7e5 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -63,7 +63,7 @@ Config::Config() nreqs(1), nclients(1), nthreads(1), - max_concurrent_streams(1), + max_concurrent_streams(-1), window_bits(16), connection_window_bits(16), port(0), @@ -111,6 +111,7 @@ Client::Client(Worker *worker) ssl(nullptr), bev(nullptr), next_addr(config.addrs), + reqidx(0), state(CLIENT_IDLE) {} @@ -279,7 +280,7 @@ int Client::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)); + (size_t)config.max_concurrent_streams)); for(; nreq > 0; --nreq) { submit_request(); } @@ -499,6 +500,26 @@ void resolve_host() } } // 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 { int client_select_next_proto_cb(SSL* ssl, unsigned char **out, unsigned char *outlen, @@ -527,7 +548,7 @@ void print_version(std::ostream& out) namespace { void print_usage(std::ostream& out) { - out << R"(Usage: h2load [OPTIONS]... + out << R"(Usage: h2load [OPTIONS]... ... benchmarking tool for HTTP/2 and SPDY server)" << std::endl; } } // namespace @@ -538,7 +559,13 @@ void print_help(std::ostream& out) print_usage(out); out << R"( - Specify URI to access. + 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: -n, --requests= Number of requests. Default: )" << config.nreqs << R"( @@ -546,10 +573,10 @@ Options: << config.nclients << R"( -t, --threads= Number of native threads. Default: )" << config.nthreads << R"( - -m, --max-concurrent-streams= - Max concurrent streams to issue per session. - Default: )" - << config.max_concurrent_streams << R"( + -m, --max-concurrent-streams=(auto|) + Max concurrent streams to issue per session. If + "auto" is given, the number of given URIs is + used. Default: auto -w, --window-bits= Sets the stream level initial window size to (2**)-1. For SPDY, 2** is used instead. @@ -598,7 +625,11 @@ int main(int argc, char **argv) config.nthreads = strtoul(optarg, nullptr, 10); break; case 'm': - config.max_concurrent_streams = strtoul(optarg, nullptr, 10); + if(util::strieq("auto", optarg)) { + config.max_concurrent_streams = -1; + } else { + config.max_concurrent_streams = strtoul(optarg, nullptr, 10); + } break; case 'w': case 'W': { @@ -687,6 +718,17 @@ int main(int argc, char **argv) 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; memset(&u, 0, sizeof(u)); auto uri = argv[optind]; @@ -703,48 +745,71 @@ int main(int argc, char **argv) } 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 = "/"; + + std::vector reqlines; + + reqlines.push_back(get_reqline(uri, u)); + + ++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); + } + + reqlines.push_back(get_reqline(uri, u)); } - 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); + if(config.max_concurrent_streams == -1) { + config.max_concurrent_streams = reqlines.size(); } - 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); + + Headers shared_nva; + shared_nva.emplace_back(":scheme", config.scheme); if(config.port != util::get_default_port(uri, u)) { - nva.emplace_back(":authority", + shared_nva.emplace_back(":authority", config.host + ":" + util::utos(config.port)); } else { - nva.emplace_back(":authority", config.host); + shared_nva.emplace_back(":authority", config.host); } - nva.emplace_back(":path", config.path); - nva.emplace_back(":method", "GET"); + shared_nva.emplace_back(":method", "GET"); - for(auto& nv : nva) { - config.nva.push_back(http2::make_nv(nv.first, nv.second)); - } + for(auto& req : reqlines) { + // For nghttp2 + std::vector nva; - // For spdylay - for(auto& nv : nva) { - if(nv.first == ":authority") { - config.nv.push_back(":host"); - } else { - config.nv.push_back(nv.first.c_str()); + 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.nv.push_back(nv.second.c_str()); + + config.nva.push_back(std::move(nva)); + + // For spdylay + std::vector cva; + + cva.push_back(":path"); + cva.push_back(req.c_str()); + + for(auto& nv : shared_nva) { + if(nv.first == ":authority") { + cva.push_back(":host"); + } else { + cva.push_back(nv.first.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(); diff --git a/src/h2load.h b/src/h2load.h index ce2af06a..c13cfb2f 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -48,17 +48,16 @@ namespace h2load { class Session; struct Config { - std::vector nva; - std::vector nv; + std::vector> nva; + std::vector> nv; std::string scheme; std::string host; - std::string path; addrinfo *addrs; size_t nreqs; size_t nclients; size_t nthreads; // The maximum number of concurrent streams per session. - size_t max_concurrent_streams; + ssize_t max_concurrent_streams; size_t window_bits; size_t connection_window_bits; uint16_t port; @@ -132,6 +131,7 @@ struct Client { SSL *ssl; bufferevent *bev; addrinfo *next_addr; + size_t reqidx; ClientState state; Client(Worker *worker); diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc index 68b43fed..b831d356 100644 --- a/src/h2load_http2_session.cc +++ b/src/h2load_http2_session.cc @@ -141,9 +141,14 @@ void Http2Session::on_connect() void Http2Session::submit_request() { - nghttp2_submit_request(session_, 0, - client_->worker->config->nva.data(), - client_->worker->config->nva.size(), + auto config = client_->worker->config; + auto& nva = config->nva[client_->reqidx++]; + + if(client_->reqidx == config->nva.size()) { + client_->reqidx = 0; + } + + nghttp2_submit_request(session_, 0, nva.data(), nva.size(), nullptr, nullptr); } diff --git a/src/h2load_spdy_session.cc b/src/h2load_spdy_session.cc index 9f8a7dab..f453d127 100644 --- a/src/h2load_spdy_session.cc +++ b/src/h2load_spdy_session.cc @@ -150,8 +150,14 @@ void SpdySession::on_connect() void SpdySession::submit_request() { - spdylay_submit_request(session_, 0, client_->worker->config->nv.data(), - nullptr, nullptr); + auto config = client_->worker->config; + 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()