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 #ifdef HAVE_NETINET_IN_H
30 #include <netinet/in.h>
31 #endif // HAVE_NETINET_IN_H
32 #include <netinet/tcp.h>
36 #endif // HAVE_FCNTL_H
49 #include <spdylay/spdylay.h>
50 #endif // HAVE_SPDYLAY
52 #include <openssl/err.h>
53 #include <openssl/conf.h>
55 #include "http-parser/http_parser.h"
57 #include "h2load_http2_session.h"
59 #include "h2load_spdy_session.h"
60 #endif // HAVE_SPDYLAY
70 using namespace nghttp2;
75 : data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
76 max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
77 no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0), default_port(0),
91 void debug(const char *format, ...) {
93 fprintf(stderr, "[DEBUG] ");
96 vfprintf(stderr, format, ap);
103 void debug_nextproto_error() {
105 debug("no supported protocol was negotiated, expected: %s, "
106 "spdy/2, spdy/3, spdy/3.1\n",
107 NGHTTP2_PROTO_VERSION_ID);
108 #else // !HAVE_SPDYLAY
109 debug("no supported protocol was negotiated, expected: %s\n",
110 NGHTTP2_PROTO_VERSION_ID);
111 #endif // !HAVE_SPDYLAY
115 RequestStat::RequestStat() : data_offset(0), completed(false) {}
117 Stats::Stats(size_t req_todo)
118 : req_todo(0), req_started(0), req_done(0), req_success(0),
119 req_status_success(0), req_failed(0), req_error(0), bytes_total(0),
120 bytes_head(0), bytes_body(0), status(), req_stats(req_todo) {}
122 Stream::Stream() : status_success(-1) {}
125 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
126 auto client = static_cast<Client *>(w->data);
127 auto rv = client->do_write();
128 if (rv == Client::ERR_CONNECT_FAIL) {
129 client->disconnect();
130 rv = client->connect();
144 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
145 auto client = static_cast<Client *>(w->data);
146 if (client->do_read() != 0) {
150 writecb(loop, &client->wev, revents);
151 // client->disconnect() and client->fail() may be called
155 Client::Client(Worker *worker, size_t req_todo)
156 : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
157 state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo),
158 req_started(0), req_done(0), fd(-1) {
159 ev_io_init(&wev, writecb, 0, EV_WRITE);
160 ev_io_init(&rev, readcb, 0, EV_READ);
166 Client::~Client() { disconnect(); }
168 int Client::do_read() { return readfn(*this); }
169 int Client::do_write() { return writefn(*this); }
171 int Client::connect() {
172 record_start_time(&worker->stats);
175 auto addr = next_addr;
176 next_addr = next_addr->ai_next;
177 fd = util::create_nonblock_socket(addr->ai_family);
181 if (config.scheme == "https") {
182 ssl = SSL_new(worker->ssl_ctx);
184 auto config = worker->config;
186 if (!util::numeric_host(config->host.c_str())) {
187 SSL_set_tlsext_host_name(ssl, config->host.c_str());
191 SSL_set_connect_state(ssl);
194 auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
195 if (rv != 0 && errno != EINPROGRESS) {
211 writefn = &Client::connected;
213 ev_io_set(&rev, fd, EV_READ);
214 ev_io_set(&wev, fd, EV_WRITE);
216 ev_io_start(worker->loop, &wev);
221 void Client::fail() {
222 process_abandoned_streams();
227 void Client::disconnect() {
231 ev_io_stop(worker->loop, &wev);
232 ev_io_stop(worker->loop, &rev);
234 SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
241 shutdown(fd, SHUT_WR);
247 void Client::submit_request() {
248 auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
249 session->submit_request(req_stat);
253 void Client::process_abandoned_streams() {
254 auto req_abandoned = req_todo - req_done;
256 worker->stats.req_failed += req_abandoned;
257 worker->stats.req_error += req_abandoned;
258 worker->stats.req_done += req_abandoned;
263 void Client::report_progress() {
264 if (worker->id == 0 &&
265 worker->stats.req_done % worker->progress_interval == 0) {
266 std::cout << "progress: "
267 << worker->stats.req_done * 100 / worker->stats.req_todo
268 << "% done" << std::endl;
273 const char *get_tls_protocol(SSL *ssl) {
274 auto session = SSL_get_session(ssl);
276 switch (session->ssl_version) {
294 void print_server_tmp_key(SSL *ssl) {
295 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
298 if (!SSL_get_server_tmp_key(ssl, &key)) {
302 auto key_del = defer(EVP_PKEY_free, key);
304 std::cout << "Server Temp Key: ";
306 switch (EVP_PKEY_id(key)) {
308 std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
311 std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
314 auto ec = EVP_PKEY_get1_EC_KEY(key);
315 auto ec_del = defer(EC_KEY_free, ec);
316 auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
317 auto cname = EC_curve_nid2nist(nid);
319 cname = OBJ_nid2sn(nid);
322 std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
327 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
331 void Client::report_tls_info() {
332 if (worker->id == 0 && !worker->tls_info_report_done) {
333 worker->tls_info_report_done = true;
334 auto cipher = SSL_get_current_cipher(ssl);
335 std::cout << "Protocol: " << get_tls_protocol(ssl) << "\n"
336 << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
337 print_server_tmp_key(ssl);
341 void Client::terminate_session() { session->terminate(); }
343 void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
345 void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
346 const uint8_t *value, size_t valuelen) {
347 auto itr = streams.find(stream_id);
348 if (itr == std::end(streams)) {
351 auto &stream = (*itr).second;
352 if (stream.status_success == -1 && namelen == 7 &&
353 util::streq_l(":status", name, namelen)) {
355 for (size_t i = 0; i < valuelen; ++i) {
356 if ('0' <= value[i] && value[i] <= '9') {
358 status += value[i] - '0';
360 stream.status_success = 0;
368 if (status >= 200 && status < 300) {
369 ++worker->stats.status[2];
370 stream.status_success = 1;
371 } else if (status < 400) {
372 ++worker->stats.status[3];
373 stream.status_success = 1;
374 } else if (status < 600) {
375 ++worker->stats.status[status / 100];
376 stream.status_success = 0;
378 stream.status_success = 0;
383 void Client::on_stream_close(int32_t stream_id, bool success,
384 RequestStat *req_stat) {
385 req_stat->stream_close_time = std::chrono::steady_clock::now();
387 req_stat->completed = true;
388 ++worker->stats.req_success;
390 ++worker->stats.req_done;
392 if (success && streams[stream_id].status_success == 1) {
393 ++worker->stats.req_status_success;
395 ++worker->stats.req_failed;
398 streams.erase(stream_id);
399 if (req_done == req_todo) {
404 if (req_started < req_todo) {
410 int Client::connection_made() {
414 const unsigned char *next_proto = nullptr;
415 unsigned int next_proto_len;
416 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
417 for (int i = 0; i < 2; ++i) {
419 if (util::check_h2_is_selected(next_proto, next_proto_len)) {
420 session = make_unique<Http2Session>(this);
424 spdylay_npn_get_version(next_proto, next_proto_len);
426 session = make_unique<SpdySession>(this, spdy_version);
428 debug_nextproto_error();
432 #else // !HAVE_SPDYLAY
433 debug_nextproto_error();
436 #endif // !HAVE_SPDYLAY
440 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
441 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
442 #else // OPENSSL_VERSION_NUMBER < 0x10002000L
444 #endif // OPENSSL_VERSION_NUMBER < 0x10002000L
448 debug_nextproto_error();
453 switch (config.no_tls_proto) {
454 case Config::PROTO_HTTP2:
455 session = make_unique<Http2Session>(this);
458 case Config::PROTO_SPDY2:
459 session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY2);
461 case Config::PROTO_SPDY3:
462 session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3);
464 case Config::PROTO_SPDY3_1:
465 session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3_1);
467 #endif // HAVE_SPDYLAY
474 state = CLIENT_CONNECTED;
476 session->on_connect();
478 record_connect_time(&worker->stats);
481 std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
483 for (; nreq > 0; --nreq) {
492 int Client::on_read(const uint8_t *data, size_t len) {
493 auto rv = session->on_read(data, len);
497 worker->stats.bytes_total += len;
502 int Client::on_write() {
503 if (session->on_write() != 0) {
509 int Client::read_clear() {
514 while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
517 if (errno == EAGAIN || errno == EWOULDBLOCK) {
527 if (on_read(buf, nread) != 0) {
531 if (!first_byte_received) {
532 first_byte_received = true;
533 record_ttfb(&worker->stats);
540 int Client::write_clear() {
542 if (wb.rleft() > 0) {
544 while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
547 if (errno == EAGAIN || errno == EWOULDBLOCK) {
548 ev_io_start(worker->loop, &wev);
557 if (on_write() != 0) {
560 if (wb.rleft() == 0) {
565 ev_io_stop(worker->loop, &wev);
570 int Client::connected() {
571 if (!util::check_socket_connected(fd)) {
572 return ERR_CONNECT_FAIL;
574 ev_io_start(worker->loop, &rev);
575 ev_io_stop(worker->loop, &wev);
578 readfn = &Client::tls_handshake;
579 writefn = &Client::tls_handshake;
584 readfn = &Client::read_clear;
585 writefn = &Client::write_clear;
587 if (connection_made() != 0) {
594 int Client::tls_handshake() {
597 auto rv = SSL_do_handshake(ssl);
604 auto err = SSL_get_error(ssl, rv);
606 case SSL_ERROR_WANT_READ:
607 ev_io_stop(worker->loop, &wev);
609 case SSL_ERROR_WANT_WRITE:
610 ev_io_start(worker->loop, &wev);
617 ev_io_stop(worker->loop, &wev);
619 readfn = &Client::read_tls;
620 writefn = &Client::write_tls;
622 if (connection_made() != 0) {
629 int Client::read_tls() {
635 auto rv = SSL_read(ssl, buf, sizeof(buf));
642 auto err = SSL_get_error(ssl, rv);
644 case SSL_ERROR_WANT_READ:
646 case SSL_ERROR_WANT_WRITE:
647 // renegotiation started
654 if (on_read(buf, rv) != 0) {
658 if (!first_byte_received) {
659 first_byte_received = true;
660 record_ttfb(&worker->stats);
665 int Client::write_tls() {
669 if (wb.rleft() > 0) {
670 auto rv = SSL_write(ssl, wb.pos, wb.rleft());
677 auto err = SSL_get_error(ssl, rv);
679 case SSL_ERROR_WANT_READ:
680 // renegotiation started
682 case SSL_ERROR_WANT_WRITE:
683 ev_io_start(worker->loop, &wev);
695 if (on_write() != 0) {
698 if (wb.rleft() == 0) {
703 ev_io_stop(worker->loop, &wev);
708 void Client::record_request_time(RequestStat *req_stat) {
709 req_stat->request_time = std::chrono::steady_clock::now();
712 void Client::record_start_time(Stats *stat) {
713 stat->start_times.push_back(std::chrono::steady_clock::now());
716 void Client::record_connect_time(Stats *stat) {
717 stat->connect_times.push_back(std::chrono::steady_clock::now());
720 void Client::record_ttfb(Stats *stat) {
721 stat->ttfbs.push_back(std::chrono::steady_clock::now());
724 void Client::signal_write() { ev_io_start(worker->loop, &wev); }
726 Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
728 : stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
729 id(id), tls_info_report_done(false) {
730 stats.req_todo = req_todo;
731 progress_interval = std::max((size_t)1, req_todo / 10);
733 auto nreqs_per_client = req_todo / nclients;
734 auto nreqs_rem = req_todo % nclients;
736 for (size_t i = 0; i < nclients; ++i) {
737 auto req_todo = nreqs_per_client;
742 clients.push_back(make_unique<Client>(this, req_todo));
747 // first clear clients so that io watchers are stopped before
748 // destructing ev_loop.
750 ev_loop_destroy(loop);
754 for (auto &client : clients) {
755 if (client->connect() != 0) {
756 std::cerr << "client could not connect to host" << std::endl;
764 // Returns percentage of number of samples within mean +/- sd.
765 template <typename Duration>
766 double within_sd(const std::vector<Duration> &samples, const Duration &mean,
767 const Duration &sd) {
768 if (samples.size() == 0) {
771 auto lower = mean - sd;
772 auto upper = mean + sd;
773 auto m = std::count_if(
774 std::begin(samples), std::end(samples),
775 [&lower, &upper](const Duration &t) { return lower <= t && t <= upper; });
776 return (m / static_cast<double>(samples.size())) * 100;
781 // Computes statistics using |samples|. The min, max, mean, sd, and
782 // percentage of number of samples within mean +/- sd are computed.
783 template <typename Duration>
784 TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
785 if (samples.size() == 0) {
786 return {Duration::zero(), Duration::zero(), Duration::zero(),
787 Duration::zero(), 0.0};
789 // standard deviation calculated using Rapid calculation method:
790 // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
794 auto res = TimeStat<Duration>{Duration::max(), Duration::min()};
795 for (const auto &t : samples) {
797 res.min = std::min(res.min, t);
798 res.max = std::max(res.max, t);
801 auto na = a + (t.count() - a) / n;
802 q += (t.count() - a) * (t.count() - na);
806 res.mean = Duration(sum / n);
807 res.sd = Duration(static_cast<typename Duration::rep>(sqrt(q / n)));
808 res.within_sd = within_sd(samples, res.mean, res.sd);
816 process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
817 size_t nrequest_times = 0, nttfb_times = 0;
818 for (const auto &w : workers) {
819 nrequest_times += w->stats.req_stats.size();
820 nttfb_times += w->stats.ttfbs.size();
823 std::vector<std::chrono::microseconds> request_times;
824 request_times.reserve(nrequest_times);
825 std::vector<std::chrono::microseconds> connect_times, ttfb_times;
826 connect_times.reserve(nttfb_times);
827 ttfb_times.reserve(nttfb_times);
829 for (const auto &w : workers) {
830 for (const auto &req_stat : w->stats.req_stats) {
831 if (!req_stat.completed) {
834 request_times.push_back(
835 std::chrono::duration_cast<std::chrono::microseconds>(
836 req_stat.stream_close_time - req_stat.request_time));
839 const auto &stat = w->stats;
840 // rule out cases where we started but didn't connect or get the
841 // first byte (errors). We will get connect event before FFTB.
842 assert(stat.start_times.size() >= stat.ttfbs.size());
843 assert(stat.connect_times.size() >= stat.ttfbs.size());
844 for (size_t i = 0; i < stat.ttfbs.size(); ++i) {
845 connect_times.push_back(
846 std::chrono::duration_cast<std::chrono::microseconds>(
847 stat.connect_times[i] - stat.start_times[i]));
849 ttfb_times.push_back(
850 std::chrono::duration_cast<std::chrono::microseconds>(
851 stat.ttfbs[i] - stat.start_times[i]));
855 return {compute_time_stat(request_times), compute_time_stat(connect_times),
856 compute_time_stat(ttfb_times)};
861 void resolve_host() {
863 addrinfo hints, *res;
865 memset(&hints, 0, sizeof(hints));
866 hints.ai_family = AF_UNSPEC;
867 hints.ai_socktype = SOCK_STREAM;
868 hints.ai_protocol = 0;
869 hints.ai_flags = AI_ADDRCONFIG;
871 rv = getaddrinfo(config.host.c_str(), util::utos(config.port).c_str(), &hints,
874 std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
877 if (res == nullptr) {
878 std::cerr << "No address returned" << std::endl;
886 std::string get_reqline(const char *uri, const http_parser_url &u) {
889 if (util::has_uri_field(u, UF_PATH)) {
890 reqline = util::get_uri_field(uri, u, UF_PATH);
895 if (util::has_uri_field(u, UF_QUERY)) {
897 reqline += util::get_uri_field(uri, u, UF_QUERY);
905 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
906 unsigned char *outlen, const unsigned char *in,
907 unsigned int inlen, void *arg) {
908 if (util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
910 return SSL_TLSEXT_ERR_OK;
913 if (spdylay_select_next_protocol(out, outlen, in, inlen) > 0) {
914 return SSL_TLSEXT_ERR_OK;
917 return SSL_TLSEXT_ERR_NOACK;
922 template <typename Iterator>
923 std::vector<std::string> parse_uris(Iterator first, Iterator last) {
924 std::vector<std::string> reqlines;
926 // First URI is treated specially. We use scheme, host and port of
927 // this URI and ignore those in the remaining URIs if present.
929 memset(&u, 0, sizeof(u));
932 std::cerr << "no URI available" << std::endl;
936 auto uri = (*first).c_str();
939 if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0 ||
940 !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
941 std::cerr << "invalid URI: " << uri << std::endl;
945 config.scheme = util::get_uri_field(uri, u, UF_SCHEMA);
946 config.host = util::get_uri_field(uri, u, UF_HOST);
947 config.default_port = util::get_default_port(uri, u);
948 if (util::has_uri_field(u, UF_PORT)) {
949 config.port = u.port;
951 config.port = config.default_port;
954 reqlines.push_back(get_reqline(uri, u));
956 for (; first != last; ++first) {
958 memset(&u, 0, sizeof(u));
960 auto uri = (*first).c_str();
962 if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0) {
963 std::cerr << "invalid URI: " << uri << std::endl;
967 reqlines.push_back(get_reqline(uri, u));
975 std::vector<std::string> read_uri_from_file(std::istream &infile) {
976 std::vector<std::string> uris;
977 std::string line_uri;
978 while (std::getline(infile, line_uri)) {
979 uris.push_back(line_uri);
987 void print_version(std::ostream &out) {
988 out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
993 void print_usage(std::ostream &out) {
994 out << R"(Usage: h2load [OPTIONS]... [URI]...
995 benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
1000 void print_help(std::ostream &out) {
1004 <URI> Specify URI to access. Multiple URIs can be specified.
1005 URIs are used in this order for each client. All URIs
1006 are used, then first URI is used and then 2nd URI, and
1007 so on. The scheme, host and port in the subsequent
1008 URIs, if present, are ignored. Those in the first URI
1013 Default: )" << config.nreqs << R"(
1015 Number of concurrent clients.
1016 Default: )" << config.nclients << R"(
1018 Number of native threads.
1019 Default: )" << config.nthreads << R"(
1020 -i, --input-file=<FILE>
1021 Path of a file with multiple URIs are separated by EOLs.
1022 This option will disable URIs getting from command-line.
1023 If '-' is given as <FILE>, URIs will be read from stdin.
1024 URIs are used in this order for each client. All URIs
1025 are used, then first URI is used and then 2nd URI, and
1026 so on. The scheme, host and port in the subsequent
1027 URIs, if present, are ignored. Those in the first URI
1029 -m, --max-concurrent-streams=(auto|<N>)
1030 Max concurrent streams to issue per session. If "auto"
1031 is given, the number of given URIs is used.
1033 -w, --window-bits=<N>
1034 Sets the stream level initial window size to (2**<N>)-1.
1035 For SPDY, 2**<N> is used instead.
1036 Default: )" << config.window_bits << R"(
1037 -W, --connection-window-bits=<N>
1038 Sets the connection level initial window size to
1039 (2**<N>)-1. For SPDY, if <N> is strictly less than 16,
1040 this option is ignored. Otherwise 2**<N> is used for
1042 Default: )" << config.connection_window_bits << R"(
1043 -H, --header=<HEADER>
1044 Add/Override a header to the requests.
1045 -p, --no-tls-proto=<PROTOID>
1046 Specify ALPN identifier of the protocol to be used when
1047 accessing http URI without SSL/TLS.)";
1050 Available protocols: spdy/2, spdy/3, spdy/3.1 and )";
1051 #else // !HAVE_SPDYLAY
1053 Available protocol: )";
1054 #endif // !HAVE_SPDYLAY
1055 out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
1056 Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
1058 Post FILE to server. The request method is changed to
1061 Output debug information.
1062 --version Display version information and exit.
1063 -h, --help Display this help and exit.)" << std::endl;
1067 int main(int argc, char **argv) {
1068 std::string datafile;
1070 static int flag = 0;
1071 static option long_options[] = {
1072 {"requests", required_argument, nullptr, 'n'},
1073 {"clients", required_argument, nullptr, 'c'},
1074 {"data", required_argument, nullptr, 'd'},
1075 {"threads", required_argument, nullptr, 't'},
1076 {"max-concurrent-streams", required_argument, nullptr, 'm'},
1077 {"window-bits", required_argument, nullptr, 'w'},
1078 {"connection-window-bits", required_argument, nullptr, 'W'},
1079 {"input-file", required_argument, nullptr, 'i'},
1080 {"header", required_argument, nullptr, 'H'},
1081 {"no-tls-proto", required_argument, nullptr, 'p'},
1082 {"verbose", no_argument, nullptr, 'v'},
1083 {"help", no_argument, nullptr, 'h'},
1084 {"version", no_argument, &flag, 1},
1085 {nullptr, 0, nullptr, 0}};
1086 int option_index = 0;
1087 auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:", long_options,
1094 config.nreqs = strtoul(optarg, nullptr, 10);
1097 config.nclients = strtoul(optarg, nullptr, 10);
1104 std::cerr << "-t: WARNING: Threading disabled at build time, "
1105 << "no threads created." << std::endl;
1107 config.nthreads = strtoul(optarg, nullptr, 10);
1111 if (util::strieq("auto", optarg)) {
1112 config.max_concurrent_streams = -1;
1114 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
1120 char *endptr = nullptr;
1121 auto n = strtoul(optarg, &endptr, 10);
1122 if (errno == 0 && *endptr == '\0' && n < 31) {
1124 config.window_bits = n;
1126 config.connection_window_bits = n;
1129 std::cerr << "-" << static_cast<char>(c)
1130 << ": specify the integer in the range [0, 30], inclusive"
1137 char *header = optarg;
1138 // Skip first possible ':' in the header name
1139 char *value = strchr(optarg + 1, ':');
1140 if (!value || (header[0] == ':' && header + 1 == value)) {
1141 std::cerr << "-H: invalid header: " << optarg << std::endl;
1146 while (isspace(*value)) {
1150 // This could also be a valid case for suppressing a header
1152 std::cerr << "-H: invalid header - value missing: " << optarg
1156 // Note that there is no processing currently to handle multiple
1157 // message-header fields with the same field name
1158 config.custom_headers.emplace_back(header, value);
1159 util::inp_strlower(config.custom_headers.back().name);
1163 config.ifile = std::string(optarg);
1167 if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
1168 config.no_tls_proto = Config::PROTO_HTTP2;
1170 } else if (util::strieq("spdy/2", optarg)) {
1171 config.no_tls_proto = Config::PROTO_SPDY2;
1172 } else if (util::strieq("spdy/3", optarg)) {
1173 config.no_tls_proto = Config::PROTO_SPDY3;
1174 } else if (util::strieq("spdy/3.1", optarg)) {
1175 config.no_tls_proto = Config::PROTO_SPDY3_1;
1176 #endif // HAVE_SPDYLAY
1178 std::cerr << "-p: unsupported protocol " << optarg << std::endl;
1183 config.verbose = true;
1186 print_help(std::cout);
1189 util::show_candidates(argv[optind - 1], long_options);
1195 print_version(std::cout);
1204 if (argc == optind) {
1205 if (config.ifile.empty()) {
1206 std::cerr << "no URI or input file given" << std::endl;
1211 if (config.nreqs == 0) {
1212 std::cerr << "-n: the number of requests must be strictly greater than 0."
1217 if (config.max_concurrent_streams == 0) {
1218 std::cerr << "-m: the max concurrent streams must be strictly greater "
1219 << "than 0." << std::endl;
1223 if (config.nthreads == 0) {
1224 std::cerr << "-t: the number of threads must be strictly greater than 0."
1229 if (config.nreqs < config.nclients) {
1230 std::cerr << "-n, -c: the number of requests must be greater than or "
1231 << "equal to the concurrent clients." << std::endl;
1235 if (config.nclients < config.nthreads) {
1236 std::cerr << "-c, -t: the number of client must be greater than or equal "
1237 "to the number of threads." << std::endl;
1241 if (config.nthreads > std::thread::hardware_concurrency()) {
1242 std::cerr << "-t: warning: the number of threads is greater than hardware "
1243 << "cores." << std::endl;
1246 if (!datafile.empty()) {
1247 config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
1248 if (config.data_fd == -1) {
1249 std::cerr << "-d: Could not open file " << datafile << std::endl;
1252 struct stat data_stat;
1253 if (fstat(config.data_fd, &data_stat) == -1) {
1254 std::cerr << "-d: Could not stat file " << datafile << std::endl;
1257 config.data_length = data_stat.st_size;
1260 struct sigaction act;
1261 memset(&act, 0, sizeof(struct sigaction));
1262 act.sa_handler = SIG_IGN;
1263 sigaction(SIGPIPE, &act, nullptr);
1264 OPENSSL_config(nullptr);
1265 OpenSSL_add_all_algorithms();
1266 SSL_load_error_strings();
1270 ssl::LibsslGlobalLock lock;
1273 auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
1275 std::cerr << "Failed to create SSL_CTX: "
1276 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1280 SSL_CTX_set_options(ssl_ctx,
1281 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
1282 SSL_OP_NO_COMPRESSION |
1283 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
1284 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
1285 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
1287 if (SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST) == 0) {
1288 std::cerr << "SSL_CTX_set_cipher_list failed: "
1289 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1293 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
1296 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
1297 auto proto_list = util::get_default_alpn();
1299 static const char spdy_proto_list[] = "\x8spdy/3.1\x6spdy/3\x6spdy/2";
1300 std::copy_n(spdy_proto_list, sizeof(spdy_proto_list) - 1,
1301 std::back_inserter(proto_list));
1302 #endif // HAVE_SPDYLAY
1303 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
1304 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
1306 std::vector<std::string> reqlines;
1308 if (config.ifile.empty()) {
1309 std::vector<std::string> uris;
1310 std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
1311 reqlines = parse_uris(std::begin(uris), std::end(uris));
1313 std::vector<std::string> uris;
1314 if (config.ifile == "-") {
1315 uris = read_uri_from_file(std::cin);
1317 std::ifstream infile(config.ifile);
1319 std::cerr << "cannot read input file: " << config.ifile << std::endl;
1323 uris = read_uri_from_file(infile);
1326 reqlines = parse_uris(std::begin(uris), std::end(uris));
1329 if (config.max_concurrent_streams == -1) {
1330 config.max_concurrent_streams = reqlines.size();
1334 shared_nva.emplace_back(":scheme", config.scheme);
1335 if (config.port != config.default_port) {
1336 shared_nva.emplace_back(":authority",
1337 config.host + ":" + util::utos(config.port));
1339 shared_nva.emplace_back(":authority", config.host);
1341 shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
1343 // list overridalbe headers
1344 auto override_hdrs =
1345 make_array<std::string>(":authority", ":host", ":method", ":scheme");
1347 for (auto &kv : config.custom_headers) {
1348 if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
1349 kv.name) != std::end(override_hdrs)) {
1351 for (auto &nv : shared_nva) {
1352 if ((nv.name == ":authority" && kv.name == ":host") ||
1353 (nv.name == kv.name)) {
1354 nv.value = kv.value;
1358 // add additional headers
1359 shared_nva.push_back(kv);
1363 for (auto &req : reqlines) {
1365 std::vector<nghttp2_nv> nva;
1367 nva.push_back(http2::make_nv_ls(":path", req));
1369 for (auto &nv : shared_nva) {
1370 nva.push_back(http2::make_nv(nv.name, nv.value, false));
1373 config.nva.push_back(std::move(nva));
1376 std::vector<const char *> cva;
1378 cva.push_back(":path");
1379 cva.push_back(req.c_str());
1381 for (auto &nv : shared_nva) {
1382 if (nv.name == ":authority") {
1383 cva.push_back(":host");
1385 cva.push_back(nv.name.c_str());
1387 cva.push_back(nv.value.c_str());
1389 cva.push_back(":version");
1390 cva.push_back("HTTP/1.1");
1391 cva.push_back(nullptr);
1393 config.nv.push_back(std::move(cva));
1398 if (config.nclients == 1) {
1399 config.nthreads = 1;
1402 size_t nreqs_per_thread = config.nreqs / config.nthreads;
1403 ssize_t nreqs_rem = config.nreqs % config.nthreads;
1405 size_t nclients_per_thread = config.nclients / config.nthreads;
1406 ssize_t nclients_rem = config.nclients % config.nthreads;
1408 std::cout << "starting benchmark..." << std::endl;
1410 auto start = std::chrono::steady_clock::now();
1412 std::vector<std::unique_ptr<Worker>> workers;
1413 workers.reserve(config.nthreads);
1416 std::vector<std::future<void>> futures;
1417 for (size_t i = 0; i < config.nthreads - 1; ++i) {
1418 auto nreqs = nreqs_per_thread + (nreqs_rem-- > 0);
1419 auto nclients = nclients_per_thread + (nclients_rem-- > 0);
1420 std::cout << "spawning thread #" << i << ": " << nclients
1421 << " concurrent clients, " << nreqs << " total requests"
1424 make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config));
1425 auto &worker = workers.back();
1427 std::async(std::launch::async, [&worker]() { worker->run(); }));
1431 auto nreqs_last = nreqs_per_thread + (nreqs_rem-- > 0);
1432 auto nclients_last = nclients_per_thread + (nclients_rem-- > 0);
1433 std::cout << "spawning thread #" << (config.nthreads - 1) << ": "
1434 << nclients_last << " concurrent clients, " << nreqs_last
1435 << " total requests" << std::endl;
1436 workers.push_back(make_unique<Worker>(config.nthreads - 1, ssl_ctx,
1437 nreqs_last, nclients_last, &config));
1438 workers.back()->run();
1441 for (auto &fut : futures) {
1446 auto end = std::chrono::steady_clock::now();
1448 std::chrono::duration_cast<std::chrono::microseconds>(end - start);
1451 for (const auto &w : workers) {
1452 const auto &s = w->stats;
1454 stats.req_todo += s.req_todo;
1455 stats.req_started += s.req_started;
1456 stats.req_done += s.req_done;
1457 stats.req_success += s.req_success;
1458 stats.req_status_success += s.req_status_success;
1459 stats.req_failed += s.req_failed;
1460 stats.req_error += s.req_error;
1461 stats.bytes_total += s.bytes_total;
1462 stats.bytes_head += s.bytes_head;
1463 stats.bytes_body += s.bytes_body;
1465 for (size_t i = 0; i < stats.status.size(); ++i) {
1466 stats.status[i] += s.status[i];
1470 auto ts = process_time_stats(workers);
1472 // Requests which have not been issued due to connection errors, are
1473 // counted towards req_failed and req_error.
1474 auto req_not_issued =
1475 stats.req_todo - stats.req_status_success - stats.req_failed;
1476 stats.req_failed += req_not_issued;
1477 stats.req_error += req_not_issued;
1479 // UI is heavily inspired by weighttp[1] and wrk[2]
1481 // [1] https://github.com/lighttpd/weighttp
1482 // [2] https://github.com/wg/wrk
1485 if (duration.count() > 0) {
1486 auto secd = static_cast<double>(duration.count()) / (1000 * 1000);
1487 rps = stats.req_success / secd;
1488 bps = stats.bytes_total / secd;
1492 finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
1493 << util::utos_with_funit(bps) << R"(B/s
1494 requests: )" << stats.req_todo << " total, " << stats.req_started
1495 << " started, " << stats.req_done << " done, "
1496 << stats.req_status_success << " succeeded, " << stats.req_failed
1497 << " failed, " << stats.req_error << R"( errored
1498 status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
1499 << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
1500 traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
1501 << " bytes headers, " << stats.bytes_body << R"( bytes data
1502 min max mean sd +/- sd
1503 time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
1504 << " " << std::setw(10) << util::format_duration(ts.request.max)
1505 << " " << std::setw(10) << util::format_duration(ts.request.mean)
1506 << " " << std::setw(10) << util::format_duration(ts.request.sd)
1507 << std::setw(9) << util::dtos(ts.request.within_sd) << "%"
1508 << "\ntime for connect: " << std::setw(10)
1509 << util::format_duration(ts.connect.min) << " " << std::setw(10)
1510 << util::format_duration(ts.connect.max) << " " << std::setw(10)
1511 << util::format_duration(ts.connect.mean) << " " << std::setw(10)
1512 << util::format_duration(ts.connect.sd) << std::setw(9)
1513 << util::dtos(ts.connect.within_sd) << "%"
1514 << "\ntime to 1st byte: " << std::setw(10)
1515 << util::format_duration(ts.ttfb.min) << " " << std::setw(10)
1516 << util::format_duration(ts.ttfb.max) << " " << std::setw(10)
1517 << util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
1518 << util::format_duration(ts.ttfb.sd) << std::setw(9)
1519 << util::dtos(ts.ttfb.within_sd) << "%" << std::endl;
1521 SSL_CTX_free(ssl_ctx);
1526 } // namespace h2load
1528 int main(int argc, char **argv) { return h2load::main(argc, argv); }