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
38 #include <netinet/udp.h>
51 #include <openssl/err.h>
54 # include <ngtcp2/ngtcp2.h>
55 #endif // ENABLE_HTTP3
57 #include "url-parser/url_parser.h"
59 #include "h2load_http1_session.h"
60 #include "h2load_http2_session.h"
62 # include "h2load_http3_session.h"
63 # include "h2load_quic.h"
64 #endif // ENABLE_HTTP3
74 using namespace nghttp2;
79 bool recorded(const std::chrono::steady_clock::time_point &t) {
80 return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
86 std::ofstream keylog_file;
87 void keylog_callback(const SSL *ssl, const char *line) {
88 keylog_file.write(line, strlen(line));
89 keylog_file.put('\n');
93 #endif // OPENSSL_1_1_1_API
96 : ciphers(tls::DEFAULT_CIPHER_LIST),
97 tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_"
98 "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"),
99 groups("X25519:P-256:P-384:P-521"),
106 max_concurrent_streams(1),
108 connection_window_bits(30),
113 conn_active_timeout(0.),
114 conn_inactivity_timeout(0.),
115 no_tls_proto(PROTO_HTTP2),
116 header_table_size(4_k),
117 encoder_header_table_size(4_k),
125 timing_script(false),
126 base_uri_unix(false),
130 max_udp_payload_size(0) {}
146 bool Config::is_rate_mode() const { return (this->rate != 0); }
147 bool Config::is_timing_based_mode() const { return (this->duration > 0); }
148 bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
149 bool Config::rps_enabled() const { return this->rps > 0.0; }
150 bool Config::is_quic() const {
152 return !npn_list.empty() &&
153 (npn_list[0] == NGHTTP3_ALPN_H3 || npn_list[0] == "\x5h3-29");
154 #else // !ENABLE_HTTP3
156 #endif // !ENABLE_HTTP3
161 constexpr size_t MAX_SAMPLES = 1000000;
164 Stats::Stats(size_t req_todo, size_t nclients)
165 : req_todo(req_todo),
169 req_status_success(0),
175 bytes_head_decomp(0),
181 Stream::Stream() : req_stat{}, status_success(-1) {}
184 std::random_device rd;
188 std::mt19937 gen(rd());
192 void sampling_init(Sampling &smp, size_t max_samples) {
194 smp.max_samples = max_samples;
199 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
200 auto client = static_cast<Client *>(w->data);
201 client->restart_timeout();
202 auto rv = client->do_write();
203 if (rv == Client::ERR_CONNECT_FAIL) {
204 client->disconnect();
206 client->current_addr = nullptr;
207 rv = client->connect();
210 client->worker->free_client(client);
218 client->worker->free_client(client);
225 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
226 auto client = static_cast<Client *>(w->data);
227 client->restart_timeout();
228 if (client->do_read() != 0) {
229 if (client->try_again_or_fail() == 0) {
232 client->worker->free_client(client);
236 client->signal_write();
241 // Called every rate_period when rate mode is being used
242 void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
243 auto worker = static_cast<Worker *>(w->data);
244 auto nclients_per_second = worker->rate;
245 auto conns_remaining = worker->nclients - worker->nconns_made;
246 auto nclients = std::min(nclients_per_second, conns_remaining);
248 for (size_t i = 0; i < nclients; ++i) {
249 auto req_todo = worker->nreqs_per_client;
250 if (worker->nreqs_rem > 0) {
255 std::make_unique<Client>(worker->next_client_id++, worker, req_todo);
257 ++worker->nconns_made;
259 if (client->connect() != 0) {
260 std::cerr << "client could not connect to host" << std::endl;
263 if (worker->config->is_timing_based_mode()) {
264 worker->clients.push_back(client.release());
269 worker->report_rate_progress();
271 if (!worker->config->is_timing_based_mode()) {
272 if (worker->nconns_made >= worker->nclients) {
273 ev_timer_stop(worker->loop, w);
276 // To check whether all created clients are pushed correctly
277 assert(worker->nclients == worker->clients.size());
283 // Called when the duration for infinite number of requests are over
284 void duration_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
285 auto worker = static_cast<Worker *>(w->data);
287 worker->current_phase = Phase::DURATION_OVER;
289 std::cout << "Main benchmark duration is over for thread #" << worker->id
290 << ". Stopping all clients." << std::endl;
291 worker->stop_all_clients();
292 std::cout << "Stopped all clients for thread #" << worker->id << std::endl;
297 // Called when the warmup duration for infinite number of requests are over
298 void warmup_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
299 auto worker = static_cast<Worker *>(w->data);
301 std::cout << "Warm-up phase is over for thread #" << worker->id << "."
303 std::cout << "Main benchmark duration is started for thread #" << worker->id
305 assert(worker->stats.req_started == 0);
306 assert(worker->stats.req_done == 0);
308 for (auto client : worker->clients) {
310 assert(client->req_todo == 0);
311 assert(client->req_left == 1);
312 assert(client->req_inflight == 0);
313 assert(client->req_started == 0);
314 assert(client->req_done == 0);
316 client->record_client_start_time();
317 client->clear_connect_times();
318 client->record_connect_start_time();
322 worker->current_phase = Phase::MAIN_DURATION;
324 ev_timer_start(worker->loop, &worker->duration_watcher);
329 void rps_cb(struct ev_loop *loop, ev_timer *w, int revents) {
330 auto client = static_cast<Client *>(w->data);
331 auto &session = client->session;
333 assert(!config.timing_script);
335 if (client->req_left == 0) {
336 ev_timer_stop(loop, w);
340 auto now = ev_now(loop);
341 auto d = now - client->rps_duration_started;
342 auto n = static_cast<size_t>(round(d * config.rps));
343 client->rps_req_pending += n;
344 client->rps_duration_started = now - d + static_cast<double>(n) / config.rps;
346 if (client->rps_req_pending == 0) {
350 auto nreq = session->max_concurrent_streams() - client->rps_req_inflight;
355 nreq = config.is_timing_based_mode() ? std::max(nreq, client->req_left)
356 : std::min(nreq, client->req_left);
357 nreq = std::min(nreq, client->rps_req_pending);
359 client->rps_req_inflight += nreq;
360 client->rps_req_pending -= nreq;
362 for (; nreq > 0; --nreq) {
363 if (client->submit_request() != 0) {
364 client->process_request_failure();
369 client->signal_write();
374 // Called when an a connection has been inactive for a set period of time
375 // or a fixed amount of time after all requests have been made on a
377 void conn_timeout_cb(EV_P_ ev_timer *w, int revents) {
378 auto client = static_cast<Client *>(w->data);
380 ev_timer_stop(client->worker->loop, &client->conn_inactivity_watcher);
381 ev_timer_stop(client->worker->loop, &client->conn_active_watcher);
383 if (util::check_socket_connected(client->fd)) {
390 bool check_stop_client_request_timeout(Client *client, ev_timer *w) {
391 if (client->req_left == 0) {
392 // no more requests to make, stop timer
393 ev_timer_stop(client->worker->loop, w);
402 void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
403 auto client = static_cast<Client *>(w->data);
405 if (client->streams.size() >= (size_t)config.max_concurrent_streams) {
406 ev_timer_stop(client->worker->loop, w);
410 if (client->submit_request() != 0) {
411 ev_timer_stop(client->worker->loop, w);
412 client->process_request_failure();
415 client->signal_write();
417 if (check_stop_client_request_timeout(client, w)) {
422 config.timings[client->reqidx] - config.timings[client->reqidx - 1];
424 while (duration < 1e-9) {
425 if (client->submit_request() != 0) {
426 ev_timer_stop(client->worker->loop, w);
427 client->process_request_failure();
430 client->signal_write();
431 if (check_stop_client_request_timeout(client, w)) {
436 config.timings[client->reqidx] - config.timings[client->reqidx - 1];
439 client->request_timeout_watcher.repeat = duration;
440 ev_timer_again(client->worker->loop, &client->request_timeout_watcher);
444 Client::Client(uint32_t id, Worker *worker, size_t req_todo)
445 : wb(&worker->mcpool),
451 #endif // ENABLE_HTTP3
452 next_addr(config.addrs),
453 current_addr(nullptr),
464 new_connection_requested(false),
466 rps_duration_started(0),
468 rps_req_inflight(0) {
469 if (req_todo == 0) { // this means infinite number of requests are to be made
470 // This ensures that number of requests are unbounded
471 // Just a positive number is fine, we chose the first positive number
474 ev_io_init(&wev, writecb, 0, EV_WRITE);
475 ev_io_init(&rev, readcb, 0, EV_READ);
480 ev_timer_init(&conn_inactivity_watcher, conn_timeout_cb, 0.,
481 worker->config->conn_inactivity_timeout);
482 conn_inactivity_watcher.data = this;
484 ev_timer_init(&conn_active_watcher, conn_timeout_cb,
485 worker->config->conn_active_timeout, 0.);
486 conn_active_watcher.data = this;
488 ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.);
489 request_timeout_watcher.data = this;
491 ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
492 rps_watcher.data = this;
495 ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
496 quic.pkt_timer.data = this;
497 #endif // ENABLE_HTTP3
504 if (config.is_quic()) {
507 #endif // ENABLE_HTTP3
513 worker->sample_client_stat(&cstat);
514 ++worker->client_smp.n;
517 int Client::do_read() { return readfn(*this); }
518 int Client::do_write() { return writefn(*this); }
520 int Client::make_socket(addrinfo *addr) {
523 if (config.is_quic()) {
525 fd = util::create_nonblock_udp_socket(addr->ai_family);
530 rv = util::bind_any_addr_udp(fd, addr->ai_family);
537 socklen_t addrlen = sizeof(local_addr.su.storage);
538 rv = getsockname(fd, &local_addr.su.sa, &addrlen);
542 local_addr.len = addrlen;
544 if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr,
545 addr->ai_addrlen) != 0) {
546 std::cerr << "quic_init failed" << std::endl;
549 #endif // ENABLE_HTTP3
551 fd = util::create_nonblock_socket(addr->ai_family);
555 if (config.scheme == "https") {
557 ssl = SSL_new(worker->ssl_ctx);
561 SSL_set_connect_state(ssl);
565 if (ssl && !util::numeric_host(config.host.c_str())) {
566 SSL_set_tlsext_host_name(ssl, config.host.c_str());
569 if (config.is_quic()) {
573 rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
574 if (rv != 0 && errno != EINPROGRESS) {
586 int Client::connect() {
589 if (!worker->config->is_timing_based_mode() ||
590 worker->current_phase == Phase::MAIN_DURATION) {
591 record_client_start_time();
592 clear_connect_times();
593 record_connect_start_time();
594 } else if (worker->current_phase == Phase::INITIAL_IDLE) {
595 worker->current_phase = Phase::WARM_UP;
596 std::cout << "Warm-up started for thread #" << worker->id << "."
598 ev_timer_start(worker->loop, &worker->warmup_watcher);
601 if (worker->config->conn_inactivity_timeout > 0.) {
602 ev_timer_again(worker->loop, &conn_inactivity_watcher);
606 rv = make_socket(current_addr);
611 addrinfo *addr = nullptr;
614 next_addr = next_addr->ai_next;
615 rv = make_socket(addr);
630 ev_io_set(&rev, fd, EV_READ);
631 ev_io_set(&wev, fd, EV_WRITE);
633 ev_io_start(worker->loop, &wev);
635 if (config.is_quic()) {
637 ev_io_start(worker->loop, &rev);
639 readfn = &Client::read_quic;
640 writefn = &Client::write_quic;
641 #endif // ENABLE_HTTP3
643 writefn = &Client::connected;
649 void Client::timeout() {
650 process_timedout_streams();
655 void Client::restart_timeout() {
656 if (worker->config->conn_inactivity_timeout > 0.) {
657 ev_timer_again(worker->loop, &conn_inactivity_watcher);
661 int Client::try_again_or_fail() {
664 if (new_connection_requested) {
665 new_connection_requested = false;
669 if (worker->current_phase == Phase::MAIN_DURATION) {
670 // At the moment, we don't have a facility to re-start request
671 // already in in-flight. Make them fail.
672 worker->stats.req_failed += req_inflight;
673 worker->stats.req_error += req_inflight;
678 // Keep using current address
679 if (connect() == 0) {
682 std::cerr << "client could not connect to host" << std::endl;
686 process_abandoned_streams();
691 void Client::fail() {
694 process_abandoned_streams();
697 void Client::disconnect() {
698 record_client_end_time();
701 if (config.is_quic()) {
702 quic_close_connection();
704 #endif // ENABLE_HTTP3
707 ev_timer_stop(worker->loop, &quic.pkt_timer);
708 #endif // ENABLE_HTTP3
709 ev_timer_stop(worker->loop, &conn_inactivity_watcher);
710 ev_timer_stop(worker->loop, &conn_active_watcher);
711 ev_timer_stop(worker->loop, &rps_watcher);
712 ev_timer_stop(worker->loop, &request_timeout_watcher);
717 ev_io_stop(worker->loop, &wev);
718 ev_io_stop(worker->loop, &rev);
720 SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
723 if (SSL_shutdown(ssl) != 1) {
729 shutdown(fd, SHUT_WR);
737 int Client::submit_request() {
738 if (session->submit_request() != 0) {
742 if (worker->current_phase != Phase::MAIN_DURATION) {
746 ++worker->stats.req_started;
749 if (!worker->config->is_timing_based_mode()) {
752 // if an active timeout is set and this is the last request to be submitted
753 // on this connection, start the active timeout.
754 if (worker->config->conn_active_timeout > 0. && req_left == 0) {
755 ev_timer_start(worker->loop, &conn_active_watcher);
761 void Client::process_timedout_streams() {
762 if (worker->current_phase != Phase::MAIN_DURATION) {
766 for (auto &p : streams) {
767 auto &req_stat = p.second.req_stat;
768 if (!req_stat.completed) {
769 req_stat.stream_close_time = std::chrono::steady_clock::now();
773 worker->stats.req_timedout += req_inflight;
775 process_abandoned_streams();
778 void Client::process_abandoned_streams() {
779 if (worker->current_phase != Phase::MAIN_DURATION) {
783 auto req_abandoned = req_inflight + req_left;
785 worker->stats.req_failed += req_abandoned;
786 worker->stats.req_error += req_abandoned;
792 void Client::process_request_failure() {
793 if (worker->current_phase != Phase::MAIN_DURATION) {
797 worker->stats.req_failed += req_left;
798 worker->stats.req_error += req_left;
802 if (req_inflight == 0) {
805 std::cout << "Process Request Failure:" << worker->stats.req_failed
810 void print_server_tmp_key(SSL *ssl) {
811 // libressl does not have SSL_get_server_tmp_key
812 #if OPENSSL_VERSION_NUMBER >= 0x10002000L && defined(SSL_get_server_tmp_key)
815 if (!SSL_get_server_tmp_key(ssl, &key)) {
819 auto key_del = defer(EVP_PKEY_free, key);
821 std::cout << "Server Temp Key: ";
823 auto pkey_id = EVP_PKEY_id(key);
826 std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
829 std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
832 # if OPENSSL_3_0_0_API
833 std::array<char, 64> curve_name;
835 if (!EVP_PKEY_get_utf8_string_param(key, "group", curve_name.data(),
836 curve_name.size(), nullptr)) {
839 cname = curve_name.data();
841 # else // !OPENSSL_3_0_0_API
842 auto ec = EVP_PKEY_get1_EC_KEY(key);
843 auto ec_del = defer(EC_KEY_free, ec);
844 auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
845 auto cname = EC_curve_nid2nist(nid);
847 cname = OBJ_nid2sn(nid);
849 # endif // !OPENSSL_3_0_0_API
851 std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
856 std::cout << OBJ_nid2sn(pkey_id) << " " << EVP_PKEY_bits(key) << " bits"
860 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
864 void Client::report_tls_info() {
865 if (worker->id == 0 && !worker->tls_info_report_done) {
866 worker->tls_info_report_done = true;
867 auto cipher = SSL_get_current_cipher(ssl);
868 std::cout << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n"
869 << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
870 print_server_tmp_key(ssl);
874 void Client::report_app_info() {
875 if (worker->id == 0 && !worker->app_info_report_done) {
876 worker->app_info_report_done = true;
877 std::cout << "Application protocol: " << selected_proto << std::endl;
881 void Client::terminate_session() {
883 if (config.is_quic()) {
884 quic.close_requested = true;
886 #endif // ENABLE_HTTP3
888 session->terminate();
890 // http1 session needs writecb to tear down session.
894 void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
896 void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
897 const uint8_t *value, size_t valuelen) {
898 auto itr = streams.find(stream_id);
899 if (itr == std::end(streams)) {
902 auto &stream = (*itr).second;
904 if (worker->current_phase != Phase::MAIN_DURATION) {
905 // If the stream is for warm-up phase, then mark as a success
906 // But we do not update the count for 2xx, 3xx, etc status codes
907 // Same has been done in on_status_code function
908 stream.status_success = 1;
912 if (stream.status_success == -1 && namelen == 7 &&
913 util::streq_l(":status", name, namelen)) {
915 for (size_t i = 0; i < valuelen; ++i) {
916 if ('0' <= value[i] && value[i] <= '9') {
918 status += value[i] - '0';
920 stream.status_success = 0;
928 stream.req_stat.status = status;
929 if (status >= 200 && status < 300) {
930 ++worker->stats.status[2];
931 stream.status_success = 1;
932 } else if (status < 400) {
933 ++worker->stats.status[3];
934 stream.status_success = 1;
935 } else if (status < 600) {
936 ++worker->stats.status[status / 100];
937 stream.status_success = 0;
939 stream.status_success = 0;
944 void Client::on_status_code(int32_t stream_id, uint16_t status) {
945 auto itr = streams.find(stream_id);
946 if (itr == std::end(streams)) {
949 auto &stream = (*itr).second;
951 if (worker->current_phase != Phase::MAIN_DURATION) {
952 stream.status_success = 1;
956 stream.req_stat.status = status;
957 if (status >= 200 && status < 300) {
958 ++worker->stats.status[2];
959 stream.status_success = 1;
960 } else if (status < 400) {
961 ++worker->stats.status[3];
962 stream.status_success = 1;
963 } else if (status < 600) {
964 ++worker->stats.status[status / 100];
965 stream.status_success = 0;
967 stream.status_success = 0;
971 void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
972 if (worker->current_phase == Phase::MAIN_DURATION) {
973 if (req_inflight > 0) {
976 auto req_stat = get_req_stat(stream_id);
981 req_stat->stream_close_time = std::chrono::steady_clock::now();
983 req_stat->completed = true;
984 ++worker->stats.req_success;
987 if (streams[stream_id].status_success == 1) {
988 ++worker->stats.req_status_success;
990 ++worker->stats.req_failed;
993 worker->sample_req_stat(req_stat);
995 // Count up in successful cases only
996 ++worker->request_times_smp.n;
998 ++worker->stats.req_failed;
999 ++worker->stats.req_error;
1001 ++worker->stats.req_done;
1004 if (worker->config->log_fd != -1) {
1005 auto start = std::chrono::duration_cast<std::chrono::microseconds>(
1006 req_stat->request_wall_time.time_since_epoch());
1007 auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
1008 req_stat->stream_close_time - req_stat->request_time);
1010 std::array<uint8_t, 256> buf;
1011 auto p = std::begin(buf);
1012 p = util::utos(p, start.count());
1015 p = util::utos(p, req_stat->status);
1021 p = util::utos(p, delta.count());
1024 auto nwrite = static_cast<size_t>(std::distance(std::begin(buf), p));
1025 assert(nwrite <= buf.size());
1026 while (write(worker->config->log_fd, buf.data(), nwrite) == -1 &&
1032 worker->report_progress();
1033 streams.erase(stream_id);
1034 if (req_left == 0 && req_inflight == 0) {
1035 terminate_session();
1039 if (!final && req_left > 0) {
1040 if (config.timing_script) {
1041 if (!ev_is_active(&request_timeout_watcher)) {
1042 ev_feed_event(worker->loop, &request_timeout_watcher, EV_TIMER);
1044 } else if (!config.rps_enabled()) {
1045 if (submit_request() != 0) {
1046 process_request_failure();
1048 } else if (rps_req_pending) {
1050 if (submit_request() != 0) {
1051 process_request_failure();
1054 assert(rps_req_inflight);
1060 RequestStat *Client::get_req_stat(int32_t stream_id) {
1061 auto it = streams.find(stream_id);
1062 if (it == std::end(streams)) {
1066 return &(*it).second.req_stat;
1069 int Client::connection_made() {
1073 const unsigned char *next_proto = nullptr;
1074 unsigned int next_proto_len;
1076 #ifndef OPENSSL_NO_NEXTPROTONEG
1077 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
1078 #endif // !OPENSSL_NO_NEXTPROTONEG
1079 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
1080 if (next_proto == nullptr) {
1081 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
1083 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
1086 auto proto = StringRef{next_proto, next_proto_len};
1087 if (config.is_quic()) {
1090 if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) &&
1091 !util::streq_l("h3-29", proto)) {
1094 #endif // ENABLE_HTTP3
1095 } else if (util::check_h2_is_selected(proto)) {
1096 session = std::make_unique<Http2Session>(this);
1097 } else if (util::streq(NGHTTP2_H1_1, proto)) {
1098 session = std::make_unique<Http1Session>(this);
1101 // Just assign next_proto to selected_proto anyway to show the
1102 // negotiation result.
1103 selected_proto = proto.str();
1104 } else if (config.is_quic()) {
1105 std::cerr << "QUIC requires ALPN negotiation" << std::endl;
1108 std::cout << "No protocol negotiated. Fallback behaviour may be activated"
1111 for (const auto &proto : config.npn_list) {
1112 if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) {
1114 << "Server does not support NPN/ALPN. Falling back to HTTP/1.1."
1116 session = std::make_unique<Http1Session>(this);
1117 selected_proto = NGHTTP2_H1_1.str();
1123 if (!selected_proto.empty()) {
1129 << "No supported protocol was negotiated. Supported protocols were:"
1131 for (const auto &proto : config.npn_list) {
1132 std::cout << proto.substr(1) << std::endl;
1138 switch (config.no_tls_proto) {
1139 case Config::PROTO_HTTP2:
1140 session = std::make_unique<Http2Session>(this);
1141 selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
1143 case Config::PROTO_HTTP1_1:
1144 session = std::make_unique<Http1Session>(this);
1145 selected_proto = NGHTTP2_H1_1.str();
1155 state = CLIENT_CONNECTED;
1157 session->on_connect();
1159 record_connect_time();
1161 if (config.rps_enabled()) {
1162 rps_watcher.repeat = std::max(0.01, 1. / config.rps);
1163 ev_timer_again(worker->loop, &rps_watcher);
1164 rps_duration_started = ev_now(worker->loop);
1167 if (config.rps_enabled()) {
1172 if (submit_request() != 0) {
1173 process_request_failure();
1175 } else if (!config.timing_script) {
1176 auto nreq = config.is_timing_based_mode()
1177 ? std::max(req_left, session->max_concurrent_streams())
1178 : std::min(req_left, session->max_concurrent_streams());
1180 for (; nreq > 0; --nreq) {
1181 if (submit_request() != 0) {
1182 process_request_failure();
1188 ev_tstamp duration = config.timings[reqidx];
1190 while (duration < 1e-9) {
1191 if (submit_request() != 0) {
1192 process_request_failure();
1195 duration = config.timings[reqidx];
1197 // if reqidx wraps around back to 0, we uses up all lines and
1203 if (duration >= 1e-9) {
1204 // double check since we may have break due to reqidx wraps
1206 request_timeout_watcher.repeat = duration;
1207 ev_timer_again(worker->loop, &request_timeout_watcher);
1215 int Client::on_read(const uint8_t *data, size_t len) {
1216 auto rv = session->on_read(data, len);
1220 if (worker->current_phase == Phase::MAIN_DURATION) {
1221 worker->stats.bytes_total += len;
1227 int Client::on_write() {
1228 if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
1232 if (session->on_write() != 0) {
1238 int Client::read_clear() {
1243 while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
1246 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1256 if (on_read(buf, nread) != 0) {
1264 int Client::write_clear() {
1265 std::array<struct iovec, 2> iov;
1268 if (on_write() != 0) {
1272 auto iovcnt = wb.riovec(iov.data(), iov.size());
1279 while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
1283 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1284 ev_io_start(worker->loop, &wev);
1293 ev_io_stop(worker->loop, &wev);
1298 int Client::connected() {
1299 if (!util::check_socket_connected(fd)) {
1300 return ERR_CONNECT_FAIL;
1302 ev_io_start(worker->loop, &rev);
1303 ev_io_stop(worker->loop, &wev);
1306 readfn = &Client::tls_handshake;
1307 writefn = &Client::tls_handshake;
1312 readfn = &Client::read_clear;
1313 writefn = &Client::write_clear;
1315 if (connection_made() != 0) {
1322 int Client::tls_handshake() {
1325 auto rv = SSL_do_handshake(ssl);
1328 auto err = SSL_get_error(ssl, rv);
1330 case SSL_ERROR_WANT_READ:
1331 ev_io_stop(worker->loop, &wev);
1333 case SSL_ERROR_WANT_WRITE:
1334 ev_io_start(worker->loop, &wev);
1341 ev_io_stop(worker->loop, &wev);
1343 readfn = &Client::read_tls;
1344 writefn = &Client::write_tls;
1346 if (connection_made() != 0) {
1353 int Client::read_tls() {
1359 auto rv = SSL_read(ssl, buf, sizeof(buf));
1362 auto err = SSL_get_error(ssl, rv);
1364 case SSL_ERROR_WANT_READ:
1366 case SSL_ERROR_WANT_WRITE:
1367 // renegotiation started
1374 if (on_read(buf, rv) != 0) {
1380 int Client::write_tls() {
1386 if (on_write() != 0) {
1390 auto iovcnt = wb.riovec(&iov, 1);
1396 auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
1399 auto err = SSL_get_error(ssl, rv);
1401 case SSL_ERROR_WANT_READ:
1402 // renegotiation started
1404 case SSL_ERROR_WANT_WRITE:
1405 ev_io_start(worker->loop, &wev);
1415 ev_io_stop(worker->loop, &wev);
1421 int Client::write_udp(const sockaddr *addr, socklen_t addrlen,
1422 const uint8_t *data, size_t datalen, size_t gso_size) {
1424 msg_iov.iov_base = const_cast<uint8_t *>(data);
1425 msg_iov.iov_len = datalen;
1428 msg.msg_name = const_cast<sockaddr *>(addr);
1429 msg.msg_namelen = addrlen;
1430 msg.msg_iov = &msg_iov;
1434 std::array<uint8_t, CMSG_SPACE(sizeof(uint16_t))> msg_ctrl{};
1435 if (gso_size && datalen > gso_size) {
1436 msg.msg_control = msg_ctrl.data();
1437 msg.msg_controllen = msg_ctrl.size();
1439 auto cm = CMSG_FIRSTHDR(&msg);
1440 cm->cmsg_level = SOL_UDP;
1441 cm->cmsg_type = UDP_SEGMENT;
1442 cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
1443 *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
1445 # endif // UDP_SEGMENT
1447 auto nwrite = sendmsg(fd, &msg, 0);
1449 std::cerr << "sendto: errno=" << errno << std::endl;
1451 ++worker->stats.udp_dgram_sent;
1454 ev_io_stop(worker->loop, &wev);
1458 #endif // ENABLE_HTTP3
1460 void Client::record_request_time(RequestStat *req_stat) {
1461 req_stat->request_time = std::chrono::steady_clock::now();
1462 req_stat->request_wall_time = std::chrono::system_clock::now();
1465 void Client::record_connect_start_time() {
1466 cstat.connect_start_time = std::chrono::steady_clock::now();
1469 void Client::record_connect_time() {
1470 cstat.connect_time = std::chrono::steady_clock::now();
1473 void Client::record_ttfb() {
1474 if (recorded(cstat.ttfb)) {
1478 cstat.ttfb = std::chrono::steady_clock::now();
1481 void Client::clear_connect_times() {
1482 cstat.connect_start_time = std::chrono::steady_clock::time_point();
1483 cstat.connect_time = std::chrono::steady_clock::time_point();
1484 cstat.ttfb = std::chrono::steady_clock::time_point();
1487 void Client::record_client_start_time() {
1488 // Record start time only once at the very first connection is going
1490 if (recorded(cstat.client_start_time)) {
1494 cstat.client_start_time = std::chrono::steady_clock::now();
1497 void Client::record_client_end_time() {
1498 // Unlike client_start_time, we overwrite client_end_time. This
1499 // handles multiple connect/disconnect for HTTP/1.1 benchmark.
1500 cstat.client_end_time = std::chrono::steady_clock::now();
1503 void Client::signal_write() { ev_io_start(worker->loop, &wev); }
1505 void Client::try_new_connection() { new_connection_requested = true; }
1508 int get_ev_loop_flags() {
1509 if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
1510 return ev_recommended_backends() | EVBACKEND_KQUEUE;
1517 Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
1518 size_t rate, size_t max_samples, Config *config)
1519 : stats(req_todo, nclients),
1520 loop(ev_loop_new(get_ev_loop_flags())),
1524 tls_info_report_done(false),
1525 app_info_report_done(false),
1528 nreqs_per_client(req_todo / nclients),
1529 nreqs_rem(req_todo % nclients),
1531 max_samples(max_samples),
1533 if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
1534 progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
1536 progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
1539 // Below timeout is not needed in case of timing-based benchmarking
1540 // create timer that will go off every rate_period
1541 ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
1542 config->rate_period);
1543 timeout_watcher.data = this;
1545 if (config->is_timing_based_mode()) {
1546 stats.req_stats.reserve(std::max(req_todo, max_samples));
1547 stats.client_stats.reserve(std::max(nclients, max_samples));
1549 stats.req_stats.reserve(std::min(req_todo, max_samples));
1550 stats.client_stats.reserve(std::min(nclients, max_samples));
1553 sampling_init(request_times_smp, max_samples);
1554 sampling_init(client_smp, max_samples);
1556 ev_timer_init(&duration_watcher, duration_timeout_cb, config->duration, 0.);
1557 duration_watcher.data = this;
1559 ev_timer_init(&warmup_watcher, warmup_timeout_cb, config->warm_up_time, 0.);
1560 warmup_watcher.data = this;
1562 if (config->is_timing_based_mode()) {
1563 current_phase = Phase::INITIAL_IDLE;
1565 current_phase = Phase::MAIN_DURATION;
1570 ev_timer_stop(loop, &timeout_watcher);
1571 ev_timer_stop(loop, &duration_watcher);
1572 ev_timer_stop(loop, &warmup_watcher);
1573 ev_loop_destroy(loop);
1576 void Worker::stop_all_clients() {
1577 for (auto client : clients) {
1579 client->terminate_session();
1584 void Worker::free_client(Client *deleted_client) {
1585 for (auto &client : clients) {
1586 if (client == deleted_client) {
1587 client->req_todo = client->req_done;
1588 stats.req_todo += client->req_todo;
1589 auto index = &client - &clients[0];
1590 clients[index] = NULL;
1596 void Worker::run() {
1597 if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
1598 for (size_t i = 0; i < nclients; ++i) {
1599 auto req_todo = nreqs_per_client;
1600 if (nreqs_rem > 0) {
1605 auto client = std::make_unique<Client>(next_client_id++, this, req_todo);
1606 if (client->connect() != 0) {
1607 std::cerr << "client could not connect to host" << std::endl;
1613 } else if (config->is_rate_mode()) {
1614 ev_timer_again(loop, &timeout_watcher);
1616 // call callback so that we don't waste the first rate_period
1617 rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
1619 // call the callback to start for one single time
1620 rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
1626 template <typename Stats, typename Stat>
1627 void sample(Sampling &smp, Stats &stats, Stat *s) {
1629 if (stats.size() < smp.max_samples) {
1630 stats.push_back(*s);
1633 auto d = std::uniform_int_distribution<unsigned long>(0, smp.n - 1);
1635 if (i < smp.max_samples) {
1641 void Worker::sample_req_stat(RequestStat *req_stat) {
1642 sample(request_times_smp, stats.req_stats, req_stat);
1645 void Worker::sample_client_stat(ClientStat *cstat) {
1646 sample(client_smp, stats.client_stats, cstat);
1649 void Worker::report_progress() {
1650 if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval ||
1651 config->is_timing_based_mode()) {
1655 std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
1659 void Worker::report_rate_progress() {
1660 if (id != 0 || nconns_made % progress_interval) {
1664 std::cout << "progress: " << nconns_made * 100 / nclients
1665 << "% of clients started" << std::endl;
1669 // Returns percentage of number of samples within mean +/- sd.
1670 double within_sd(const std::vector<double> &samples, double mean, double sd) {
1671 if (samples.size() == 0) {
1674 auto lower = mean - sd;
1675 auto upper = mean + sd;
1676 auto m = std::count_if(
1677 std::begin(samples), std::end(samples),
1678 [&lower, &upper](double t) { return lower <= t && t <= upper; });
1679 return (m / static_cast<double>(samples.size())) * 100;
1684 // Computes statistics using |samples|. The min, max, mean, sd, and
1685 // percentage of number of samples within mean +/- sd are computed.
1686 // If |sampling| is true, this computes sample variance. Otherwise,
1687 // population variance.
1688 SDStat compute_time_stat(const std::vector<double> &samples,
1689 bool sampling = false) {
1690 if (samples.empty()) {
1691 return {0.0, 0.0, 0.0, 0.0, 0.0};
1693 // standard deviation calculated using Rapid calculation method:
1694 // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
1695 double a = 0, q = 0;
1698 auto res = SDStat{std::numeric_limits<double>::max(),
1699 std::numeric_limits<double>::min()};
1700 for (const auto &t : samples) {
1702 res.min = std::min(res.min, t);
1703 res.max = std::max(res.max, t);
1706 auto na = a + (t - a) / n;
1707 q += (t - a) * (t - na);
1713 res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
1714 res.within_sd = within_sd(samples, res.mean, res.sd);
1722 process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
1723 auto request_times_sampling = false;
1724 auto client_times_sampling = false;
1725 size_t nrequest_times = 0;
1726 size_t nclient_times = 0;
1727 for (const auto &w : workers) {
1728 nrequest_times += w->stats.req_stats.size();
1729 request_times_sampling = w->request_times_smp.n > w->stats.req_stats.size();
1731 nclient_times += w->stats.client_stats.size();
1732 client_times_sampling = w->client_smp.n > w->stats.client_stats.size();
1735 std::vector<double> request_times;
1736 request_times.reserve(nrequest_times);
1738 std::vector<double> connect_times, ttfb_times, rps_values;
1739 connect_times.reserve(nclient_times);
1740 ttfb_times.reserve(nclient_times);
1741 rps_values.reserve(nclient_times);
1743 for (const auto &w : workers) {
1744 for (const auto &req_stat : w->stats.req_stats) {
1745 if (!req_stat.completed) {
1748 request_times.push_back(
1749 std::chrono::duration_cast<std::chrono::duration<double>>(
1750 req_stat.stream_close_time - req_stat.request_time)
1754 const auto &stat = w->stats;
1756 for (const auto &cstat : stat.client_stats) {
1757 if (recorded(cstat.client_start_time) &&
1758 recorded(cstat.client_end_time)) {
1759 auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
1760 cstat.client_end_time - cstat.client_start_time)
1763 rps_values.push_back(cstat.req_success / t);
1767 // We will get connect event before FFTB.
1768 if (!recorded(cstat.connect_start_time) ||
1769 !recorded(cstat.connect_time)) {
1773 connect_times.push_back(
1774 std::chrono::duration_cast<std::chrono::duration<double>>(
1775 cstat.connect_time - cstat.connect_start_time)
1778 if (!recorded(cstat.ttfb)) {
1782 ttfb_times.push_back(
1783 std::chrono::duration_cast<std::chrono::duration<double>>(
1784 cstat.ttfb - cstat.connect_start_time)
1789 return {compute_time_stat(request_times, request_times_sampling),
1790 compute_time_stat(connect_times, client_times_sampling),
1791 compute_time_stat(ttfb_times, client_times_sampling),
1792 compute_time_stat(rps_values, client_times_sampling)};
1797 void resolve_host() {
1798 if (config.base_uri_unix) {
1799 auto res = std::make_unique<addrinfo>();
1800 res->ai_family = config.unix_addr.sun_family;
1801 res->ai_socktype = SOCK_STREAM;
1802 res->ai_addrlen = sizeof(config.unix_addr);
1804 static_cast<struct sockaddr *>(static_cast<void *>(&config.unix_addr));
1806 config.addrs = res.release();
1811 addrinfo hints{}, *res;
1813 hints.ai_family = AF_UNSPEC;
1814 hints.ai_socktype = SOCK_STREAM;
1815 hints.ai_protocol = 0;
1816 hints.ai_flags = AI_ADDRCONFIG;
1818 const auto &resolve_host =
1819 config.connect_to_host.empty() ? config.host : config.connect_to_host;
1821 config.connect_to_port == 0 ? config.port : config.connect_to_port;
1824 getaddrinfo(resolve_host.c_str(), util::utos(port).c_str(), &hints, &res);
1826 std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
1829 if (res == nullptr) {
1830 std::cerr << "No address returned" << std::endl;
1838 std::string get_reqline(const char *uri, const http_parser_url &u) {
1839 std::string reqline;
1841 if (util::has_uri_field(u, UF_PATH)) {
1842 reqline = util::get_uri_field(uri, u, UF_PATH).str();
1847 if (util::has_uri_field(u, UF_QUERY)) {
1849 reqline += util::get_uri_field(uri, u, UF_QUERY);
1856 #ifndef OPENSSL_NO_NEXTPROTONEG
1858 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
1859 unsigned char *outlen, const unsigned char *in,
1860 unsigned int inlen, void *arg) {
1861 if (util::select_protocol(const_cast<const unsigned char **>(out), outlen, in,
1862 inlen, config.npn_list)) {
1863 return SSL_TLSEXT_ERR_OK;
1866 // OpenSSL will terminate handshake with fatal alert if we return
1867 // NOACK. So there is no way to fallback.
1868 return SSL_TLSEXT_ERR_NOACK;
1871 #endif // !OPENSSL_NO_NEXTPROTONEG
1874 constexpr char UNIX_PATH_PREFIX[] = "unix:";
1878 bool parse_base_uri(const StringRef &base_uri) {
1879 http_parser_url u{};
1880 if (http_parser_parse_url(base_uri.c_str(), base_uri.size(), 0, &u) != 0 ||
1881 !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
1885 config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA).str();
1886 config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST).str();
1887 config.default_port = util::get_default_port(base_uri.c_str(), u);
1888 if (util::has_uri_field(u, UF_PORT)) {
1889 config.port = u.port;
1891 config.port = config.default_port;
1898 // Use std::vector<std::string>::iterator explicitly, without that,
1899 // http_parser_url u{} fails with clang-3.4.
1900 std::vector<std::string> parse_uris(std::vector<std::string>::iterator first,
1901 std::vector<std::string>::iterator last) {
1902 std::vector<std::string> reqlines;
1904 if (first == last) {
1905 std::cerr << "no URI available" << std::endl;
1909 if (!config.has_base_uri()) {
1911 if (!parse_base_uri(StringRef{*first})) {
1912 std::cerr << "invalid URI: " << *first << std::endl;
1916 config.base_uri = *first;
1919 for (; first != last; ++first) {
1920 http_parser_url u{};
1922 auto uri = (*first).c_str();
1924 if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0) {
1925 std::cerr << "invalid URI: " << uri << std::endl;
1929 reqlines.push_back(get_reqline(uri, u));
1937 std::vector<std::string> read_uri_from_file(std::istream &infile) {
1938 std::vector<std::string> uris;
1939 std::string line_uri;
1940 while (std::getline(infile, line_uri)) {
1941 uris.push_back(line_uri);
1949 void read_script_from_file(std::istream &infile,
1950 std::vector<ev_tstamp> &timings,
1951 std::vector<std::string> &uris) {
1952 std::string script_line;
1954 while (std::getline(infile, script_line)) {
1956 if (script_line.empty()) {
1957 std::cerr << "Empty line detected at line " << line_count
1958 << ". Ignoring and continuing." << std::endl;
1962 std::size_t pos = script_line.find("\t");
1963 if (pos == std::string::npos) {
1964 std::cerr << "Invalid line format detected, no tab character at line "
1965 << line_count << ". \n\t" << script_line << std::endl;
1969 const char *start = script_line.c_str();
1971 auto v = std::strtod(start, &end);
1974 if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) {
1976 std::cerr << "Time value error at line " << line_count << ". \n\t"
1977 << "value = " << script_line.substr(0, pos) << std::endl;
1979 std::cerr << "\t" << strerror(error) << std::endl;
1984 timings.push_back(v / 1000.0);
1985 uris.push_back(script_line.substr(pos + 1, script_line.size()));
1991 std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
1992 size_t nreqs, size_t nclients,
1993 size_t rate, size_t max_samples) {
1994 std::stringstream rate_report;
1995 if (config.is_rate_mode() && nclients > rate) {
1996 rate_report << "Up to " << rate << " client(s) will be created every "
1997 << util::duration_str(config.rate_period) << " ";
2000 if (config.is_timing_based_mode()) {
2001 std::cout << "spawning thread #" << id << ": " << nclients
2002 << " total client(s). Timing-based test with "
2003 << config.warm_up_time << "s of warm-up time and "
2004 << config.duration << "s of main duration for measurements."
2007 std::cout << "spawning thread #" << id << ": " << nclients
2008 << " total client(s). " << rate_report.str() << nreqs
2009 << " total requests" << std::endl;
2012 if (config.is_rate_mode()) {
2013 return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate,
2014 max_samples, &config);
2016 // Here rate is same as client because the rate_timeout callback
2017 // will be called only once
2018 return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, nclients,
2019 max_samples, &config);
2025 int parse_header_table_size(uint32_t &dst, const char *opt,
2026 const char *optarg) {
2027 auto n = util::parse_uint_with_unit(optarg);
2029 std::cerr << "--" << opt << ": Bad option value: " << optarg << std::endl;
2032 if (n > std::numeric_limits<uint32_t>::max()) {
2033 std::cerr << "--" << opt
2034 << ": Value too large. It should be less than or equal to "
2035 << std::numeric_limits<uint32_t>::max() << std::endl;
2046 void print_version(std::ostream &out) {
2047 out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
2052 void print_usage(std::ostream &out) {
2053 out << R"(Usage: h2load [OPTIONS]... [URI]...
2054 benchmarking tool for HTTP/2 server)"
2060 constexpr char DEFAULT_NPN_LIST[] = "h2,h2-16,h2-14,http/1.1";
2064 void print_help(std::ostream &out) {
2067 auto config = Config();
2070 <URI> Specify URI to access. Multiple URIs can be specified.
2071 URIs are used in this order for each client. All URIs
2072 are used, then first URI is used and then 2nd URI, and
2073 so on. The scheme, host and port in the subsequent
2074 URIs, if present, are ignored. Those in the first URI
2075 are used solely. Definition of a base URI overrides all
2076 scheme, host or port values.
2079 Number of requests across all clients. If it is used
2080 with --timing-script-file option, this option specifies
2081 the number of requests each client performs rather than
2082 the number of requests across all clients. This option
2083 is ignored if timing-based benchmarking is enabled (see
2086 << config.nreqs << R"(
2088 Number of concurrent clients. With -r option, this
2089 specifies the maximum number of connections to be made.
2091 << config.nclients << R"(
2093 Number of native threads.
2095 << config.nthreads << R"(
2096 -i, --input-file=<PATH>
2097 Path of a file with multiple URIs are separated by EOLs.
2098 This option will disable URIs getting from command-line.
2099 If '-' is given as <PATH>, URIs will be read from stdin.
2100 URIs are used in this order for each client. All URIs
2101 are used, then first URI is used and then 2nd URI, and
2102 so on. The scheme, host and port in the subsequent
2103 URIs, if present, are ignored. Those in the first URI
2104 are used solely. Definition of a base URI overrides all
2105 scheme, host or port values.
2106 -m, --max-concurrent-streams=<N>
2107 Max concurrent streams to issue per session. When
2108 http/1.1 is used, this specifies the number of HTTP
2109 pipelining requests in-flight.
2111 -w, --window-bits=<N>
2112 Sets the stream level initial window size to (2**<N>)-1.
2113 For QUIC, <N> is capped to 26 (roughly 64MiB).
2115 << config.window_bits << R"(
2116 -W, --connection-window-bits=<N>
2117 Sets the connection level initial window size to
2120 << config.connection_window_bits << R"(
2121 -H, --header=<HEADER>
2122 Add/Override a header to the requests.
2124 Set allowed cipher list for TLSv1.2 or ealier. The
2125 format of the string is described in OpenSSL ciphers(1).
2127 << config.ciphers << R"(
2128 --tls13-ciphers=<SUITE>
2129 Set allowed cipher list for TLSv1.3. The format of the
2130 string is described in OpenSSL ciphers(1).
2132 << config.tls13_ciphers << R"(
2133 -p, --no-tls-proto=<PROTOID>
2134 Specify ALPN identifier of the protocol to be used when
2135 accessing http URI without SSL/TLS.
2136 Available protocols: )"
2137 << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1 << R"(
2139 << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
2141 Post FILE to server. The request method is changed to
2142 POST. For http/1.1 connection, if -d is used, the
2143 maximum number of in-flight pipelined requests is set to
2146 Specifies the fixed rate at which connections are
2147 created. The rate must be a positive integer,
2148 representing the number of connections to be made per
2149 rate period. The maximum number of connections to be
2150 made is given in -c option. This rate will be
2151 distributed among threads as evenly as possible. For
2152 example, with -t2 and -r4, each thread gets 2
2153 connections per period. When the rate is 0, the program
2154 will run as it normally does, creating connections at
2155 whatever variable rate it wants. The default value for
2156 this option is 0. -r and -D are mutually exclusive.
2157 --rate-period=<DURATION>
2158 Specifies the time period between creating connections.
2159 The period must be a positive number, representing the
2160 length of the period in time. This option is ignored if
2161 the rate option is not used. The default value for this
2163 -D, --duration=<DURATION>
2164 Specifies the main duration for the measurements in case
2165 of timing-based benchmarking. -D and -r are mutually
2167 --warm-up-time=<DURATION>
2168 Specifies the time period before starting the actual
2169 measurements, in case of timing-based benchmarking.
2170 Needs to provided along with -D option.
2171 -T, --connection-active-timeout=<DURATION>
2172 Specifies the maximum time that h2load is willing to
2173 keep a connection open, regardless of the activity on
2174 said connection. <DURATION> must be a positive integer,
2175 specifying the amount of time to wait. When no timeout
2176 value is set (either active or inactive), h2load will
2177 keep a connection open indefinitely, waiting for a
2179 -N, --connection-inactivity-timeout=<DURATION>
2180 Specifies the amount of time that h2load is willing to
2181 wait to see activity on a given connection. <DURATION>
2182 must be a positive integer, specifying the amount of
2183 time to wait. When no timeout value is set (either
2184 active or inactive), h2load will keep a connection open
2185 indefinitely, waiting for a response.
2186 --timing-script-file=<PATH>
2187 Path of a file containing one or more lines separated by
2188 EOLs. Each script line is composed of two tab-separated
2189 fields. The first field represents the time offset from
2190 the start of execution, expressed as a positive value of
2191 milliseconds with microsecond resolution. The second
2192 field represents the URI. This option will disable URIs
2193 getting from command-line. If '-' is given as <PATH>,
2194 script lines will be read from stdin. Script lines are
2195 used in order for each client. If -n is given, it must
2196 be less than or equal to the number of script lines,
2197 larger values are clamped to the number of script lines.
2198 If -n is not given, the number of requests will default
2199 to the number of script lines. The scheme, host and
2200 port defined in the first URI are used solely. Values
2201 contained in other URIs, if present, are ignored.
2202 Definition of a base URI overrides all scheme, host or
2203 port values. --timing-script-file and --rps are
2205 -B, --base-uri=(<URI>|unix:<PATH>)
2206 Specify URI from which the scheme, host and port will be
2207 used for all requests. The base URI overrides all
2208 values defined either at the command line or inside
2209 input files. If argument starts with "unix:", then the
2210 rest of the argument will be treated as UNIX domain
2211 socket path. The connection is made through that path
2212 instead of TCP. In this case, scheme is inferred from
2213 the first URI appeared in the command line or inside
2214 input files as usual.
2216 Comma delimited list of ALPN protocol identifier sorted
2217 in the order of preference. That means most desirable
2218 protocol comes first. This is used in both ALPN and
2219 NPN. The parameter must be delimited by a single comma
2220 only and any white spaces are treated as a part of
2223 << DEFAULT_NPN_LIST << R"(
2224 --h1 Short hand for --npn-list=http/1.1
2225 --no-tls-proto=http/1.1, which effectively force
2226 http/1.1 for both http and https URI.
2227 --header-table-size=<SIZE>
2228 Specify decoder header table size.
2230 << util::utos_unit(config.header_table_size) << R"(
2231 --encoder-header-table-size=<SIZE>
2232 Specify encoder header table size. The decoder (server)
2233 specifies the maximum dynamic table size it accepts.
2234 Then the negotiated dynamic table size is the minimum of
2235 this option value and the value which server specified.
2237 << util::utos_unit(config.encoder_header_table_size) << R"(
2239 Write per-request information to a file as tab-separated
2240 columns: start time as microseconds since epoch; HTTP
2241 status code; microseconds until end of response. More
2242 columns may be added later. Rows are ordered by end-of-
2243 response time when using one worker thread, but may
2244 appear slightly out of order with multiple threads due
2245 to buffering. Status code is -1 for failed streams.
2246 --qlog-file-base=<PATH>
2247 Enable qlog output and specify base file name for qlogs.
2248 Qlog is emitted for each connection.
2249 For a given base name "base", each output file name
2250 becomes "base.M.N.qlog" where M is worker ID and N is
2251 client ID (e.g. "base.0.3.qlog").
2252 Only effective in QUIC runs.
2253 --connect-to=<HOST>[:<PORT>]
2254 Host and port to connect instead of using the authority
2256 --rps=<N> Specify request per second for each client. --rps and
2257 --timing-script-file are mutually exclusive.
2259 Specify the supported groups.
2261 << config.groups << R"(
2264 --max-udp-payload-size=<SIZE>
2265 Specify the maximum outgoing UDP datagram payload size.
2267 Output debug information.
2268 --version Display version information and exit.
2269 -h, --help Display this help and exit.
2273 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2274 10 * 1024). Units are K, M and G (powers of 1024).
2276 The <DURATION> argument is an integer and an optional unit (e.g., 1s
2277 is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
2278 (hours, minutes, seconds and milliseconds, respectively). If a unit
2279 is omitted, a second is used as unit.)"
2284 int main(int argc, char **argv) {
2288 tls::LibsslGlobalLock lock;
2291 std::string datafile;
2292 std::string logfile;
2293 std::string qlog_base;
2294 bool nreqs_set_manually = false;
2296 static int flag = 0;
2297 constexpr static option long_options[] = {
2298 {"requests", required_argument, nullptr, 'n'},
2299 {"clients", required_argument, nullptr, 'c'},
2300 {"data", required_argument, nullptr, 'd'},
2301 {"threads", required_argument, nullptr, 't'},
2302 {"max-concurrent-streams", required_argument, nullptr, 'm'},
2303 {"window-bits", required_argument, nullptr, 'w'},
2304 {"connection-window-bits", required_argument, nullptr, 'W'},
2305 {"input-file", required_argument, nullptr, 'i'},
2306 {"header", required_argument, nullptr, 'H'},
2307 {"no-tls-proto", required_argument, nullptr, 'p'},
2308 {"verbose", no_argument, nullptr, 'v'},
2309 {"help", no_argument, nullptr, 'h'},
2310 {"version", no_argument, &flag, 1},
2311 {"ciphers", required_argument, &flag, 2},
2312 {"rate", required_argument, nullptr, 'r'},
2313 {"connection-active-timeout", required_argument, nullptr, 'T'},
2314 {"connection-inactivity-timeout", required_argument, nullptr, 'N'},
2315 {"duration", required_argument, nullptr, 'D'},
2316 {"timing-script-file", required_argument, &flag, 3},
2317 {"base-uri", required_argument, nullptr, 'B'},
2318 {"npn-list", required_argument, &flag, 4},
2319 {"rate-period", required_argument, &flag, 5},
2320 {"h1", no_argument, &flag, 6},
2321 {"header-table-size", required_argument, &flag, 7},
2322 {"encoder-header-table-size", required_argument, &flag, 8},
2323 {"warm-up-time", required_argument, &flag, 9},
2324 {"log-file", required_argument, &flag, 10},
2325 {"connect-to", required_argument, &flag, 11},
2326 {"rps", required_argument, &flag, 12},
2327 {"groups", required_argument, &flag, 13},
2328 {"tls13-ciphers", required_argument, &flag, 14},
2329 {"no-udp-gso", no_argument, &flag, 15},
2330 {"qlog-file-base", required_argument, &flag, 16},
2331 {"max-udp-payload-size", required_argument, &flag, 17},
2332 {nullptr, 0, nullptr, 0}};
2333 int option_index = 0;
2334 auto c = getopt_long(argc, argv,
2335 "hvW:c:d:m:n:p:t:w:H:i:r:T:N:D:B:", long_options,
2342 config.nreqs = strtoul(optarg, nullptr, 10);
2343 nreqs_set_manually = true;
2346 config.nclients = strtoul(optarg, nullptr, 10);
2353 std::cerr << "-t: WARNING: Threading disabled at build time, "
2354 << "no threads created." << std::endl;
2356 config.nthreads = strtoul(optarg, nullptr, 10);
2360 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
2365 char *endptr = nullptr;
2366 auto n = strtoul(optarg, &endptr, 10);
2367 if (errno == 0 && *endptr == '\0' && n < 31) {
2369 config.window_bits = n;
2371 config.connection_window_bits = n;
2374 std::cerr << "-" << static_cast<char>(c)
2375 << ": specify the integer in the range [0, 30], inclusive"
2382 char *header = optarg;
2383 // Skip first possible ':' in the header name
2384 char *value = strchr(optarg + 1, ':');
2385 if (!value || (header[0] == ':' && header + 1 == value)) {
2386 std::cerr << "-H: invalid header: " << optarg << std::endl;
2391 while (isspace(*value)) {
2395 // This could also be a valid case for suppressing a header
2397 std::cerr << "-H: invalid header - value missing: " << optarg
2401 // Note that there is no processing currently to handle multiple
2402 // message-header fields with the same field name
2403 config.custom_headers.emplace_back(header, value);
2404 util::inp_strlower(config.custom_headers.back().name);
2408 config.ifile = optarg;
2411 auto proto = StringRef{optarg};
2412 if (util::strieq(StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID),
2414 config.no_tls_proto = Config::PROTO_HTTP2;
2415 } else if (util::strieq(NGHTTP2_H1_1, proto)) {
2416 config.no_tls_proto = Config::PROTO_HTTP1_1;
2418 std::cerr << "-p: unsupported protocol " << proto << std::endl;
2424 config.rate = strtoul(optarg, nullptr, 10);
2425 if (config.rate == 0) {
2426 std::cerr << "-r: the rate at which connections are made "
2427 << "must be positive." << std::endl;
2432 config.conn_active_timeout = util::parse_duration_with_unit(optarg);
2433 if (!std::isfinite(config.conn_active_timeout)) {
2434 std::cerr << "-T: bad value for the conn_active_timeout wait time: "
2435 << optarg << std::endl;
2440 config.conn_inactivity_timeout = util::parse_duration_with_unit(optarg);
2441 if (!std::isfinite(config.conn_inactivity_timeout)) {
2442 std::cerr << "-N: bad value for the conn_inactivity_timeout wait time: "
2443 << optarg << std::endl;
2448 auto arg = StringRef{optarg};
2449 config.base_uri = "";
2450 config.base_uri_unix = false;
2452 if (util::istarts_with_l(arg, UNIX_PATH_PREFIX)) {
2453 // UNIX domain socket path
2456 auto path = StringRef{std::begin(arg) + str_size(UNIX_PATH_PREFIX),
2459 if (path.size() == 0 || path.size() + 1 > sizeof(un.sun_path)) {
2460 std::cerr << "--base-uri: invalid UNIX domain socket path: " << arg
2465 config.base_uri_unix = true;
2467 auto &unix_addr = config.unix_addr;
2468 std::copy(std::begin(path), std::end(path), unix_addr.sun_path);
2469 unix_addr.sun_path[path.size()] = '\0';
2470 unix_addr.sun_family = AF_UNIX;
2475 if (!parse_base_uri(arg)) {
2476 std::cerr << "--base-uri: invalid base URI: " << arg << std::endl;
2480 config.base_uri = arg.str();
2484 config.duration = util::parse_duration_with_unit(optarg);
2485 if (!std::isfinite(config.duration)) {
2486 std::cerr << "-D: value error " << optarg << std::endl;
2491 config.verbose = true;
2494 print_help(std::cout);
2497 util::show_candidates(argv[optind - 1], long_options);
2503 print_version(std::cout);
2507 config.ciphers = optarg;
2510 // timing-script option
2511 config.ifile = optarg;
2512 config.timing_script = true;
2516 config.npn_list = util::parse_config_str_list(StringRef{optarg});
2520 config.rate_period = util::parse_duration_with_unit(optarg);
2521 if (!std::isfinite(config.rate_period)) {
2522 std::cerr << "--rate-period: value error " << optarg << std::endl;
2529 util::parse_config_str_list(StringRef::from_lit("http/1.1"));
2530 config.no_tls_proto = Config::PROTO_HTTP1_1;
2533 // --header-table-size
2534 if (parse_header_table_size(config.header_table_size,
2535 "header-table-size", optarg) != 0) {
2540 // --encoder-header-table-size
2541 if (parse_header_table_size(config.encoder_header_table_size,
2542 "encoder-header-table-size", optarg) != 0) {
2548 config.warm_up_time = util::parse_duration_with_unit(optarg);
2549 if (!std::isfinite(config.warm_up_time)) {
2550 std::cerr << "--warm-up-time: value error " << optarg << std::endl;
2560 auto p = util::split_hostport(StringRef{optarg});
2562 if (p.first.empty() ||
2563 (!p.second.empty() && (port = util::parse_uint(p.second)) == -1)) {
2564 std::cerr << "--connect-to: Invalid value " << optarg << std::endl;
2567 config.connect_to_host = p.first.str();
2568 config.connect_to_port = port;
2573 auto v = std::strtod(optarg, &end);
2574 if (end == optarg || *end != '\0' || !std::isfinite(v) ||
2576 std::cerr << "--rps: Invalid value " << optarg << std::endl;
2584 config.groups = optarg;
2588 config.tls13_ciphers = optarg;
2592 config.no_udp_gso = true;
2599 // --max-udp-payload-size
2600 auto n = util::parse_uint_with_unit(optarg);
2602 std::cerr << "--max-udp-payload-size: bad option value: " << optarg
2606 if (static_cast<uint64_t>(n) > 64_k) {
2607 std::cerr << "--max-udp-payload-size: must not exceed 65536"
2611 config.max_udp_payload_size = n;
2621 if (argc == optind) {
2622 if (config.ifile.empty()) {
2623 std::cerr << "no URI or input file given" << std::endl;
2628 if (config.nclients == 0) {
2629 std::cerr << "-c: the number of clients must be strictly greater than 0."
2634 if (config.npn_list.empty()) {
2636 util::parse_config_str_list(StringRef::from_lit(DEFAULT_NPN_LIST));
2639 // serialize the APLN tokens
2640 for (auto &proto : config.npn_list) {
2641 proto.insert(proto.begin(), static_cast<unsigned char>(proto.size()));
2644 std::vector<std::string> reqlines;
2646 if (config.ifile.empty()) {
2647 std::vector<std::string> uris;
2648 std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
2649 reqlines = parse_uris(std::begin(uris), std::end(uris));
2651 std::vector<std::string> uris;
2652 if (!config.timing_script) {
2653 if (config.ifile == "-") {
2654 uris = read_uri_from_file(std::cin);
2656 std::ifstream infile(config.ifile);
2658 std::cerr << "cannot read input file: " << config.ifile << std::endl;
2662 uris = read_uri_from_file(infile);
2665 if (config.ifile == "-") {
2666 read_script_from_file(std::cin, config.timings, uris);
2668 std::ifstream infile(config.ifile);
2670 std::cerr << "cannot read input file: " << config.ifile << std::endl;
2674 read_script_from_file(infile, config.timings, uris);
2677 if (nreqs_set_manually) {
2678 if (config.nreqs > uris.size()) {
2679 std::cerr << "-n: the number of requests must be less than or equal "
2680 "to the number of timing script entries. Setting number "
2682 << uris.size() << std::endl;
2684 config.nreqs = uris.size();
2687 config.nreqs = uris.size();
2691 reqlines = parse_uris(std::begin(uris), std::end(uris));
2694 if (reqlines.empty()) {
2695 std::cerr << "No URI given" << std::endl;
2699 if (config.is_timing_based_mode() && config.is_rate_mode()) {
2700 std::cerr << "-r, -D: they are mutually exclusive." << std::endl;
2704 if (config.timing_script && config.rps_enabled()) {
2705 std::cerr << "--timing-script-file, --rps: they are mutually exclusive."
2710 if (config.nreqs == 0 && !config.is_timing_based_mode()) {
2711 std::cerr << "-n: the number of requests must be strictly greater than 0 "
2712 "if timing-based test is not being run."
2717 if (config.max_concurrent_streams == 0) {
2718 std::cerr << "-m: the max concurrent streams must be strictly greater "
2719 << "than 0." << std::endl;
2723 if (config.nthreads == 0) {
2724 std::cerr << "-t: the number of threads must be strictly greater than 0."
2729 if (config.nthreads > std::thread::hardware_concurrency()) {
2730 std::cerr << "-t: warning: the number of threads is greater than hardware "
2731 << "cores." << std::endl;
2734 // With timing script, we don't distribute config.nreqs to each
2735 // client or thread.
2736 if (!config.timing_script && config.nreqs < config.nclients &&
2737 !config.is_timing_based_mode()) {
2738 std::cerr << "-n, -c: the number of requests must be greater than or "
2739 << "equal to the clients." << std::endl;
2743 if (config.nclients < config.nthreads) {
2744 std::cerr << "-c, -t: the number of clients must be greater than or equal "
2745 << "to the number of threads." << std::endl;
2749 if (config.is_timing_based_mode()) {
2753 if (config.is_rate_mode()) {
2754 if (config.rate < config.nthreads) {
2755 std::cerr << "-r, -t: the connection rate must be greater than or equal "
2756 << "to the number of threads." << std::endl;
2760 if (config.rate > config.nclients) {
2761 std::cerr << "-r, -c: the connection rate must be smaller than or equal "
2762 "to the number of clients."
2768 if (!datafile.empty()) {
2769 config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
2770 if (config.data_fd == -1) {
2771 std::cerr << "-d: Could not open file " << datafile << std::endl;
2774 struct stat data_stat;
2775 if (fstat(config.data_fd, &data_stat) == -1) {
2776 std::cerr << "-d: Could not stat file " << datafile << std::endl;
2779 config.data_length = data_stat.st_size;
2780 auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED,
2782 if (addr == MAP_FAILED) {
2783 std::cerr << "-d: Could not mmap file " << datafile << std::endl;
2786 config.data = static_cast<uint8_t *>(addr);
2789 if (!logfile.empty()) {
2790 config.log_fd = open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND,
2791 S_IRUSR | S_IWUSR | S_IRGRP);
2792 if (config.log_fd == -1) {
2793 std::cerr << "--log-file: Could not open file " << logfile << std::endl;
2798 if (!qlog_base.empty()) {
2799 if (!config.is_quic()) {
2801 << "Warning: --qlog-file-base: only effective in quic, ignoring."
2805 config.qlog_file_base = qlog_base;
2806 #endif // ENABLE_HTTP3
2810 struct sigaction act {};
2811 act.sa_handler = SIG_IGN;
2812 sigaction(SIGPIPE, &act, nullptr);
2814 auto ssl_ctx = SSL_CTX_new(TLS_client_method());
2816 std::cerr << "Failed to create SSL_CTX: "
2817 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2821 auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2822 SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2823 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2825 SSL_CTX_set_options(ssl_ctx, ssl_opts);
2826 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2827 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2829 if (config.is_quic()) {
2831 SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
2832 SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
2833 #endif // ENABLE_HTTP3
2834 } else if (nghttp2::tls::ssl_ctx_set_proto_versions(
2835 ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2836 nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2837 std::cerr << "Could not set TLS versions" << std::endl;
2841 if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) {
2842 std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers
2843 << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
2848 #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
2849 if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) {
2850 std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers
2851 << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
2855 #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
2857 #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
2858 if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) {
2859 std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
2862 #else // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
2863 if (SSL_CTX_set1_curves_list(ssl_ctx, config.groups.c_str()) != 1) {
2864 std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl;
2867 #endif // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
2869 #ifndef OPENSSL_NO_NEXTPROTONEG
2870 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2872 #endif // !OPENSSL_NO_NEXTPROTONEG
2874 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2875 std::vector<unsigned char> proto_list;
2876 for (const auto &proto : config.npn_list) {
2877 std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list));
2880 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2881 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2883 #if OPENSSL_1_1_1_API
2884 auto keylog_filename = getenv("SSLKEYLOGFILE");
2885 if (keylog_filename) {
2886 keylog_file.open(keylog_filename, std::ios_base::app);
2888 SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
2891 #endif // OPENSSL_1_1_1_API
2893 std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
2895 shared_nva.emplace_back(":scheme", config.scheme);
2896 if (config.port != config.default_port) {
2897 shared_nva.emplace_back(":authority",
2898 config.host + ":" + util::utos(config.port));
2900 shared_nva.emplace_back(":authority", config.host);
2902 shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
2903 shared_nva.emplace_back("user-agent", user_agent);
2905 // list header fields that can be overridden.
2906 auto override_hdrs = make_array<std::string>(":authority", ":host", ":method",
2907 ":scheme", "user-agent");
2909 for (auto &kv : config.custom_headers) {
2910 if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
2911 kv.name) != std::end(override_hdrs)) {
2913 for (auto &nv : shared_nva) {
2914 if ((nv.name == ":authority" && kv.name == ":host") ||
2915 (nv.name == kv.name)) {
2916 nv.value = kv.value;
2920 // add additional headers
2921 shared_nva.push_back(kv);
2925 std::string content_length_str;
2926 if (config.data_fd != -1) {
2927 content_length_str = util::utos(config.data_length);
2931 std::find_if(std::begin(shared_nva), std::end(shared_nva),
2932 [](const Header &nv) { return nv.name == ":method"; });
2933 assert(method_it != std::end(shared_nva));
2935 config.h1reqs.reserve(reqlines.size());
2936 config.nva.reserve(reqlines.size());
2938 for (auto &req : reqlines) {
2940 auto h1req = (*method_it).value;
2943 h1req += " HTTP/1.1\r\n";
2944 for (auto &nv : shared_nva) {
2945 if (nv.name == ":authority") {
2951 if (nv.name[0] == ':') {
2960 if (!content_length_str.empty()) {
2961 h1req += "Content-Length: ";
2962 h1req += content_length_str;
2967 config.h1reqs.push_back(std::move(h1req));
2970 std::vector<nghttp2_nv> nva;
2971 // 2 for :path, and possible content-length
2972 nva.reserve(2 + shared_nva.size());
2974 nva.push_back(http2::make_nv_ls(":path", req));
2976 for (auto &nv : shared_nva) {
2977 nva.push_back(http2::make_nv(nv.name, nv.value, false));
2980 if (!content_length_str.empty()) {
2981 nva.push_back(http2::make_nv(StringRef::from_lit("content-length"),
2982 StringRef{content_length_str}));
2985 config.nva.push_back(std::move(nva));
2988 // Don't DOS our server!
2989 if (config.host == "nghttp2.org") {
2990 std::cerr << "Using h2load against public server " << config.host
2991 << " should be prohibited." << std::endl;
2997 std::cout << "starting benchmark..." << std::endl;
2999 std::vector<std::unique_ptr<Worker>> workers;
3000 workers.reserve(config.nthreads);
3003 size_t nreqs_per_thread = 0;
3004 ssize_t nreqs_rem = 0;
3006 if (!config.timing_script) {
3007 nreqs_per_thread = config.nreqs / config.nthreads;
3008 nreqs_rem = config.nreqs % config.nthreads;
3011 size_t nclients_per_thread = config.nclients / config.nthreads;
3012 ssize_t nclients_rem = config.nclients % config.nthreads;
3014 size_t rate_per_thread = config.rate / config.nthreads;
3015 ssize_t rate_per_thread_rem = config.rate % config.nthreads;
3017 size_t max_samples_per_thread =
3018 std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
3021 std::condition_variable cv;
3024 std::vector<std::future<void>> futures;
3025 for (size_t i = 0; i < config.nthreads; ++i) {
3026 auto rate = rate_per_thread;
3027 if (rate_per_thread_rem > 0) {
3028 --rate_per_thread_rem;
3031 auto nclients = nclients_per_thread;
3032 if (nclients_rem > 0) {
3038 if (config.timing_script) {
3039 // With timing script, each client issues config.nreqs requests.
3040 // We divide nreqs by number of clients in Worker ctor to
3041 // distribute requests to those clients evenly, so multiply
3042 // config.nreqs here by config.nclients.
3043 nreqs = config.nreqs * nclients;
3045 nreqs = nreqs_per_thread;
3046 if (nreqs_rem > 0) {
3052 workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate,
3053 max_samples_per_thread));
3054 auto &worker = workers.back();
3056 std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
3058 std::unique_lock<std::mutex> ulk(mu);
3059 cv.wait(ulk, [&ready] { return ready; });
3066 std::lock_guard<std::mutex> lg(mu);
3071 auto start = std::chrono::steady_clock::now();
3073 for (auto &fut : futures) {
3078 auto rate = config.rate;
3079 auto nclients = config.nclients;
3081 config.timing_script ? config.nreqs * config.nclients : config.nreqs;
3084 create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
3086 auto start = std::chrono::steady_clock::now();
3088 workers.back()->run();
3091 auto end = std::chrono::steady_clock::now();
3093 std::chrono::duration_cast<std::chrono::microseconds>(end - start);
3096 for (const auto &w : workers) {
3097 const auto &s = w->stats;
3099 stats.req_todo += s.req_todo;
3100 stats.req_started += s.req_started;
3101 stats.req_done += s.req_done;
3102 stats.req_timedout += s.req_timedout;
3103 stats.req_success += s.req_success;
3104 stats.req_status_success += s.req_status_success;
3105 stats.req_failed += s.req_failed;
3106 stats.req_error += s.req_error;
3107 stats.bytes_total += s.bytes_total;
3108 stats.bytes_head += s.bytes_head;
3109 stats.bytes_head_decomp += s.bytes_head_decomp;
3110 stats.bytes_body += s.bytes_body;
3111 stats.udp_dgram_recv += s.udp_dgram_recv;
3112 stats.udp_dgram_sent += s.udp_dgram_sent;
3114 for (size_t i = 0; i < stats.status.size(); ++i) {
3115 stats.status[i] += s.status[i];
3119 auto ts = process_time_stats(workers);
3121 // Requests which have not been issued due to connection errors, are
3122 // counted towards req_failed and req_error.
3123 auto req_not_issued =
3124 (stats.req_todo - stats.req_status_success - stats.req_failed);
3125 stats.req_failed += req_not_issued;
3126 stats.req_error += req_not_issued;
3128 // UI is heavily inspired by weighttp[1] and wrk[2]
3130 // [1] https://github.com/lighttpd/weighttp
3131 // [2] https://github.com/wg/wrk
3134 if (duration.count() > 0) {
3135 if (config.is_timing_based_mode()) {
3136 // we only want to consider the main duration if warm-up is given
3137 rps = stats.req_success / config.duration;
3138 bps = stats.bytes_total / config.duration;
3140 auto secd = std::chrono::duration_cast<
3141 std::chrono::duration<double, std::chrono::seconds::period>>(
3143 rps = stats.req_success / secd.count();
3144 bps = stats.bytes_total / secd.count();
3148 double header_space_savings = 0.;
3149 if (stats.bytes_head_decomp > 0) {
3150 header_space_savings =
3151 1. - static_cast<double>(stats.bytes_head) / stats.bytes_head_decomp;
3154 std::cout << std::fixed << std::setprecision(2) << R"(
3156 << util::format_duration(duration) << ", " << rps << " req/s, "
3157 << util::utos_funit(bps) << R"(B/s
3158 requests: )" << stats.req_todo
3159 << " total, " << stats.req_started << " started, " << stats.req_done
3160 << " done, " << stats.req_status_success << " succeeded, "
3161 << stats.req_failed << " failed, " << stats.req_error
3162 << " errored, " << stats.req_timedout << R"( timeout
3164 << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
3165 << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
3166 traffic: )" << util::utos_funit(stats.bytes_total)
3167 << "B (" << stats.bytes_total << ") total, "
3168 << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
3169 << ") headers (space savings " << header_space_savings * 100
3170 << "%), " << util::utos_funit(stats.bytes_body) << "B ("
3171 << stats.bytes_body << R"() data)" << std::endl;
3173 if (config.is_quic()) {
3174 std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, "
3175 << stats.udp_dgram_recv << " received" << std::endl;
3177 #endif // ENABLE_HTTP3
3179 << R"( min max mean sd +/- sd
3180 time for request: )"
3181 << std::setw(10) << util::format_duration(ts.request.min) << " "
3182 << std::setw(10) << util::format_duration(ts.request.max) << " "
3183 << std::setw(10) << util::format_duration(ts.request.mean) << " "
3184 << std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9)
3185 << util::dtos(ts.request.within_sd) << "%"
3186 << "\ntime for connect: " << std::setw(10)
3187 << util::format_duration(ts.connect.min) << " " << std::setw(10)
3188 << util::format_duration(ts.connect.max) << " " << std::setw(10)
3189 << util::format_duration(ts.connect.mean) << " " << std::setw(10)
3190 << util::format_duration(ts.connect.sd) << std::setw(9)
3191 << util::dtos(ts.connect.within_sd) << "%"
3192 << "\ntime to 1st byte: " << std::setw(10)
3193 << util::format_duration(ts.ttfb.min) << " " << std::setw(10)
3194 << util::format_duration(ts.ttfb.max) << " " << std::setw(10)
3195 << util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
3196 << util::format_duration(ts.ttfb.sd) << std::setw(9)
3197 << util::dtos(ts.ttfb.within_sd) << "%"
3198 << "\nreq/s : " << std::setw(10) << ts.rps.min << " "
3199 << std::setw(10) << ts.rps.max << " " << std::setw(10) << ts.rps.mean
3200 << " " << std::setw(10) << ts.rps.sd << std::setw(9)
3201 << util::dtos(ts.rps.within_sd) << "%" << std::endl;
3203 SSL_CTX_free(ssl_ctx);
3205 if (config.log_fd != -1) {
3206 close(config.log_fd);
3212 } // namespace h2load
3214 int main(int argc, char **argv) { return h2load::main(argc, argv); }