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 <openssl/err.h>
51 #include "url-parser/url_parser.h"
53 #include "h2load_http1_session.h"
54 #include "h2load_http2_session.h"
64 using namespace nghttp2;
69 bool recorded(const std::chrono::steady_clock::time_point &t) {
70 return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
75 : ciphers(tls::DEFAULT_CIPHER_LIST),
81 max_concurrent_streams(1),
83 connection_window_bits(30),
88 conn_active_timeout(0.),
89 conn_inactivity_timeout(0.),
90 no_tls_proto(PROTO_HTTP2),
91 header_table_size(4_k),
92 encoder_header_table_size(4_k),
100 base_uri_unix(false),
117 bool Config::is_rate_mode() const { return (this->rate != 0); }
118 bool Config::is_timing_based_mode() const { return (this->duration > 0); }
119 bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
123 constexpr size_t MAX_SAMPLES = 1000000;
126 Stats::Stats(size_t req_todo, size_t nclients)
127 : req_todo(req_todo),
131 req_status_success(0),
137 bytes_head_decomp(0),
141 Stream::Stream() : req_stat{}, status_success(-1) {}
144 std::random_device rd;
148 std::mt19937 gen(rd());
152 void sampling_init(Sampling &smp, size_t max_samples) {
154 smp.max_samples = max_samples;
159 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
160 auto client = static_cast<Client *>(w->data);
161 client->restart_timeout();
162 auto rv = client->do_write();
163 if (rv == Client::ERR_CONNECT_FAIL) {
164 client->disconnect();
166 client->current_addr = nullptr;
167 rv = client->connect();
170 client->worker->free_client(client);
178 client->worker->free_client(client);
185 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
186 auto client = static_cast<Client *>(w->data);
187 client->restart_timeout();
188 if (client->do_read() != 0) {
189 if (client->try_again_or_fail() == 0) {
192 client->worker->free_client(client);
196 writecb(loop, &client->wev, revents);
197 // client->disconnect() and client->fail() may be called
202 // Called every rate_period when rate mode is being used
203 void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
204 auto worker = static_cast<Worker *>(w->data);
205 auto nclients_per_second = worker->rate;
206 auto conns_remaining = worker->nclients - worker->nconns_made;
207 auto nclients = std::min(nclients_per_second, conns_remaining);
209 for (size_t i = 0; i < nclients; ++i) {
210 auto req_todo = worker->nreqs_per_client;
211 if (worker->nreqs_rem > 0) {
216 std::make_unique<Client>(worker->next_client_id++, worker, req_todo);
218 ++worker->nconns_made;
220 if (client->connect() != 0) {
221 std::cerr << "client could not connect to host" << std::endl;
224 if (worker->config->is_timing_based_mode()) {
225 worker->clients.push_back(client.release());
230 worker->report_rate_progress();
232 if (!worker->config->is_timing_based_mode()) {
233 if (worker->nconns_made >= worker->nclients) {
234 ev_timer_stop(worker->loop, w);
237 // To check whether all created clients are pushed correctly
238 assert(worker->nclients == worker->clients.size());
244 // Called when the duration for infinite number of requests are over
245 void duration_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
246 auto worker = static_cast<Worker *>(w->data);
248 worker->current_phase = Phase::DURATION_OVER;
250 std::cout << "Main benchmark duration is over for thread #" << worker->id
251 << ". Stopping all clients." << std::endl;
252 worker->stop_all_clients();
253 std::cout << "Stopped all clients for thread #" << worker->id << std::endl;
258 // Called when the warmup duration for infinite number of requests are over
259 void warmup_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
260 auto worker = static_cast<Worker *>(w->data);
262 std::cout << "Warm-up phase is over for thread #" << worker->id << "."
264 std::cout << "Main benchmark duration is started for thread #" << worker->id
266 assert(worker->stats.req_started == 0);
267 assert(worker->stats.req_done == 0);
269 for (auto client : worker->clients) {
271 assert(client->req_todo == 0);
272 assert(client->req_left == 1);
273 assert(client->req_inflight == 0);
274 assert(client->req_started == 0);
275 assert(client->req_done == 0);
277 client->record_client_start_time();
278 client->clear_connect_times();
279 client->record_connect_start_time();
283 worker->current_phase = Phase::MAIN_DURATION;
285 ev_timer_start(worker->loop, &worker->duration_watcher);
290 // Called when an a connection has been inactive for a set period of time
291 // or a fixed amount of time after all requests have been made on a
293 void conn_timeout_cb(EV_P_ ev_timer *w, int revents) {
294 auto client = static_cast<Client *>(w->data);
296 ev_timer_stop(client->worker->loop, &client->conn_inactivity_watcher);
297 ev_timer_stop(client->worker->loop, &client->conn_active_watcher);
299 if (util::check_socket_connected(client->fd)) {
306 bool check_stop_client_request_timeout(Client *client, ev_timer *w) {
307 if (client->req_left == 0) {
308 // no more requests to make, stop timer
309 ev_timer_stop(client->worker->loop, w);
318 void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
319 auto client = static_cast<Client *>(w->data);
321 if (client->streams.size() >= (size_t)config.max_concurrent_streams) {
322 ev_timer_stop(client->worker->loop, w);
326 if (client->submit_request() != 0) {
327 ev_timer_stop(client->worker->loop, w);
328 client->process_request_failure();
331 client->signal_write();
333 if (check_stop_client_request_timeout(client, w)) {
338 config.timings[client->reqidx] - config.timings[client->reqidx - 1];
340 while (duration < 1e-9) {
341 if (client->submit_request() != 0) {
342 ev_timer_stop(client->worker->loop, w);
343 client->process_request_failure();
346 client->signal_write();
347 if (check_stop_client_request_timeout(client, w)) {
352 config.timings[client->reqidx] - config.timings[client->reqidx - 1];
355 client->request_timeout_watcher.repeat = duration;
356 ev_timer_again(client->worker->loop, &client->request_timeout_watcher);
360 Client::Client(uint32_t id, Worker *worker, size_t req_todo)
361 : wb(&worker->mcpool),
365 next_addr(config.addrs),
366 current_addr(nullptr),
376 new_connection_requested(false),
378 if (req_todo == 0) { // this means infinite number of requests are to be made
379 // This ensures that number of requests are unbounded
380 // Just a positive number is fine, we chose the first positive number
383 ev_io_init(&wev, writecb, 0, EV_WRITE);
384 ev_io_init(&rev, readcb, 0, EV_READ);
389 ev_timer_init(&conn_inactivity_watcher, conn_timeout_cb, 0.,
390 worker->config->conn_inactivity_timeout);
391 conn_inactivity_watcher.data = this;
393 ev_timer_init(&conn_active_watcher, conn_timeout_cb,
394 worker->config->conn_active_timeout, 0.);
395 conn_active_watcher.data = this;
397 ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.);
398 request_timeout_watcher.data = this;
408 worker->sample_client_stat(&cstat);
409 ++worker->client_smp.n;
412 int Client::do_read() { return readfn(*this); }
413 int Client::do_write() { return writefn(*this); }
415 int Client::make_socket(addrinfo *addr) {
416 fd = util::create_nonblock_socket(addr->ai_family);
420 if (config.scheme == "https") {
422 ssl = SSL_new(worker->ssl_ctx);
425 auto config = worker->config;
427 if (!util::numeric_host(config->host.c_str())) {
428 SSL_set_tlsext_host_name(ssl, config->host.c_str());
432 SSL_set_connect_state(ssl);
435 auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
436 if (rv != 0 && errno != EINPROGRESS) {
448 int Client::connect() {
451 if (!worker->config->is_timing_based_mode() ||
452 worker->current_phase == Phase::MAIN_DURATION) {
453 record_client_start_time();
454 clear_connect_times();
455 record_connect_start_time();
456 } else if (worker->current_phase == Phase::INITIAL_IDLE) {
457 worker->current_phase = Phase::WARM_UP;
458 std::cout << "Warm-up started for thread #" << worker->id << "."
460 ev_timer_start(worker->loop, &worker->warmup_watcher);
463 if (worker->config->conn_inactivity_timeout > 0.) {
464 ev_timer_again(worker->loop, &conn_inactivity_watcher);
468 rv = make_socket(current_addr);
473 addrinfo *addr = nullptr;
476 next_addr = next_addr->ai_next;
477 rv = make_socket(addr);
492 writefn = &Client::connected;
494 ev_io_set(&rev, fd, EV_READ);
495 ev_io_set(&wev, fd, EV_WRITE);
497 ev_io_start(worker->loop, &wev);
502 void Client::timeout() {
503 process_timedout_streams();
508 void Client::restart_timeout() {
509 if (worker->config->conn_inactivity_timeout > 0.) {
510 ev_timer_again(worker->loop, &conn_inactivity_watcher);
514 int Client::try_again_or_fail() {
517 if (new_connection_requested) {
518 new_connection_requested = false;
522 if (worker->current_phase == Phase::MAIN_DURATION) {
523 // At the moment, we don't have a facility to re-start request
524 // already in in-flight. Make them fail.
525 worker->stats.req_failed += req_inflight;
526 worker->stats.req_error += req_inflight;
531 // Keep using current address
532 if (connect() == 0) {
535 std::cerr << "client could not connect to host" << std::endl;
539 process_abandoned_streams();
544 void Client::fail() {
547 process_abandoned_streams();
550 void Client::disconnect() {
551 record_client_end_time();
553 ev_timer_stop(worker->loop, &conn_inactivity_watcher);
554 ev_timer_stop(worker->loop, &conn_active_watcher);
555 ev_timer_stop(worker->loop, &request_timeout_watcher);
560 ev_io_stop(worker->loop, &wev);
561 ev_io_stop(worker->loop, &rev);
563 SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
566 if (SSL_shutdown(ssl) != 1) {
572 shutdown(fd, SHUT_WR);
580 int Client::submit_request() {
581 if (session->submit_request() != 0) {
585 if (worker->current_phase != Phase::MAIN_DURATION) {
589 ++worker->stats.req_started;
592 if (!worker->config->is_timing_based_mode()) {
595 // if an active timeout is set and this is the last request to be submitted
596 // on this connection, start the active timeout.
597 if (worker->config->conn_active_timeout > 0. && req_left == 0) {
598 ev_timer_start(worker->loop, &conn_active_watcher);
604 void Client::process_timedout_streams() {
605 if (worker->current_phase != Phase::MAIN_DURATION) {
609 for (auto &p : streams) {
610 auto &req_stat = p.second.req_stat;
611 if (!req_stat.completed) {
612 req_stat.stream_close_time = std::chrono::steady_clock::now();
616 worker->stats.req_timedout += req_inflight;
618 process_abandoned_streams();
621 void Client::process_abandoned_streams() {
622 if (worker->current_phase != Phase::MAIN_DURATION) {
626 auto req_abandoned = req_inflight + req_left;
628 worker->stats.req_failed += req_abandoned;
629 worker->stats.req_error += req_abandoned;
635 void Client::process_request_failure() {
636 if (worker->current_phase != Phase::MAIN_DURATION) {
640 worker->stats.req_failed += req_left;
641 worker->stats.req_error += req_left;
645 if (req_inflight == 0) {
648 std::cout << "Process Request Failure:" << worker->stats.req_failed
653 void print_server_tmp_key(SSL *ssl) {
654 // libressl does not have SSL_get_server_tmp_key
655 #if OPENSSL_VERSION_NUMBER >= 0x10002000L && defined(SSL_get_server_tmp_key)
658 if (!SSL_get_server_tmp_key(ssl, &key)) {
662 auto key_del = defer(EVP_PKEY_free, key);
664 std::cout << "Server Temp Key: ";
666 auto pkey_id = EVP_PKEY_id(key);
669 std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
672 std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
675 auto ec = EVP_PKEY_get1_EC_KEY(key);
676 auto ec_del = defer(EC_KEY_free, ec);
677 auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
678 auto cname = EC_curve_nid2nist(nid);
680 cname = OBJ_nid2sn(nid);
683 std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
688 std::cout << OBJ_nid2sn(pkey_id) << " " << EVP_PKEY_bits(key) << " bits"
692 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
696 void Client::report_tls_info() {
697 if (worker->id == 0 && !worker->tls_info_report_done) {
698 worker->tls_info_report_done = true;
699 auto cipher = SSL_get_current_cipher(ssl);
700 std::cout << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n"
701 << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
702 print_server_tmp_key(ssl);
706 void Client::report_app_info() {
707 if (worker->id == 0 && !worker->app_info_report_done) {
708 worker->app_info_report_done = true;
709 std::cout << "Application protocol: " << selected_proto << std::endl;
713 void Client::terminate_session() {
714 session->terminate();
715 // http1 session needs writecb to tear down session.
719 void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
721 void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
722 const uint8_t *value, size_t valuelen) {
723 auto itr = streams.find(stream_id);
724 if (itr == std::end(streams)) {
727 auto &stream = (*itr).second;
729 if (worker->current_phase != Phase::MAIN_DURATION) {
730 // If the stream is for warm-up phase, then mark as a success
731 // But we do not update the count for 2xx, 3xx, etc status codes
732 // Same has been done in on_status_code function
733 stream.status_success = 1;
737 if (stream.status_success == -1 && namelen == 7 &&
738 util::streq_l(":status", name, namelen)) {
740 for (size_t i = 0; i < valuelen; ++i) {
741 if ('0' <= value[i] && value[i] <= '9') {
743 status += value[i] - '0';
745 stream.status_success = 0;
753 stream.req_stat.status = status;
754 if (status >= 200 && status < 300) {
755 ++worker->stats.status[2];
756 stream.status_success = 1;
757 } else if (status < 400) {
758 ++worker->stats.status[3];
759 stream.status_success = 1;
760 } else if (status < 600) {
761 ++worker->stats.status[status / 100];
762 stream.status_success = 0;
764 stream.status_success = 0;
769 void Client::on_status_code(int32_t stream_id, uint16_t status) {
770 auto itr = streams.find(stream_id);
771 if (itr == std::end(streams)) {
774 auto &stream = (*itr).second;
776 if (worker->current_phase != Phase::MAIN_DURATION) {
777 stream.status_success = 1;
781 stream.req_stat.status = status;
782 if (status >= 200 && status < 300) {
783 ++worker->stats.status[2];
784 stream.status_success = 1;
785 } else if (status < 400) {
786 ++worker->stats.status[3];
787 stream.status_success = 1;
788 } else if (status < 600) {
789 ++worker->stats.status[status / 100];
790 stream.status_success = 0;
792 stream.status_success = 0;
796 void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
797 if (worker->current_phase == Phase::MAIN_DURATION) {
798 if (req_inflight > 0) {
801 auto req_stat = get_req_stat(stream_id);
806 req_stat->stream_close_time = std::chrono::steady_clock::now();
808 req_stat->completed = true;
809 ++worker->stats.req_success;
812 if (streams[stream_id].status_success == 1) {
813 ++worker->stats.req_status_success;
815 ++worker->stats.req_failed;
818 worker->sample_req_stat(req_stat);
820 // Count up in successful cases only
821 ++worker->request_times_smp.n;
823 ++worker->stats.req_failed;
824 ++worker->stats.req_error;
826 ++worker->stats.req_done;
829 if (worker->config->log_fd != -1) {
830 auto start = std::chrono::duration_cast<std::chrono::microseconds>(
831 req_stat->request_wall_time.time_since_epoch());
832 auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
833 req_stat->stream_close_time - req_stat->request_time);
835 std::array<uint8_t, 256> buf;
836 auto p = std::begin(buf);
837 p = util::utos(p, start.count());
840 p = util::utos(p, req_stat->status);
846 p = util::utos(p, delta.count());
849 auto nwrite = static_cast<size_t>(std::distance(std::begin(buf), p));
850 assert(nwrite <= buf.size());
851 while (write(worker->config->log_fd, buf.data(), nwrite) == -1 &&
857 worker->report_progress();
858 streams.erase(stream_id);
859 if (req_left == 0 && req_inflight == 0) {
864 if (!final && req_left > 0) {
865 if (config.timing_script) {
866 if (!ev_is_active(&request_timeout_watcher)) {
867 ev_feed_event(worker->loop, &request_timeout_watcher, EV_TIMER);
869 } else if (submit_request() != 0) {
870 process_request_failure();
875 RequestStat *Client::get_req_stat(int32_t stream_id) {
876 auto it = streams.find(stream_id);
877 if (it == std::end(streams)) {
881 return &(*it).second.req_stat;
884 int Client::connection_made() {
888 const unsigned char *next_proto = nullptr;
889 unsigned int next_proto_len;
891 #ifndef OPENSSL_NO_NEXTPROTONEG
892 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
893 #endif // !OPENSSL_NO_NEXTPROTONEG
894 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
895 if (next_proto == nullptr) {
896 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
898 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
901 auto proto = StringRef{next_proto, next_proto_len};
902 if (util::check_h2_is_selected(proto)) {
903 session = std::make_unique<Http2Session>(this);
904 } else if (util::streq(NGHTTP2_H1_1, proto)) {
905 session = std::make_unique<Http1Session>(this);
908 // Just assign next_proto to selected_proto anyway to show the
909 // negotiation result.
910 selected_proto = proto.str();
912 std::cout << "No protocol negotiated. Fallback behaviour may be activated"
915 for (const auto &proto : config.npn_list) {
916 if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) {
918 << "Server does not support NPN/ALPN. Falling back to HTTP/1.1."
920 session = std::make_unique<Http1Session>(this);
921 selected_proto = NGHTTP2_H1_1.str();
927 if (!selected_proto.empty()) {
933 << "No supported protocol was negotiated. Supported protocols were:"
935 for (const auto &proto : config.npn_list) {
936 std::cout << proto.substr(1) << std::endl;
942 switch (config.no_tls_proto) {
943 case Config::PROTO_HTTP2:
944 session = std::make_unique<Http2Session>(this);
945 selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
947 case Config::PROTO_HTTP1_1:
948 session = std::make_unique<Http1Session>(this);
949 selected_proto = NGHTTP2_H1_1.str();
959 state = CLIENT_CONNECTED;
961 session->on_connect();
963 record_connect_time();
965 if (!config.timing_script) {
966 auto nreq = config.is_timing_based_mode()
967 ? std::max(req_left, session->max_concurrent_streams())
968 : std::min(req_left, session->max_concurrent_streams());
969 for (; nreq > 0; --nreq) {
970 if (submit_request() != 0) {
971 process_request_failure();
977 ev_tstamp duration = config.timings[reqidx];
979 while (duration < 1e-9) {
980 if (submit_request() != 0) {
981 process_request_failure();
984 duration = config.timings[reqidx];
986 // if reqidx wraps around back to 0, we uses up all lines and
992 if (duration >= 1e-9) {
993 // double check since we may have break due to reqidx wraps
995 request_timeout_watcher.repeat = duration;
996 ev_timer_again(worker->loop, &request_timeout_watcher);
1004 int Client::on_read(const uint8_t *data, size_t len) {
1005 auto rv = session->on_read(data, len);
1009 if (worker->current_phase == Phase::MAIN_DURATION) {
1010 worker->stats.bytes_total += len;
1016 int Client::on_write() {
1017 if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
1021 if (session->on_write() != 0) {
1027 int Client::read_clear() {
1032 while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
1035 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1045 if (on_read(buf, nread) != 0) {
1053 int Client::write_clear() {
1054 std::array<struct iovec, 2> iov;
1057 if (on_write() != 0) {
1061 auto iovcnt = wb.riovec(iov.data(), iov.size());
1068 while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
1072 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1073 ev_io_start(worker->loop, &wev);
1082 ev_io_stop(worker->loop, &wev);
1087 int Client::connected() {
1088 if (!util::check_socket_connected(fd)) {
1089 return ERR_CONNECT_FAIL;
1091 ev_io_start(worker->loop, &rev);
1092 ev_io_stop(worker->loop, &wev);
1095 readfn = &Client::tls_handshake;
1096 writefn = &Client::tls_handshake;
1101 readfn = &Client::read_clear;
1102 writefn = &Client::write_clear;
1104 if (connection_made() != 0) {
1111 int Client::tls_handshake() {
1114 auto rv = SSL_do_handshake(ssl);
1117 auto err = SSL_get_error(ssl, rv);
1119 case SSL_ERROR_WANT_READ:
1120 ev_io_stop(worker->loop, &wev);
1122 case SSL_ERROR_WANT_WRITE:
1123 ev_io_start(worker->loop, &wev);
1130 ev_io_stop(worker->loop, &wev);
1132 readfn = &Client::read_tls;
1133 writefn = &Client::write_tls;
1135 if (connection_made() != 0) {
1142 int Client::read_tls() {
1148 auto rv = SSL_read(ssl, buf, sizeof(buf));
1151 auto err = SSL_get_error(ssl, rv);
1153 case SSL_ERROR_WANT_READ:
1155 case SSL_ERROR_WANT_WRITE:
1156 // renegotiation started
1163 if (on_read(buf, rv) != 0) {
1169 int Client::write_tls() {
1175 if (on_write() != 0) {
1179 auto iovcnt = wb.riovec(&iov, 1);
1185 auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
1188 auto err = SSL_get_error(ssl, rv);
1190 case SSL_ERROR_WANT_READ:
1191 // renegotiation started
1193 case SSL_ERROR_WANT_WRITE:
1194 ev_io_start(worker->loop, &wev);
1204 ev_io_stop(worker->loop, &wev);
1209 void Client::record_request_time(RequestStat *req_stat) {
1210 req_stat->request_time = std::chrono::steady_clock::now();
1211 req_stat->request_wall_time = std::chrono::system_clock::now();
1214 void Client::record_connect_start_time() {
1215 cstat.connect_start_time = std::chrono::steady_clock::now();
1218 void Client::record_connect_time() {
1219 cstat.connect_time = std::chrono::steady_clock::now();
1222 void Client::record_ttfb() {
1223 if (recorded(cstat.ttfb)) {
1227 cstat.ttfb = std::chrono::steady_clock::now();
1230 void Client::clear_connect_times() {
1231 cstat.connect_start_time = std::chrono::steady_clock::time_point();
1232 cstat.connect_time = std::chrono::steady_clock::time_point();
1233 cstat.ttfb = std::chrono::steady_clock::time_point();
1236 void Client::record_client_start_time() {
1237 // Record start time only once at the very first connection is going
1239 if (recorded(cstat.client_start_time)) {
1243 cstat.client_start_time = std::chrono::steady_clock::now();
1246 void Client::record_client_end_time() {
1247 // Unlike client_start_time, we overwrite client_end_time. This
1248 // handles multiple connect/disconnect for HTTP/1.1 benchmark.
1249 cstat.client_end_time = std::chrono::steady_clock::now();
1252 void Client::signal_write() { ev_io_start(worker->loop, &wev); }
1254 void Client::try_new_connection() { new_connection_requested = true; }
1257 int get_ev_loop_flags() {
1258 if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
1259 return ev_recommended_backends() | EVBACKEND_KQUEUE;
1266 Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
1267 size_t rate, size_t max_samples, Config *config)
1268 : stats(req_todo, nclients),
1269 loop(ev_loop_new(get_ev_loop_flags())),
1273 tls_info_report_done(false),
1274 app_info_report_done(false),
1277 nreqs_per_client(req_todo / nclients),
1278 nreqs_rem(req_todo % nclients),
1280 max_samples(max_samples),
1282 if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
1283 progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
1285 progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
1288 // Below timeout is not needed in case of timing-based benchmarking
1289 // create timer that will go off every rate_period
1290 ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
1291 config->rate_period);
1292 timeout_watcher.data = this;
1294 if (config->is_timing_based_mode()) {
1295 stats.req_stats.reserve(std::max(req_todo, max_samples));
1296 stats.client_stats.reserve(std::max(nclients, max_samples));
1298 stats.req_stats.reserve(std::min(req_todo, max_samples));
1299 stats.client_stats.reserve(std::min(nclients, max_samples));
1302 sampling_init(request_times_smp, max_samples);
1303 sampling_init(client_smp, max_samples);
1305 ev_timer_init(&duration_watcher, duration_timeout_cb, config->duration, 0.);
1306 duration_watcher.data = this;
1308 ev_timer_init(&warmup_watcher, warmup_timeout_cb, config->warm_up_time, 0.);
1309 warmup_watcher.data = this;
1311 if (config->is_timing_based_mode()) {
1312 current_phase = Phase::INITIAL_IDLE;
1314 current_phase = Phase::MAIN_DURATION;
1319 ev_timer_stop(loop, &timeout_watcher);
1320 ev_timer_stop(loop, &duration_watcher);
1321 ev_timer_stop(loop, &warmup_watcher);
1322 ev_loop_destroy(loop);
1325 void Worker::stop_all_clients() {
1326 for (auto client : clients) {
1327 if (client && client->session) {
1328 client->terminate_session();
1333 void Worker::free_client(Client *deleted_client) {
1334 for (auto &client : clients) {
1335 if (client == deleted_client) {
1336 client->req_todo = client->req_done;
1337 stats.req_todo += client->req_todo;
1338 auto index = &client - &clients[0];
1339 clients[index] = NULL;
1345 void Worker::run() {
1346 if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
1347 for (size_t i = 0; i < nclients; ++i) {
1348 auto req_todo = nreqs_per_client;
1349 if (nreqs_rem > 0) {
1354 auto client = std::make_unique<Client>(next_client_id++, this, req_todo);
1355 if (client->connect() != 0) {
1356 std::cerr << "client could not connect to host" << std::endl;
1362 } else if (config->is_rate_mode()) {
1363 ev_timer_again(loop, &timeout_watcher);
1365 // call callback so that we don't waste the first rate_period
1366 rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
1368 // call the callback to start for one single time
1369 rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
1375 template <typename Stats, typename Stat>
1376 void sample(Sampling &smp, Stats &stats, Stat *s) {
1378 if (stats.size() < smp.max_samples) {
1379 stats.push_back(*s);
1382 auto d = std::uniform_int_distribution<unsigned long>(0, smp.n - 1);
1384 if (i < smp.max_samples) {
1390 void Worker::sample_req_stat(RequestStat *req_stat) {
1391 sample(request_times_smp, stats.req_stats, req_stat);
1394 void Worker::sample_client_stat(ClientStat *cstat) {
1395 sample(client_smp, stats.client_stats, cstat);
1398 void Worker::report_progress() {
1399 if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval ||
1400 config->is_timing_based_mode()) {
1404 std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
1408 void Worker::report_rate_progress() {
1409 if (id != 0 || nconns_made % progress_interval) {
1413 std::cout << "progress: " << nconns_made * 100 / nclients
1414 << "% of clients started" << std::endl;
1418 // Returns percentage of number of samples within mean +/- sd.
1419 double within_sd(const std::vector<double> &samples, double mean, double sd) {
1420 if (samples.size() == 0) {
1423 auto lower = mean - sd;
1424 auto upper = mean + sd;
1425 auto m = std::count_if(
1426 std::begin(samples), std::end(samples),
1427 [&lower, &upper](double t) { return lower <= t && t <= upper; });
1428 return (m / static_cast<double>(samples.size())) * 100;
1433 // Computes statistics using |samples|. The min, max, mean, sd, and
1434 // percentage of number of samples within mean +/- sd are computed.
1435 // If |sampling| is true, this computes sample variance. Otherwise,
1436 // population variance.
1437 SDStat compute_time_stat(const std::vector<double> &samples,
1438 bool sampling = false) {
1439 if (samples.empty()) {
1440 return {0.0, 0.0, 0.0, 0.0, 0.0};
1442 // standard deviation calculated using Rapid calculation method:
1443 // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
1444 double a = 0, q = 0;
1447 auto res = SDStat{std::numeric_limits<double>::max(),
1448 std::numeric_limits<double>::min()};
1449 for (const auto &t : samples) {
1451 res.min = std::min(res.min, t);
1452 res.max = std::max(res.max, t);
1455 auto na = a + (t - a) / n;
1456 q += (t - a) * (t - na);
1462 res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
1463 res.within_sd = within_sd(samples, res.mean, res.sd);
1471 process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
1472 auto request_times_sampling = false;
1473 auto client_times_sampling = false;
1474 size_t nrequest_times = 0;
1475 size_t nclient_times = 0;
1476 for (const auto &w : workers) {
1477 nrequest_times += w->stats.req_stats.size();
1478 request_times_sampling = w->request_times_smp.n > w->stats.req_stats.size();
1480 nclient_times += w->stats.client_stats.size();
1481 client_times_sampling = w->client_smp.n > w->stats.client_stats.size();
1484 std::vector<double> request_times;
1485 request_times.reserve(nrequest_times);
1487 std::vector<double> connect_times, ttfb_times, rps_values;
1488 connect_times.reserve(nclient_times);
1489 ttfb_times.reserve(nclient_times);
1490 rps_values.reserve(nclient_times);
1492 for (const auto &w : workers) {
1493 for (const auto &req_stat : w->stats.req_stats) {
1494 if (!req_stat.completed) {
1497 request_times.push_back(
1498 std::chrono::duration_cast<std::chrono::duration<double>>(
1499 req_stat.stream_close_time - req_stat.request_time)
1503 const auto &stat = w->stats;
1505 for (const auto &cstat : stat.client_stats) {
1506 if (recorded(cstat.client_start_time) &&
1507 recorded(cstat.client_end_time)) {
1508 auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
1509 cstat.client_end_time - cstat.client_start_time)
1512 rps_values.push_back(cstat.req_success / t);
1516 // We will get connect event before FFTB.
1517 if (!recorded(cstat.connect_start_time) ||
1518 !recorded(cstat.connect_time)) {
1522 connect_times.push_back(
1523 std::chrono::duration_cast<std::chrono::duration<double>>(
1524 cstat.connect_time - cstat.connect_start_time)
1527 if (!recorded(cstat.ttfb)) {
1531 ttfb_times.push_back(
1532 std::chrono::duration_cast<std::chrono::duration<double>>(
1533 cstat.ttfb - cstat.connect_start_time)
1538 return {compute_time_stat(request_times, request_times_sampling),
1539 compute_time_stat(connect_times, client_times_sampling),
1540 compute_time_stat(ttfb_times, client_times_sampling),
1541 compute_time_stat(rps_values, client_times_sampling)};
1546 void resolve_host() {
1547 if (config.base_uri_unix) {
1548 auto res = std::make_unique<addrinfo>();
1549 res->ai_family = config.unix_addr.sun_family;
1550 res->ai_socktype = SOCK_STREAM;
1551 res->ai_addrlen = sizeof(config.unix_addr);
1553 static_cast<struct sockaddr *>(static_cast<void *>(&config.unix_addr));
1555 config.addrs = res.release();
1560 addrinfo hints{}, *res;
1562 hints.ai_family = AF_UNSPEC;
1563 hints.ai_socktype = SOCK_STREAM;
1564 hints.ai_protocol = 0;
1565 hints.ai_flags = AI_ADDRCONFIG;
1567 const auto &resolve_host =
1568 config.connect_to_host.empty() ? config.host : config.connect_to_host;
1570 config.connect_to_port == 0 ? config.port : config.connect_to_port;
1573 getaddrinfo(resolve_host.c_str(), util::utos(port).c_str(), &hints, &res);
1575 std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
1578 if (res == nullptr) {
1579 std::cerr << "No address returned" << std::endl;
1587 std::string get_reqline(const char *uri, const http_parser_url &u) {
1588 std::string reqline;
1590 if (util::has_uri_field(u, UF_PATH)) {
1591 reqline = util::get_uri_field(uri, u, UF_PATH).str();
1596 if (util::has_uri_field(u, UF_QUERY)) {
1598 reqline += util::get_uri_field(uri, u, UF_QUERY);
1605 #ifndef OPENSSL_NO_NEXTPROTONEG
1607 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
1608 unsigned char *outlen, const unsigned char *in,
1609 unsigned int inlen, void *arg) {
1610 if (util::select_protocol(const_cast<const unsigned char **>(out), outlen, in,
1611 inlen, config.npn_list)) {
1612 return SSL_TLSEXT_ERR_OK;
1615 // OpenSSL will terminate handshake with fatal alert if we return
1616 // NOACK. So there is no way to fallback.
1617 return SSL_TLSEXT_ERR_NOACK;
1620 #endif // !OPENSSL_NO_NEXTPROTONEG
1623 constexpr char UNIX_PATH_PREFIX[] = "unix:";
1627 bool parse_base_uri(const StringRef &base_uri) {
1628 http_parser_url u{};
1629 if (http_parser_parse_url(base_uri.c_str(), base_uri.size(), 0, &u) != 0 ||
1630 !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
1634 config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA).str();
1635 config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST).str();
1636 config.default_port = util::get_default_port(base_uri.c_str(), u);
1637 if (util::has_uri_field(u, UF_PORT)) {
1638 config.port = u.port;
1640 config.port = config.default_port;
1647 // Use std::vector<std::string>::iterator explicitly, without that,
1648 // http_parser_url u{} fails with clang-3.4.
1649 std::vector<std::string> parse_uris(std::vector<std::string>::iterator first,
1650 std::vector<std::string>::iterator last) {
1651 std::vector<std::string> reqlines;
1653 if (first == last) {
1654 std::cerr << "no URI available" << std::endl;
1658 if (!config.has_base_uri()) {
1660 if (!parse_base_uri(StringRef{*first})) {
1661 std::cerr << "invalid URI: " << *first << std::endl;
1665 config.base_uri = *first;
1668 for (; first != last; ++first) {
1669 http_parser_url u{};
1671 auto uri = (*first).c_str();
1673 if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0) {
1674 std::cerr << "invalid URI: " << uri << std::endl;
1678 reqlines.push_back(get_reqline(uri, u));
1686 std::vector<std::string> read_uri_from_file(std::istream &infile) {
1687 std::vector<std::string> uris;
1688 std::string line_uri;
1689 while (std::getline(infile, line_uri)) {
1690 uris.push_back(line_uri);
1698 void read_script_from_file(std::istream &infile,
1699 std::vector<ev_tstamp> &timings,
1700 std::vector<std::string> &uris) {
1701 std::string script_line;
1703 while (std::getline(infile, script_line)) {
1705 if (script_line.empty()) {
1706 std::cerr << "Empty line detected at line " << line_count
1707 << ". Ignoring and continuing." << std::endl;
1711 std::size_t pos = script_line.find("\t");
1712 if (pos == std::string::npos) {
1713 std::cerr << "Invalid line format detected, no tab character at line "
1714 << line_count << ". \n\t" << script_line << std::endl;
1718 const char *start = script_line.c_str();
1720 auto v = std::strtod(start, &end);
1723 if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) {
1725 std::cerr << "Time value error at line " << line_count << ". \n\t"
1726 << "value = " << script_line.substr(0, pos) << std::endl;
1728 std::cerr << "\t" << strerror(error) << std::endl;
1733 timings.push_back(v / 1000.0);
1734 uris.push_back(script_line.substr(pos + 1, script_line.size()));
1740 std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
1741 size_t nreqs, size_t nclients,
1742 size_t rate, size_t max_samples) {
1743 std::stringstream rate_report;
1744 if (config.is_rate_mode() && nclients > rate) {
1745 rate_report << "Up to " << rate << " client(s) will be created every "
1746 << util::duration_str(config.rate_period) << " ";
1749 if (config.is_timing_based_mode()) {
1750 std::cout << "spawning thread #" << id << ": " << nclients
1751 << " total client(s). Timing-based test with "
1752 << config.warm_up_time << "s of warm-up time and "
1753 << config.duration << "s of main duration for measurements."
1756 std::cout << "spawning thread #" << id << ": " << nclients
1757 << " total client(s). " << rate_report.str() << nreqs
1758 << " total requests" << std::endl;
1761 if (config.is_rate_mode()) {
1762 return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate,
1763 max_samples, &config);
1765 // Here rate is same as client because the rate_timeout callback
1766 // will be called only once
1767 return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, nclients,
1768 max_samples, &config);
1774 int parse_header_table_size(uint32_t &dst, const char *opt,
1775 const char *optarg) {
1776 auto n = util::parse_uint_with_unit(optarg);
1778 std::cerr << "--" << opt << ": Bad option value: " << optarg << std::endl;
1781 if (n > std::numeric_limits<uint32_t>::max()) {
1782 std::cerr << "--" << opt
1783 << ": Value too large. It should be less than or equal to "
1784 << std::numeric_limits<uint32_t>::max() << std::endl;
1795 void print_version(std::ostream &out) {
1796 out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
1801 void print_usage(std::ostream &out) {
1802 out << R"(Usage: h2load [OPTIONS]... [URI]...
1803 benchmarking tool for HTTP/2 server)"
1809 constexpr char DEFAULT_NPN_LIST[] = "h2,h2-16,h2-14,http/1.1";
1813 void print_help(std::ostream &out) {
1816 auto config = Config();
1819 <URI> Specify URI to access. Multiple URIs can be specified.
1820 URIs are used in this order for each client. All URIs
1821 are used, then first URI is used and then 2nd URI, and
1822 so on. The scheme, host and port in the subsequent
1823 URIs, if present, are ignored. Those in the first URI
1824 are used solely. Definition of a base URI overrides all
1825 scheme, host or port values.
1828 Number of requests across all clients. If it is used
1829 with --timing-script-file option, this option specifies
1830 the number of requests each client performs rather than
1831 the number of requests across all clients. This option
1832 is ignored if timing-based benchmarking is enabled (see
1835 << config.nreqs << R"(
1837 Number of concurrent clients. With -r option, this
1838 specifies the maximum number of connections to be made.
1840 << config.nclients << R"(
1842 Number of native threads.
1844 << config.nthreads << R"(
1845 -i, --input-file=<PATH>
1846 Path of a file with multiple URIs are separated by EOLs.
1847 This option will disable URIs getting from command-line.
1848 If '-' is given as <PATH>, URIs will be read from stdin.
1849 URIs are used in this order for each client. All URIs
1850 are used, then first URI is used and then 2nd URI, and
1851 so on. The scheme, host and port in the subsequent
1852 URIs, if present, are ignored. Those in the first URI
1853 are used solely. Definition of a base URI overrides all
1854 scheme, host or port values.
1855 -m, --max-concurrent-streams=<N>
1856 Max concurrent streams to issue per session. When
1857 http/1.1 is used, this specifies the number of HTTP
1858 pipelining requests in-flight.
1860 -w, --window-bits=<N>
1861 Sets the stream level initial window size to (2**<N>)-1.
1863 << config.window_bits << R"(
1864 -W, --connection-window-bits=<N>
1865 Sets the connection level initial window size to
1868 << config.connection_window_bits << R"(
1869 -H, --header=<HEADER>
1870 Add/Override a header to the requests.
1872 Set allowed cipher list. The format of the string is
1873 described in OpenSSL ciphers(1).
1875 << config.ciphers << R"(
1876 -p, --no-tls-proto=<PROTOID>
1877 Specify ALPN identifier of the protocol to be used when
1878 accessing http URI without SSL/TLS.
1879 Available protocols: )"
1880 << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1 << R"(
1882 << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
1884 Post FILE to server. The request method is changed to
1885 POST. For http/1.1 connection, if -d is used, the
1886 maximum number of in-flight pipelined requests is set to
1889 Specifies the fixed rate at which connections are
1890 created. The rate must be a positive integer,
1891 representing the number of connections to be made per
1892 rate period. The maximum number of connections to be
1893 made is given in -c option. This rate will be
1894 distributed among threads as evenly as possible. For
1895 example, with -t2 and -r4, each thread gets 2
1896 connections per period. When the rate is 0, the program
1897 will run as it normally does, creating connections at
1898 whatever variable rate it wants. The default value for
1899 this option is 0. -r and -D are mutually exclusive.
1900 --rate-period=<DURATION>
1901 Specifies the time period between creating connections.
1902 The period must be a positive number, representing the
1903 length of the period in time. This option is ignored if
1904 the rate option is not used. The default value for this
1907 Specifies the main duration for the measurements in case
1908 of timing-based benchmarking. -D and -r are mutually
1910 --warm-up-time=<DURATION>
1911 Specifies the time period before starting the actual
1912 measurements, in case of timing-based benchmarking.
1913 Needs to provided along with -D option.
1914 -T, --connection-active-timeout=<DURATION>
1915 Specifies the maximum time that h2load is willing to
1916 keep a connection open, regardless of the activity on
1917 said connection. <DURATION> must be a positive integer,
1918 specifying the amount of time to wait. When no timeout
1919 value is set (either active or inactive), h2load will
1920 keep a connection open indefinitely, waiting for a
1922 -N, --connection-inactivity-timeout=<DURATION>
1923 Specifies the amount of time that h2load is willing to
1924 wait to see activity on a given connection. <DURATION>
1925 must be a positive integer, specifying the amount of
1926 time to wait. When no timeout value is set (either
1927 active or inactive), h2load will keep a connection open
1928 indefinitely, waiting for a response.
1929 --timing-script-file=<PATH>
1930 Path of a file containing one or more lines separated by
1931 EOLs. Each script line is composed of two tab-separated
1932 fields. The first field represents the time offset from
1933 the start of execution, expressed as a positive value of
1934 milliseconds with microsecond resolution. The second
1935 field represents the URI. This option will disable URIs
1936 getting from command-line. If '-' is given as <PATH>,
1937 script lines will be read from stdin. Script lines are
1938 used in order for each client. If -n is given, it must
1939 be less than or equal to the number of script lines,
1940 larger values are clamped to the number of script lines.
1941 If -n is not given, the number of requests will default
1942 to the number of script lines. The scheme, host and
1943 port defined in the first URI are used solely. Values
1944 contained in other URIs, if present, are ignored.
1945 Definition of a base URI overrides all scheme, host or
1947 -B, --base-uri=(<URI>|unix:<PATH>)
1948 Specify URI from which the scheme, host and port will be
1949 used for all requests. The base URI overrides all
1950 values defined either at the command line or inside
1951 input files. If argument starts with "unix:", then the
1952 rest of the argument will be treated as UNIX domain
1953 socket path. The connection is made through that path
1954 instead of TCP. In this case, scheme is inferred from
1955 the first URI appeared in the command line or inside
1956 input files as usual.
1958 Comma delimited list of ALPN protocol identifier sorted
1959 in the order of preference. That means most desirable
1960 protocol comes first. This is used in both ALPN and
1961 NPN. The parameter must be delimited by a single comma
1962 only and any white spaces are treated as a part of
1965 << DEFAULT_NPN_LIST << R"(
1966 --h1 Short hand for --npn-list=http/1.1
1967 --no-tls-proto=http/1.1, which effectively force
1968 http/1.1 for both http and https URI.
1969 --header-table-size=<SIZE>
1970 Specify decoder header table size.
1972 << util::utos_unit(config.header_table_size) << R"(
1973 --encoder-header-table-size=<SIZE>
1974 Specify encoder header table size. The decoder (server)
1975 specifies the maximum dynamic table size it accepts.
1976 Then the negotiated dynamic table size is the minimum of
1977 this option value and the value which server specified.
1979 << util::utos_unit(config.encoder_header_table_size) << R"(
1981 Write per-request information to a file as tab-separated
1982 columns: start time as microseconds since epoch; HTTP
1983 status code; microseconds until end of response. More
1984 columns may be added later. Rows are ordered by end-of-
1985 response time when using one worker thread, but may
1986 appear slightly out of order with multiple threads due
1987 to buffering. Status code is -1 for failed streams.
1988 --connect-to=<HOST>[:<PORT>]
1989 Host and port to connect instead of using the authority
1992 Output debug information.
1993 --version Display version information and exit.
1994 -h, --help Display this help and exit.
1998 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
1999 10 * 1024). Units are K, M and G (powers of 1024).
2001 The <DURATION> argument is an integer and an optional unit (e.g., 1s
2002 is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
2003 (hours, minutes, seconds and milliseconds, respectively). If a unit
2004 is omitted, a second is used as unit.)"
2009 int main(int argc, char **argv) {
2013 tls::LibsslGlobalLock lock;
2016 std::string datafile;
2017 std::string logfile;
2018 bool nreqs_set_manually = false;
2020 static int flag = 0;
2021 constexpr static option long_options[] = {
2022 {"requests", required_argument, nullptr, 'n'},
2023 {"clients", required_argument, nullptr, 'c'},
2024 {"data", required_argument, nullptr, 'd'},
2025 {"threads", required_argument, nullptr, 't'},
2026 {"max-concurrent-streams", required_argument, nullptr, 'm'},
2027 {"window-bits", required_argument, nullptr, 'w'},
2028 {"connection-window-bits", required_argument, nullptr, 'W'},
2029 {"input-file", required_argument, nullptr, 'i'},
2030 {"header", required_argument, nullptr, 'H'},
2031 {"no-tls-proto", required_argument, nullptr, 'p'},
2032 {"verbose", no_argument, nullptr, 'v'},
2033 {"help", no_argument, nullptr, 'h'},
2034 {"version", no_argument, &flag, 1},
2035 {"ciphers", required_argument, &flag, 2},
2036 {"rate", required_argument, nullptr, 'r'},
2037 {"connection-active-timeout", required_argument, nullptr, 'T'},
2038 {"connection-inactivity-timeout", required_argument, nullptr, 'N'},
2039 {"duration", required_argument, nullptr, 'D'},
2040 {"timing-script-file", required_argument, &flag, 3},
2041 {"base-uri", required_argument, nullptr, 'B'},
2042 {"npn-list", required_argument, &flag, 4},
2043 {"rate-period", required_argument, &flag, 5},
2044 {"h1", no_argument, &flag, 6},
2045 {"header-table-size", required_argument, &flag, 7},
2046 {"encoder-header-table-size", required_argument, &flag, 8},
2047 {"warm-up-time", required_argument, &flag, 9},
2048 {"log-file", required_argument, &flag, 10},
2049 {"connect-to", required_argument, &flag, 11},
2050 {nullptr, 0, nullptr, 0}};
2051 int option_index = 0;
2052 auto c = getopt_long(argc, argv,
2053 "hvW:c:d:m:n:p:t:w:H:i:r:T:N:D:B:", long_options,
2060 config.nreqs = strtoul(optarg, nullptr, 10);
2061 nreqs_set_manually = true;
2064 config.nclients = strtoul(optarg, nullptr, 10);
2071 std::cerr << "-t: WARNING: Threading disabled at build time, "
2072 << "no threads created." << std::endl;
2074 config.nthreads = strtoul(optarg, nullptr, 10);
2078 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
2083 char *endptr = nullptr;
2084 auto n = strtoul(optarg, &endptr, 10);
2085 if (errno == 0 && *endptr == '\0' && n < 31) {
2087 config.window_bits = n;
2089 config.connection_window_bits = n;
2092 std::cerr << "-" << static_cast<char>(c)
2093 << ": specify the integer in the range [0, 30], inclusive"
2100 char *header = optarg;
2101 // Skip first possible ':' in the header name
2102 char *value = strchr(optarg + 1, ':');
2103 if (!value || (header[0] == ':' && header + 1 == value)) {
2104 std::cerr << "-H: invalid header: " << optarg << std::endl;
2109 while (isspace(*value)) {
2113 // This could also be a valid case for suppressing a header
2115 std::cerr << "-H: invalid header - value missing: " << optarg
2119 // Note that there is no processing currently to handle multiple
2120 // message-header fields with the same field name
2121 config.custom_headers.emplace_back(header, value);
2122 util::inp_strlower(config.custom_headers.back().name);
2126 config.ifile = optarg;
2129 auto proto = StringRef{optarg};
2130 if (util::strieq(StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID),
2132 config.no_tls_proto = Config::PROTO_HTTP2;
2133 } else if (util::strieq(NGHTTP2_H1_1, proto)) {
2134 config.no_tls_proto = Config::PROTO_HTTP1_1;
2136 std::cerr << "-p: unsupported protocol " << proto << std::endl;
2142 config.rate = strtoul(optarg, nullptr, 10);
2143 if (config.rate == 0) {
2144 std::cerr << "-r: the rate at which connections are made "
2145 << "must be positive." << std::endl;
2150 config.conn_active_timeout = util::parse_duration_with_unit(optarg);
2151 if (!std::isfinite(config.conn_active_timeout)) {
2152 std::cerr << "-T: bad value for the conn_active_timeout wait time: "
2153 << optarg << std::endl;
2158 config.conn_inactivity_timeout = util::parse_duration_with_unit(optarg);
2159 if (!std::isfinite(config.conn_inactivity_timeout)) {
2160 std::cerr << "-N: bad value for the conn_inactivity_timeout wait time: "
2161 << optarg << std::endl;
2166 auto arg = StringRef{optarg};
2167 config.base_uri = "";
2168 config.base_uri_unix = false;
2170 if (util::istarts_with_l(arg, UNIX_PATH_PREFIX)) {
2171 // UNIX domain socket path
2174 auto path = StringRef{std::begin(arg) + str_size(UNIX_PATH_PREFIX),
2177 if (path.size() == 0 || path.size() + 1 > sizeof(un.sun_path)) {
2178 std::cerr << "--base-uri: invalid UNIX domain socket path: " << arg
2183 config.base_uri_unix = true;
2185 auto &unix_addr = config.unix_addr;
2186 std::copy(std::begin(path), std::end(path), unix_addr.sun_path);
2187 unix_addr.sun_path[path.size()] = '\0';
2188 unix_addr.sun_family = AF_UNIX;
2193 if (!parse_base_uri(arg)) {
2194 std::cerr << "--base-uri: invalid base URI: " << arg << std::endl;
2198 config.base_uri = arg.str();
2202 config.duration = strtoul(optarg, nullptr, 10);
2203 if (config.duration == 0) {
2204 std::cerr << "-D: the main duration for timing-based benchmarking "
2205 << "must be positive." << std::endl;
2210 config.verbose = true;
2213 print_help(std::cout);
2216 util::show_candidates(argv[optind - 1], long_options);
2222 print_version(std::cout);
2226 config.ciphers = optarg;
2229 // timing-script option
2230 config.ifile = optarg;
2231 config.timing_script = true;
2235 config.npn_list = util::parse_config_str_list(StringRef{optarg});
2239 config.rate_period = util::parse_duration_with_unit(optarg);
2240 if (!std::isfinite(config.rate_period)) {
2241 std::cerr << "--rate-period: value error " << optarg << std::endl;
2248 util::parse_config_str_list(StringRef::from_lit("http/1.1"));
2249 config.no_tls_proto = Config::PROTO_HTTP1_1;
2252 // --header-table-size
2253 if (parse_header_table_size(config.header_table_size,
2254 "header-table-size", optarg) != 0) {
2259 // --encoder-header-table-size
2260 if (parse_header_table_size(config.encoder_header_table_size,
2261 "encoder-header-table-size", optarg) != 0) {
2267 config.warm_up_time = util::parse_duration_with_unit(optarg);
2268 if (!std::isfinite(config.warm_up_time)) {
2269 std::cerr << "--warm-up-time: value error " << optarg << std::endl;
2279 auto p = util::split_hostport(StringRef{optarg});
2281 if (p.first.empty() ||
2282 (!p.second.empty() && (port = util::parse_uint(p.second)) == -1)) {
2283 std::cerr << "--connect-to: Invalid value " << optarg << std::endl;
2286 config.connect_to_host = p.first.str();
2287 config.connect_to_port = port;
2297 if (argc == optind) {
2298 if (config.ifile.empty()) {
2299 std::cerr << "no URI or input file given" << std::endl;
2304 if (config.nclients == 0) {
2305 std::cerr << "-c: the number of clients must be strictly greater than 0."
2310 if (config.npn_list.empty()) {
2312 util::parse_config_str_list(StringRef::from_lit(DEFAULT_NPN_LIST));
2315 // serialize the APLN tokens
2316 for (auto &proto : config.npn_list) {
2317 proto.insert(proto.begin(), static_cast<unsigned char>(proto.size()));
2320 std::vector<std::string> reqlines;
2322 if (config.ifile.empty()) {
2323 std::vector<std::string> uris;
2324 std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
2325 reqlines = parse_uris(std::begin(uris), std::end(uris));
2327 std::vector<std::string> uris;
2328 if (!config.timing_script) {
2329 if (config.ifile == "-") {
2330 uris = read_uri_from_file(std::cin);
2332 std::ifstream infile(config.ifile);
2334 std::cerr << "cannot read input file: " << config.ifile << std::endl;
2338 uris = read_uri_from_file(infile);
2341 if (config.ifile == "-") {
2342 read_script_from_file(std::cin, config.timings, uris);
2344 std::ifstream infile(config.ifile);
2346 std::cerr << "cannot read input file: " << config.ifile << std::endl;
2350 read_script_from_file(infile, config.timings, uris);
2353 if (nreqs_set_manually) {
2354 if (config.nreqs > uris.size()) {
2355 std::cerr << "-n: the number of requests must be less than or equal "
2356 "to the number of timing script entries. Setting number "
2358 << uris.size() << std::endl;
2360 config.nreqs = uris.size();
2363 config.nreqs = uris.size();
2367 reqlines = parse_uris(std::begin(uris), std::end(uris));
2370 if (reqlines.empty()) {
2371 std::cerr << "No URI given" << std::endl;
2375 if (config.is_timing_based_mode() && config.is_rate_mode()) {
2376 std::cerr << "-r, -D: they are mutually exclusive." << std::endl;
2380 if (config.nreqs == 0 && !config.is_timing_based_mode()) {
2381 std::cerr << "-n: the number of requests must be strictly greater than 0 "
2382 "if timing-based test is not being run."
2387 if (config.max_concurrent_streams == 0) {
2388 std::cerr << "-m: the max concurrent streams must be strictly greater "
2389 << "than 0." << std::endl;
2393 if (config.nthreads == 0) {
2394 std::cerr << "-t: the number of threads must be strictly greater than 0."
2399 if (config.nthreads > std::thread::hardware_concurrency()) {
2400 std::cerr << "-t: warning: the number of threads is greater than hardware "
2401 << "cores." << std::endl;
2404 // With timing script, we don't distribute config.nreqs to each
2405 // client or thread.
2406 if (!config.timing_script && config.nreqs < config.nclients &&
2407 !config.is_timing_based_mode()) {
2408 std::cerr << "-n, -c: the number of requests must be greater than or "
2409 << "equal to the clients." << std::endl;
2413 if (config.nclients < config.nthreads) {
2414 std::cerr << "-c, -t: the number of clients must be greater than or equal "
2415 << "to the number of threads." << std::endl;
2419 if (config.is_timing_based_mode()) {
2423 if (config.is_rate_mode()) {
2424 if (config.rate < config.nthreads) {
2425 std::cerr << "-r, -t: the connection rate must be greater than or equal "
2426 << "to the number of threads." << std::endl;
2430 if (config.rate > config.nclients) {
2431 std::cerr << "-r, -c: the connection rate must be smaller than or equal "
2432 "to the number of clients."
2438 if (!datafile.empty()) {
2439 config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
2440 if (config.data_fd == -1) {
2441 std::cerr << "-d: Could not open file " << datafile << std::endl;
2444 struct stat data_stat;
2445 if (fstat(config.data_fd, &data_stat) == -1) {
2446 std::cerr << "-d: Could not stat file " << datafile << std::endl;
2449 config.data_length = data_stat.st_size;
2452 if (!logfile.empty()) {
2453 config.log_fd = open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND,
2454 S_IRUSR | S_IWUSR | S_IRGRP);
2455 if (config.log_fd == -1) {
2456 std::cerr << "--log-file: Could not open file " << logfile << std::endl;
2461 struct sigaction act {};
2462 act.sa_handler = SIG_IGN;
2463 sigaction(SIGPIPE, &act, nullptr);
2465 auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
2467 std::cerr << "Failed to create SSL_CTX: "
2468 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2472 auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2473 SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2474 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2476 SSL_CTX_set_options(ssl_ctx, ssl_opts);
2477 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2478 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2480 if (nghttp2::tls::ssl_ctx_set_proto_versions(
2481 ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2482 nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2483 std::cerr << "Could not set TLS versions" << std::endl;
2487 if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) {
2488 std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers
2489 << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
2494 #ifndef OPENSSL_NO_NEXTPROTONEG
2495 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2497 #endif // !OPENSSL_NO_NEXTPROTONEG
2499 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2500 std::vector<unsigned char> proto_list;
2501 for (const auto &proto : config.npn_list) {
2502 std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list));
2505 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2506 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2508 std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
2510 shared_nva.emplace_back(":scheme", config.scheme);
2511 if (config.port != config.default_port) {
2512 shared_nva.emplace_back(":authority",
2513 config.host + ":" + util::utos(config.port));
2515 shared_nva.emplace_back(":authority", config.host);
2517 shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
2518 shared_nva.emplace_back("user-agent", user_agent);
2520 // list header fields that can be overridden.
2521 auto override_hdrs = make_array<std::string>(":authority", ":host", ":method",
2522 ":scheme", "user-agent");
2524 for (auto &kv : config.custom_headers) {
2525 if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
2526 kv.name) != std::end(override_hdrs)) {
2528 for (auto &nv : shared_nva) {
2529 if ((nv.name == ":authority" && kv.name == ":host") ||
2530 (nv.name == kv.name)) {
2531 nv.value = kv.value;
2535 // add additional headers
2536 shared_nva.push_back(kv);
2540 std::string content_length_str;
2541 if (config.data_fd != -1) {
2542 content_length_str = util::utos(config.data_length);
2546 std::find_if(std::begin(shared_nva), std::end(shared_nva),
2547 [](const Header &nv) { return nv.name == ":method"; });
2548 assert(method_it != std::end(shared_nva));
2550 config.h1reqs.reserve(reqlines.size());
2551 config.nva.reserve(reqlines.size());
2553 for (auto &req : reqlines) {
2555 auto h1req = (*method_it).value;
2558 h1req += " HTTP/1.1\r\n";
2559 for (auto &nv : shared_nva) {
2560 if (nv.name == ":authority") {
2566 if (nv.name[0] == ':') {
2575 if (!content_length_str.empty()) {
2576 h1req += "Content-Length: ";
2577 h1req += content_length_str;
2582 config.h1reqs.push_back(std::move(h1req));
2585 std::vector<nghttp2_nv> nva;
2586 // 2 for :path, and possible content-length
2587 nva.reserve(2 + shared_nva.size());
2589 nva.push_back(http2::make_nv_ls(":path", req));
2591 for (auto &nv : shared_nva) {
2592 nva.push_back(http2::make_nv(nv.name, nv.value, false));
2595 if (!content_length_str.empty()) {
2596 nva.push_back(http2::make_nv(StringRef::from_lit("content-length"),
2597 StringRef{content_length_str}));
2600 config.nva.push_back(std::move(nva));
2603 // Don't DOS our server!
2604 if (config.host == "nghttp2.org") {
2605 std::cerr << "Using h2load against public server " << config.host
2606 << " should be prohibited." << std::endl;
2612 std::cout << "starting benchmark..." << std::endl;
2614 std::vector<std::unique_ptr<Worker>> workers;
2615 workers.reserve(config.nthreads);
2618 size_t nreqs_per_thread = 0;
2619 ssize_t nreqs_rem = 0;
2621 if (!config.timing_script) {
2622 nreqs_per_thread = config.nreqs / config.nthreads;
2623 nreqs_rem = config.nreqs % config.nthreads;
2626 size_t nclients_per_thread = config.nclients / config.nthreads;
2627 ssize_t nclients_rem = config.nclients % config.nthreads;
2629 size_t rate_per_thread = config.rate / config.nthreads;
2630 ssize_t rate_per_thread_rem = config.rate % config.nthreads;
2632 size_t max_samples_per_thread =
2633 std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
2636 std::condition_variable cv;
2639 std::vector<std::future<void>> futures;
2640 for (size_t i = 0; i < config.nthreads; ++i) {
2641 auto rate = rate_per_thread;
2642 if (rate_per_thread_rem > 0) {
2643 --rate_per_thread_rem;
2646 auto nclients = nclients_per_thread;
2647 if (nclients_rem > 0) {
2653 if (config.timing_script) {
2654 // With timing script, each client issues config.nreqs requests.
2655 // We divide nreqs by number of clients in Worker ctor to
2656 // distribute requests to those clients evenly, so multiply
2657 // config.nreqs here by config.nclients.
2658 nreqs = config.nreqs * nclients;
2660 nreqs = nreqs_per_thread;
2661 if (nreqs_rem > 0) {
2667 workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate,
2668 max_samples_per_thread));
2669 auto &worker = workers.back();
2671 std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
2673 std::unique_lock<std::mutex> ulk(mu);
2674 cv.wait(ulk, [&ready] { return ready; });
2681 std::lock_guard<std::mutex> lg(mu);
2686 auto start = std::chrono::steady_clock::now();
2688 for (auto &fut : futures) {
2693 auto rate = config.rate;
2694 auto nclients = config.nclients;
2696 config.timing_script ? config.nreqs * config.nclients : config.nreqs;
2699 create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
2701 auto start = std::chrono::steady_clock::now();
2703 workers.back()->run();
2706 auto end = std::chrono::steady_clock::now();
2708 std::chrono::duration_cast<std::chrono::microseconds>(end - start);
2711 for (const auto &w : workers) {
2712 const auto &s = w->stats;
2714 stats.req_todo += s.req_todo;
2715 stats.req_started += s.req_started;
2716 stats.req_done += s.req_done;
2717 stats.req_timedout += s.req_timedout;
2718 stats.req_success += s.req_success;
2719 stats.req_status_success += s.req_status_success;
2720 stats.req_failed += s.req_failed;
2721 stats.req_error += s.req_error;
2722 stats.bytes_total += s.bytes_total;
2723 stats.bytes_head += s.bytes_head;
2724 stats.bytes_head_decomp += s.bytes_head_decomp;
2725 stats.bytes_body += s.bytes_body;
2727 for (size_t i = 0; i < stats.status.size(); ++i) {
2728 stats.status[i] += s.status[i];
2732 auto ts = process_time_stats(workers);
2734 // Requests which have not been issued due to connection errors, are
2735 // counted towards req_failed and req_error.
2736 auto req_not_issued =
2737 (stats.req_todo - stats.req_status_success - stats.req_failed);
2738 stats.req_failed += req_not_issued;
2739 stats.req_error += req_not_issued;
2741 // UI is heavily inspired by weighttp[1] and wrk[2]
2743 // [1] https://github.com/lighttpd/weighttp
2744 // [2] https://github.com/wg/wrk
2747 if (duration.count() > 0) {
2748 if (config.is_timing_based_mode()) {
2749 // we only want to consider the main duration if warm-up is given
2750 rps = stats.req_success / config.duration;
2751 bps = stats.bytes_total / config.duration;
2753 auto secd = std::chrono::duration_cast<
2754 std::chrono::duration<double, std::chrono::seconds::period>>(
2756 rps = stats.req_success / secd.count();
2757 bps = stats.bytes_total / secd.count();
2761 double header_space_savings = 0.;
2762 if (stats.bytes_head_decomp > 0) {
2763 header_space_savings =
2764 1. - static_cast<double>(stats.bytes_head) / stats.bytes_head_decomp;
2767 std::cout << std::fixed << std::setprecision(2) << R"(
2769 << util::format_duration(duration) << ", " << rps << " req/s, "
2770 << util::utos_funit(bps) << R"(B/s
2771 requests: )" << stats.req_todo
2772 << " total, " << stats.req_started << " started, " << stats.req_done
2773 << " done, " << stats.req_status_success << " succeeded, "
2774 << stats.req_failed << " failed, " << stats.req_error
2775 << " errored, " << stats.req_timedout << R"( timeout
2777 << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
2778 << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
2779 traffic: )" << util::utos_funit(stats.bytes_total)
2780 << "B (" << stats.bytes_total << ") total, "
2781 << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
2782 << ") headers (space savings " << header_space_savings * 100
2783 << "%), " << util::utos_funit(stats.bytes_body) << "B ("
2784 << stats.bytes_body << R"() data
2785 min max mean sd +/- sd
2786 time for request: )"
2787 << std::setw(10) << util::format_duration(ts.request.min) << " "
2788 << std::setw(10) << util::format_duration(ts.request.max) << " "
2789 << std::setw(10) << util::format_duration(ts.request.mean) << " "
2790 << std::setw(10) << util::format_duration(ts.request.sd)
2791 << std::setw(9) << util::dtos(ts.request.within_sd) << "%"
2792 << "\ntime for connect: " << std::setw(10)
2793 << util::format_duration(ts.connect.min) << " " << std::setw(10)
2794 << util::format_duration(ts.connect.max) << " " << std::setw(10)
2795 << util::format_duration(ts.connect.mean) << " " << std::setw(10)
2796 << util::format_duration(ts.connect.sd) << std::setw(9)
2797 << util::dtos(ts.connect.within_sd) << "%"
2798 << "\ntime to 1st byte: " << std::setw(10)
2799 << util::format_duration(ts.ttfb.min) << " " << std::setw(10)
2800 << util::format_duration(ts.ttfb.max) << " " << std::setw(10)
2801 << util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
2802 << util::format_duration(ts.ttfb.sd) << std::setw(9)
2803 << util::dtos(ts.ttfb.within_sd) << "%"
2804 << "\nreq/s : " << std::setw(10) << ts.rps.min << " "
2805 << std::setw(10) << ts.rps.max << " " << std::setw(10)
2806 << ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
2807 << util::dtos(ts.rps.within_sd) << "%" << std::endl;
2809 SSL_CTX_free(ssl_ctx);
2811 if (config.log_fd != -1) {
2812 close(config.log_fd);
2818 } // namespace h2load
2820 int main(int argc, char **argv) { return h2load::main(argc, argv); }