2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2014 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 #include <netinet/in.h>
30 #include <netinet/tcp.h>
43 #include <spdylay/spdylay.h>
44 #endif // HAVE_SPDYLAY
46 #include <openssl/err.h>
47 #include <openssl/conf.h>
49 #include "http-parser/http_parser.h"
51 #include "h2load_http2_session.h"
53 #include "h2load_spdy_session.h"
54 #endif // HAVE_SPDYLAY
60 using namespace nghttp2;
65 : addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
66 max_concurrent_streams(-1), window_bits(16), connection_window_bits(16),
67 no_tls_proto(PROTO_HTTP2), port(0), default_port(0), verbose(false) {}
69 Config::~Config() { freeaddrinfo(addrs); }
74 void debug(const char *format, ...) {
76 fprintf(stderr, "[DEBUG] ");
79 vfprintf(stderr, format, ap);
86 void debug_nextproto_error() {
88 debug("no supported protocol was negotiated, expected: %s, "
89 "spdy/2, spdy/3, spdy/3.1\n",
90 NGHTTP2_PROTO_VERSION_ID);
91 #else // !HAVE_SPDYLAY
92 debug("no supported protocol was negotiated, expected: %s\n",
93 NGHTTP2_PROTO_VERSION_ID);
94 #endif // !HAVE_SPDYLAY
98 RequestStat::RequestStat() : completed(false) {}
100 Stats::Stats(size_t req_todo)
101 : req_todo(0), req_started(0), req_done(0), req_success(0),
102 req_status_success(0), req_failed(0), req_error(0), bytes_total(0),
103 bytes_head(0), bytes_body(0), status(), req_stats(req_todo) {}
105 Stream::Stream() : status_success(-1) {}
108 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
109 auto client = static_cast<Client *>(w->data);
110 if (client->do_read() != 0) {
117 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
118 auto client = static_cast<Client *>(w->data);
119 auto rv = client->do_write();
120 if (rv == Client::ERR_CONNECT_FAIL) {
121 client->disconnect();
122 rv = client->connect();
135 Client::Client(Worker *worker, size_t req_todo)
136 : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
137 state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0),
139 ev_io_init(&wev, writecb, 0, EV_WRITE);
140 ev_io_init(&rev, readcb, 0, EV_READ);
146 Client::~Client() { disconnect(); }
148 int Client::do_read() { return readfn(*this); }
149 int Client::do_write() { return writefn(*this); }
151 int Client::connect() {
153 auto addr = next_addr;
154 next_addr = next_addr->ai_next;
155 fd = util::create_nonblock_socket(addr->ai_family);
159 if (config.scheme == "https") {
160 ssl = SSL_new(worker->ssl_ctx);
162 auto config = worker->config;
164 if (!util::numeric_host(config->host.c_str())) {
165 SSL_set_tlsext_host_name(ssl, config->host.c_str());
169 SSL_set_connect_state(ssl);
172 auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
173 if (rv != 0 && errno != EINPROGRESS) {
189 writefn = &Client::connected;
191 on_readfn = &Client::on_read;
192 on_writefn = &Client::on_write;
194 ev_io_set(&rev, fd, EV_READ);
195 ev_io_set(&wev, fd, EV_WRITE);
197 ev_io_start(worker->loop, &wev);
202 void Client::fail() {
203 process_abandoned_streams();
208 void Client::disconnect() {
212 ev_io_stop(worker->loop, &wev);
213 ev_io_stop(worker->loop, &rev);
215 SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
222 shutdown(fd, SHUT_WR);
228 void Client::submit_request() {
229 auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
230 session->submit_request(req_stat);
234 void Client::process_abandoned_streams() {
235 auto req_abandoned = req_todo - req_done;
237 worker->stats.req_failed += req_abandoned;
238 worker->stats.req_error += req_abandoned;
239 worker->stats.req_done += req_abandoned;
244 void Client::report_progress() {
245 if (worker->id == 0 &&
246 worker->stats.req_done % worker->progress_interval == 0) {
247 std::cout << "progress: "
248 << worker->stats.req_done * 100 / worker->stats.req_todo
249 << "% done" << std::endl;
254 const char *get_tls_protocol(SSL *ssl) {
255 auto session = SSL_get_session(ssl);
257 switch (session->ssl_version) {
275 void print_server_tmp_key(SSL *ssl) {
276 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
279 if (!SSL_get_server_tmp_key(ssl, &key)) {
283 auto key_del = defer(EVP_PKEY_free, key);
285 std::cout << "Server Temp Key: ";
287 switch (EVP_PKEY_id(key)) {
289 std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
292 std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
295 auto ec = EVP_PKEY_get1_EC_KEY(key);
296 auto ec_del = defer(EC_KEY_free, ec);
297 auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
298 auto cname = EC_curve_nid2nist(nid);
300 cname = OBJ_nid2sn(nid);
303 std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
308 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
312 void Client::report_tls_info() {
313 if (worker->id == 0 && !worker->tls_info_report_done) {
314 worker->tls_info_report_done = true;
315 auto cipher = SSL_get_current_cipher(ssl);
316 std::cout << "Protocol: " << get_tls_protocol(ssl) << "\n"
317 << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
318 print_server_tmp_key(ssl);
322 void Client::terminate_session() { session->terminate(); }
324 void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
326 void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
327 const uint8_t *value, size_t valuelen) {
328 auto itr = streams.find(stream_id);
329 if (itr == std::end(streams)) {
332 auto &stream = (*itr).second;
333 if (stream.status_success == -1 && namelen == 7 &&
334 util::streq(":status", 7, name, namelen)) {
336 for (size_t i = 0; i < valuelen; ++i) {
337 if ('0' <= value[i] && value[i] <= '9') {
339 status += value[i] - '0';
341 stream.status_success = 0;
349 if (status >= 200 && status < 300) {
350 ++worker->stats.status[2];
351 stream.status_success = 1;
352 } else if (status < 400) {
353 ++worker->stats.status[3];
354 stream.status_success = 1;
355 } else if (status < 600) {
356 ++worker->stats.status[status / 100];
357 stream.status_success = 0;
359 stream.status_success = 0;
364 void Client::on_stream_close(int32_t stream_id, bool success,
365 RequestStat *req_stat) {
366 req_stat->stream_close_time = std::chrono::steady_clock::now();
368 req_stat->completed = true;
369 ++worker->stats.req_success;
371 ++worker->stats.req_done;
373 if (success && streams[stream_id].status_success == 1) {
374 ++worker->stats.req_status_success;
376 ++worker->stats.req_failed;
379 streams.erase(stream_id);
380 if (req_done == req_todo) {
385 if (req_started < req_todo) {
391 int Client::noop() { return 0; }
393 int Client::on_connect() {
397 const unsigned char *next_proto = nullptr;
398 unsigned int next_proto_len;
399 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
400 for (int i = 0; i < 2; ++i) {
402 if (util::check_h2_is_selected(next_proto, next_proto_len)) {
403 session = make_unique<Http2Session>(this);
407 spdylay_npn_get_version(next_proto, next_proto_len);
409 session = make_unique<SpdySession>(this, spdy_version);
411 debug_nextproto_error();
415 #else // !HAVE_SPDYLAY
416 debug_nextproto_error();
419 #endif // !HAVE_SPDYLAY
423 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
424 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
425 #else // OPENSSL_VERSION_NUMBER < 0x10002000L
427 #endif // OPENSSL_VERSION_NUMBER < 0x10002000L
431 debug_nextproto_error();
436 switch (config.no_tls_proto) {
437 case Config::PROTO_HTTP2:
438 session = make_unique<Http2Session>(this);
441 case Config::PROTO_SPDY2:
442 session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY2);
444 case Config::PROTO_SPDY3:
445 session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3);
447 case Config::PROTO_SPDY3_1:
448 session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3_1);
450 #endif // HAVE_SPDYLAY
457 state = CLIENT_CONNECTED;
459 session->on_connect();
462 std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
464 for (; nreq > 0; --nreq) {
473 int Client::on_read(const uint8_t *data, size_t len) {
474 auto rv = session->on_read(data, len);
478 worker->stats.bytes_total += len;
483 int Client::on_write() {
484 if (session->on_write() != 0) {
490 int Client::read_clear() {
495 while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
498 if (errno == EAGAIN || errno == EWOULDBLOCK) {
508 if (on_read(buf, nread) != 0) {
516 int Client::write_clear() {
518 if (wb.rleft() > 0) {
520 while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
523 if (errno == EAGAIN || errno == EWOULDBLOCK) {
524 ev_io_start(worker->loop, &wev);
533 if (on_write() != 0) {
536 if (wb.rleft() == 0) {
542 ev_io_stop(worker->loop, &wev);
547 int Client::connected() {
548 if (!util::check_socket_connected(fd)) {
549 return ERR_CONNECT_FAIL;
551 ev_io_start(worker->loop, &rev);
552 ev_io_stop(worker->loop, &wev);
555 readfn = &Client::tls_handshake;
556 writefn = &Client::tls_handshake;
561 readfn = &Client::read_clear;
562 writefn = &Client::write_clear;
564 if (on_connect() != 0) {
571 int Client::tls_handshake() {
574 auto rv = SSL_do_handshake(ssl);
581 auto err = SSL_get_error(ssl, rv);
583 case SSL_ERROR_WANT_READ:
584 ev_io_stop(worker->loop, &wev);
586 case SSL_ERROR_WANT_WRITE:
587 ev_io_start(worker->loop, &wev);
594 ev_io_stop(worker->loop, &wev);
596 readfn = &Client::read_tls;
597 writefn = &Client::write_tls;
599 if (on_connect() != 0) {
606 int Client::read_tls() {
612 auto rv = SSL_read(ssl, buf, sizeof(buf));
619 auto err = SSL_get_error(ssl, rv);
621 case SSL_ERROR_WANT_READ:
623 case SSL_ERROR_WANT_WRITE:
624 // renegotiation started
631 if (on_read(buf, rv) != 0) {
637 int Client::write_tls() {
641 if (wb.rleft() > 0) {
642 auto rv = SSL_write(ssl, wb.pos, wb.rleft());
649 auto err = SSL_get_error(ssl, rv);
651 case SSL_ERROR_WANT_READ:
652 // renegotiation started
654 case SSL_ERROR_WANT_WRITE:
655 ev_io_start(worker->loop, &wev);
666 if (on_write() != 0) {
669 if (wb.rleft() == 0) {
674 ev_io_stop(worker->loop, &wev);
679 void Client::record_request_time(RequestStat *req_stat) {
680 req_stat->request_time = std::chrono::steady_clock::now();
683 void Client::signal_write() { ev_io_start(worker->loop, &wev); }
685 Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
687 : stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
688 id(id), tls_info_report_done(false) {
689 stats.req_todo = req_todo;
690 progress_interval = std::max((size_t)1, req_todo / 10);
692 auto nreqs_per_client = req_todo / nclients;
693 auto nreqs_rem = req_todo % nclients;
695 for (size_t i = 0; i < nclients; ++i) {
696 auto req_todo = nreqs_per_client;
701 clients.push_back(make_unique<Client>(this, req_todo));
706 // first clear clients so that io watchers are stopped before
707 // destructing ev_loop.
709 ev_loop_destroy(loop);
713 for (auto &client : clients) {
714 if (client->connect() != 0) {
715 std::cerr << "client could not connect to host" << std::endl;
723 double within_sd(const std::vector<std::unique_ptr<Worker>> &workers,
724 const std::chrono::microseconds &mean,
725 const std::chrono::microseconds &sd, size_t n) {
726 auto upper = mean.count() + sd.count();
727 auto lower = mean.count() - sd.count();
729 for (const auto &w : workers) {
730 for (const auto &req_stat : w->stats.req_stats) {
731 if (!req_stat.completed) {
734 auto t = std::chrono::duration_cast<std::chrono::microseconds>(
735 req_stat.stream_close_time - req_stat.request_time);
736 if (lower <= t.count() && t.count() <= upper) {
741 return (m / static_cast<double>(n)) * 100;
747 process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
748 auto ts = TimeStats();
752 ts.time_min = std::chrono::microseconds::max();
753 ts.time_max = std::chrono::microseconds::min();
756 // standard deviation calculated using Rapid calculation method:
757 // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
759 for (const auto &w : workers) {
760 for (const auto &req_stat : w->stats.req_stats) {
761 if (!req_stat.completed) {
765 auto t = std::chrono::duration_cast<std::chrono::microseconds>(
766 req_stat.stream_close_time - req_stat.request_time);
767 ts.time_min = std::min(ts.time_min, t);
768 ts.time_max = std::max(ts.time_max, t);
771 auto na = a + (t.count() - a) / n;
772 q = q + (t.count() - a) * (t.count() - na);
777 ts.time_max = ts.time_min = std::chrono::microseconds::zero();
781 ts.time_mean = std::chrono::microseconds(sum / n);
782 ts.time_sd = std::chrono::microseconds(
783 static_cast<std::chrono::microseconds::rep>(sqrt(q / n)));
785 ts.within_sd = within_sd(workers, ts.time_mean, ts.time_sd, n);
791 void resolve_host() {
793 addrinfo hints, *res;
795 memset(&hints, 0, sizeof(hints));
796 hints.ai_family = AF_UNSPEC;
797 hints.ai_socktype = SOCK_STREAM;
798 hints.ai_protocol = 0;
799 hints.ai_flags = AI_ADDRCONFIG;
801 rv = getaddrinfo(config.host.c_str(), util::utos(config.port).c_str(), &hints,
804 std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
807 if (res == nullptr) {
808 std::cerr << "No address returned" << std::endl;
816 std::string get_reqline(const char *uri, const http_parser_url &u) {
819 if (util::has_uri_field(u, UF_PATH)) {
820 reqline = util::get_uri_field(uri, u, UF_PATH);
825 if (util::has_uri_field(u, UF_QUERY)) {
827 reqline += util::get_uri_field(uri, u, UF_QUERY);
835 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
836 unsigned char *outlen, const unsigned char *in,
837 unsigned int inlen, void *arg) {
838 if (util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
840 return SSL_TLSEXT_ERR_OK;
843 if (spdylay_select_next_protocol(out, outlen, in, inlen) > 0) {
844 return SSL_TLSEXT_ERR_OK;
847 return SSL_TLSEXT_ERR_NOACK;
852 template <typename Iterator>
853 std::vector<std::string> parse_uris(Iterator first, Iterator last) {
854 std::vector<std::string> reqlines;
856 // First URI is treated specially. We use scheme, host and port of
857 // this URI and ignore those in the remaining URIs if present.
859 memset(&u, 0, sizeof(u));
862 std::cerr << "no URI available" << std::endl;
866 auto uri = (*first).c_str();
869 if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0 ||
870 !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
871 std::cerr << "invalid URI: " << uri << std::endl;
875 config.scheme = util::get_uri_field(uri, u, UF_SCHEMA);
876 config.host = util::get_uri_field(uri, u, UF_HOST);
877 config.default_port = util::get_default_port(uri, u);
878 if (util::has_uri_field(u, UF_PORT)) {
879 config.port = u.port;
881 config.port = config.default_port;
884 reqlines.push_back(get_reqline(uri, u));
886 for (; first != last; ++first) {
888 memset(&u, 0, sizeof(u));
890 auto uri = (*first).c_str();
892 if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0) {
893 std::cerr << "invalid URI: " << uri << std::endl;
897 reqlines.push_back(get_reqline(uri, u));
905 std::vector<std::string> read_uri_from_file(std::istream &infile) {
906 std::vector<std::string> uris;
907 std::string line_uri;
908 while (std::getline(infile, line_uri)) {
909 uris.push_back(line_uri);
917 void print_version(std::ostream &out) {
918 out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
923 void print_usage(std::ostream &out) {
924 out << R"(Usage: h2load [OPTIONS]... [URI]...
925 benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
930 void print_help(std::ostream &out) {
934 <URI> Specify URI to access. Multiple URIs can be specified.
935 URIs are used in this order for each client. All URIs
936 are used, then first URI is used and then 2nd URI, and
937 so on. The scheme, host and port in the subsequent
938 URIs, if present, are ignored. Those in the first URI
943 Default: )" << config.nreqs << R"(
945 Number of concurrent clients.
946 Default: )" << config.nclients << R"(
948 Number of native threads.
949 Default: )" << config.nthreads << R"(
950 -i, --input-file=<FILE>
951 Path of a file with multiple URIs are seperated by EOLs.
952 This option will disable URIs getting from command-line.
953 If '-' is given as <FILE>, URIs will be read from stdin.
954 URIs are used in this order for each client. All URIs
955 are used, then first URI is used and then 2nd URI, and
956 so on. The scheme, host and port in the subsequent
957 URIs, if present, are ignored. Those in the first URI
959 -m, --max-concurrent-streams=(auto|<N>)
960 Max concurrent streams to issue per session. If "auto"
961 is given, the number of given URIs is used.
963 -w, --window-bits=<N>
964 Sets the stream level initial window size to (2**<N>)-1.
965 For SPDY, 2**<N> is used instead.
966 -W, --connection-window-bits=<N>
967 Sets the connection level initial window size to
968 (2**<N>)-1. For SPDY, if <N> is strictly less than 16,
969 this option is ignored. Otherwise 2**<N> is used for
971 -H, --header=<HEADER>
972 Add/Override a header to the requests.
973 -p, --no-tls-proto=<PROTOID>
974 Specify ALPN identifier of the protocol to be used when
975 accessing http URI without SSL/TLS.)";
978 Available protocols: spdy/2, spdy/3, spdy/3.1 and )";
979 #else // !HAVE_SPDYLAY
981 Available protocol: )";
982 #endif // !HAVE_SPDYLAY
983 out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
984 Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
986 Output debug information.
987 --version Display version information and exit.
988 -h, --help Display this help and exit.)" << std::endl;
992 int main(int argc, char **argv) {
995 static option long_options[] = {
996 {"requests", required_argument, nullptr, 'n'},
997 {"clients", required_argument, nullptr, 'c'},
998 {"threads", required_argument, nullptr, 't'},
999 {"max-concurrent-streams", required_argument, nullptr, 'm'},
1000 {"window-bits", required_argument, nullptr, 'w'},
1001 {"connection-window-bits", required_argument, nullptr, 'W'},
1002 {"input-file", required_argument, nullptr, 'i'},
1003 {"header", required_argument, nullptr, 'H'},
1004 {"no-tls-proto", required_argument, nullptr, 'p'},
1005 {"verbose", no_argument, nullptr, 'v'},
1006 {"help", no_argument, nullptr, 'h'},
1007 {"version", no_argument, &flag, 1},
1008 {nullptr, 0, nullptr, 0}};
1009 int option_index = 0;
1010 auto c = getopt_long(argc, argv, "hvW:c:m:n:p:t:w:H:i:", long_options,
1017 config.nreqs = strtoul(optarg, nullptr, 10);
1020 config.nclients = strtoul(optarg, nullptr, 10);
1024 std::cerr << "-t: WARNING: Threading disabled at build time, "
1025 << "no threads created." << std::endl;
1027 config.nthreads = strtoul(optarg, nullptr, 10);
1031 if (util::strieq("auto", optarg)) {
1032 config.max_concurrent_streams = -1;
1034 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
1040 char *endptr = nullptr;
1041 auto n = strtoul(optarg, &endptr, 10);
1042 if (errno == 0 && *endptr == '\0' && n < 31) {
1044 config.window_bits = n;
1046 config.connection_window_bits = n;
1049 std::cerr << "-" << static_cast<char>(c)
1050 << ": specify the integer in the range [0, 30], inclusive"
1057 char *header = optarg;
1058 // Skip first possible ':' in the header name
1059 char *value = strchr(optarg + 1, ':');
1060 if (!value || (header[0] == ':' && header + 1 == value)) {
1061 std::cerr << "-H: invalid header: " << optarg << std::endl;
1066 while (isspace(*value)) {
1070 // This could also be a valid case for suppressing a header
1072 std::cerr << "-H: invalid header - value missing: " << optarg
1076 // Note that there is no processing currently to handle multiple
1077 // message-header fields with the same field name
1078 config.custom_headers.emplace_back(header, value);
1079 util::inp_strlower(config.custom_headers.back().name);
1083 config.ifile = std::string(optarg);
1087 if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
1088 config.no_tls_proto = Config::PROTO_HTTP2;
1090 } else if (util::strieq("spdy/2", optarg)) {
1091 config.no_tls_proto = Config::PROTO_SPDY2;
1092 } else if (util::strieq("spdy/3", optarg)) {
1093 config.no_tls_proto = Config::PROTO_SPDY3;
1094 } else if (util::strieq("spdy/3.1", optarg)) {
1095 config.no_tls_proto = Config::PROTO_SPDY3_1;
1096 #endif // HAVE_SPDYLAY
1098 std::cerr << "-p: unsupported protocol " << optarg << std::endl;
1103 config.verbose = true;
1106 print_help(std::cout);
1109 util::show_candidates(argv[optind - 1], long_options);
1115 print_version(std::cout);
1124 if (argc == optind) {
1125 if (config.ifile.empty()) {
1126 std::cerr << "no URI or input file given" << std::endl;
1131 if (config.nreqs == 0) {
1132 std::cerr << "-n: the number of requests must be strictly greater than 0."
1137 if (config.max_concurrent_streams == 0) {
1138 std::cerr << "-m: the max concurrent streams must be strictly greater "
1139 << "than 0." << std::endl;
1143 if (config.nthreads == 0) {
1144 std::cerr << "-t: the number of threads must be strictly greater than 0."
1149 if (config.nreqs < config.nclients) {
1150 std::cerr << "-n, -c: the number of requests must be greater than or "
1151 << "equal to the concurrent clients." << std::endl;
1155 if (config.nthreads > std::thread::hardware_concurrency()) {
1156 std::cerr << "-t: warning: the number of threads is greater than hardware "
1157 << "cores." << std::endl;
1160 struct sigaction act;
1161 memset(&act, 0, sizeof(struct sigaction));
1162 act.sa_handler = SIG_IGN;
1163 sigaction(SIGPIPE, &act, nullptr);
1164 OPENSSL_config(nullptr);
1165 OpenSSL_add_all_algorithms();
1166 SSL_load_error_strings();
1170 ssl::LibsslGlobalLock lock;
1173 auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
1175 std::cerr << "Failed to create SSL_CTX: "
1176 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1180 SSL_CTX_set_options(ssl_ctx,
1181 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
1182 SSL_OP_NO_COMPRESSION |
1183 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
1184 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
1185 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
1187 if (SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST) == 0) {
1188 std::cerr << "SSL_CTX_set_cipher_list failed: "
1189 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1193 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
1196 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
1197 auto proto_list = util::get_default_alpn();
1199 static const char spdy_proto_list[] = "\x8spdy/3.1\x6spdy/3\x6spdy/2";
1200 std::copy_n(spdy_proto_list, sizeof(spdy_proto_list) - 1,
1201 std::back_inserter(proto_list));
1202 #endif // HAVE_SPDYLAY
1203 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
1204 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
1206 std::vector<std::string> reqlines;
1208 if (config.ifile.empty()) {
1209 std::vector<std::string> uris;
1210 std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
1211 reqlines = parse_uris(std::begin(uris), std::end(uris));
1213 std::vector<std::string> uris;
1214 if (config.ifile == "-") {
1215 uris = read_uri_from_file(std::cin);
1217 std::ifstream infile(config.ifile);
1219 std::cerr << "cannot read input file: " << config.ifile << std::endl;
1223 uris = read_uri_from_file(infile);
1226 reqlines = parse_uris(std::begin(uris), std::end(uris));
1229 if (config.max_concurrent_streams == -1) {
1230 config.max_concurrent_streams = reqlines.size();
1234 shared_nva.emplace_back(":scheme", config.scheme);
1235 if (config.port != config.default_port) {
1236 shared_nva.emplace_back(":authority",
1237 config.host + ":" + util::utos(config.port));
1239 shared_nva.emplace_back(":authority", config.host);
1241 shared_nva.emplace_back(":method", "GET");
1243 // list overridalbe headers
1244 auto override_hdrs =
1245 make_array<std::string>(":authority", ":host", ":method", ":scheme");
1247 for (auto &kv : config.custom_headers) {
1248 if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
1249 kv.name) != std::end(override_hdrs)) {
1251 for (auto &nv : shared_nva) {
1252 if ((nv.name == ":authority" && kv.name == ":host") ||
1253 (nv.name == kv.name)) {
1254 nv.value = kv.value;
1258 // add additional headers
1259 shared_nva.push_back(kv);
1263 for (auto &req : reqlines) {
1265 std::vector<nghttp2_nv> nva;
1267 nva.push_back(http2::make_nv_ls(":path", req));
1269 for (auto &nv : shared_nva) {
1270 nva.push_back(http2::make_nv(nv.name, nv.value, false));
1273 config.nva.push_back(std::move(nva));
1276 std::vector<const char *> cva;
1278 cva.push_back(":path");
1279 cva.push_back(req.c_str());
1281 for (auto &nv : shared_nva) {
1282 if (nv.name == ":authority") {
1283 cva.push_back(":host");
1285 cva.push_back(nv.name.c_str());
1287 cva.push_back(nv.value.c_str());
1289 cva.push_back(":version");
1290 cva.push_back("HTTP/1.1");
1291 cva.push_back(nullptr);
1293 config.nv.push_back(std::move(cva));
1298 if (config.nclients == 1) {
1299 config.nthreads = 1;
1302 size_t nreqs_per_thread = config.nreqs / config.nthreads;
1303 ssize_t nreqs_rem = config.nreqs % config.nthreads;
1305 size_t nclients_per_thread = config.nclients / config.nthreads;
1306 ssize_t nclients_rem = config.nclients % config.nthreads;
1308 std::cout << "starting benchmark..." << std::endl;
1310 auto start = std::chrono::steady_clock::now();
1312 std::vector<std::unique_ptr<Worker>> workers;
1313 workers.reserve(config.nthreads - 1);
1316 std::vector<std::future<void>> futures;
1317 for (size_t i = 0; i < config.nthreads - 1; ++i) {
1318 auto nreqs = nreqs_per_thread + (nreqs_rem-- > 0);
1319 auto nclients = nclients_per_thread + (nclients_rem-- > 0);
1320 std::cout << "spawning thread #" << i << ": " << nclients
1321 << " concurrent clients, " << nreqs << " total requests"
1324 make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config));
1325 auto &worker = workers.back();
1327 std::async(std::launch::async, [&worker]() { worker->run(); }));
1331 auto nreqs_last = nreqs_per_thread + (nreqs_rem-- > 0);
1332 auto nclients_last = nclients_per_thread + (nclients_rem-- > 0);
1333 std::cout << "spawning thread #" << (config.nthreads - 1) << ": "
1334 << nclients_last << " concurrent clients, " << nreqs_last
1335 << " total requests" << std::endl;
1336 workers.push_back(make_unique<Worker>(config.nthreads - 1, ssl_ctx,
1337 nreqs_last, nclients_last, &config));
1338 workers.back()->run();
1341 for (auto &fut : futures) {
1346 auto end = std::chrono::steady_clock::now();
1348 std::chrono::duration_cast<std::chrono::microseconds>(end - start);
1351 for (const auto &w : workers) {
1352 const auto &s = w->stats;
1354 stats.req_todo += s.req_todo;
1355 stats.req_started += s.req_started;
1356 stats.req_done += s.req_done;
1357 stats.req_success += s.req_success;
1358 stats.req_status_success += s.req_status_success;
1359 stats.req_failed += s.req_failed;
1360 stats.req_error += s.req_error;
1361 stats.bytes_total += s.bytes_total;
1362 stats.bytes_head += s.bytes_head;
1363 stats.bytes_body += s.bytes_body;
1365 for (size_t i = 0; i < stats.status.size(); ++i) {
1366 stats.status[i] += s.status[i];
1370 auto time_stats = process_time_stats(workers);
1372 // Requests which have not been issued due to connection errors, are
1373 // counted towards req_failed and req_error.
1374 auto req_not_issued =
1375 stats.req_todo - stats.req_status_success - stats.req_failed;
1376 stats.req_failed += req_not_issued;
1377 stats.req_error += req_not_issued;
1379 // UI is heavily inspired by weighttp[1] and wrk[2]
1381 // [1] https://github.com/lighttpd/weighttp
1382 // [2] https://github.com/wg/wrk
1385 if (duration.count() > 0) {
1386 auto secd = static_cast<double>(duration.count()) / (1000 * 1000);
1387 rps = stats.req_success / secd;
1388 bps = stats.bytes_total / secd;
1392 finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
1393 << util::utos_with_funit(bps) << R"(B/s
1394 requests: )" << stats.req_todo << " total, " << stats.req_started
1395 << " started, " << stats.req_done << " done, "
1396 << stats.req_status_success << " succeeded, " << stats.req_failed
1397 << " failed, " << stats.req_error << R"( errored
1398 status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
1399 << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
1400 traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
1401 << " bytes headers, " << stats.bytes_body << R"( bytes data
1402 min max mean sd +/- sd
1403 time for request: )" << std::setw(10)
1404 << util::format_duration(time_stats.time_min) << " "
1405 << std::setw(10) << util::format_duration(time_stats.time_max)
1406 << " " << std::setw(10)
1407 << util::format_duration(time_stats.time_mean) << " "
1408 << std::setw(10) << util::format_duration(time_stats.time_sd)
1409 << std::setw(9) << util::dtos(time_stats.within_sd) << "%"
1412 SSL_CTX_free(ssl_ctx);
1417 } // namespace h2load
1419 int main(int argc, char **argv) { return h2load::main(argc, argv); }