• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2014 Tatsuhiro Tsujikawa
5  *
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:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
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.
24  */
25 #include "h2load.h"
26 
27 #include <getopt.h>
28 #include <signal.h>
29 #ifdef HAVE_NETINET_IN_H
30 #  include <netinet/in.h>
31 #endif // HAVE_NETINET_IN_H
32 #include <netinet/tcp.h>
33 #include <sys/stat.h>
34 #ifdef HAVE_FCNTL_H
35 #  include <fcntl.h>
36 #endif // HAVE_FCNTL_H
37 #include <sys/mman.h>
38 #include <netinet/udp.h>
39 
40 #include <cstdio>
41 #include <cassert>
42 #include <cstdlib>
43 #include <iostream>
44 #include <iomanip>
45 #include <fstream>
46 #include <chrono>
47 #include <thread>
48 #include <future>
49 #include <random>
50 #include <string_view>
51 
52 #include "ssl_compat.h"
53 
54 #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL
55 #  include <wolfssl/options.h>
56 #  include <wolfssl/openssl/err.h>
57 #else // !NGHTTP2_OPENSSL_IS_WOLFSSL
58 #  include <openssl/err.h>
59 #endif // !NGHTTP2_OPENSSL_IS_WOLFSSL
60 
61 #ifdef ENABLE_HTTP3
62 #  ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
63 #    include <ngtcp2/ngtcp2_crypto_quictls.h>
64 #  endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
65 #  ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
66 #    include <ngtcp2/ngtcp2_crypto_boringssl.h>
67 #  endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
68 #  ifdef HAVE_LIBNGTCP2_CRYPTO_WOLFSSL
69 #    include <ngtcp2/ngtcp2_crypto_wolfssl.h>
70 #  endif // HAVE_LIBNGTCP2_CRYPTO_WOLFSSL
71 #endif   // ENABLE_HTTP3
72 
73 #include "url-parser/url_parser.h"
74 
75 #include "h2load_http1_session.h"
76 #include "h2load_http2_session.h"
77 #ifdef ENABLE_HTTP3
78 #  include "h2load_http3_session.h"
79 #  include "h2load_quic.h"
80 #endif // ENABLE_HTTP3
81 #include "tls.h"
82 #include "http2.h"
83 #include "util.h"
84 #include "template.h"
85 
86 #ifndef O_BINARY
87 #  define O_BINARY (0)
88 #endif // O_BINARY
89 
90 using namespace nghttp2;
91 
92 namespace h2load {
93 
94 namespace {
recorded(const std::chrono::steady_clock::time_point & t)95 bool recorded(const std::chrono::steady_clock::time_point &t) {
96   return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
97 }
98 } // namespace
99 
Config()100 Config::Config()
101   : ciphers(tls::DEFAULT_CIPHER_LIST),
102     tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_"
103                   "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"),
104     groups("X25519:P-256:P-384:P-521"),
105     data_length(-1),
106     data(nullptr),
107     addrs(nullptr),
108     nreqs(1),
109     nclients(1),
110     nthreads(1),
111     max_concurrent_streams(1),
112     window_bits(30),
113     connection_window_bits(30),
114     max_frame_size(16_k),
115     rate(0),
116     rate_period(1.0),
117     duration(0.0),
118     warm_up_time(0.0),
119     conn_active_timeout(0.),
120     conn_inactivity_timeout(0.),
121     no_tls_proto(PROTO_HTTP2),
122     header_table_size(4_k),
123     encoder_header_table_size(4_k),
124     data_fd(-1),
125     log_fd(-1),
126     qlog_file_base(),
127     port(0),
128     default_port(0),
129     connect_to_port(0),
130     verbose(false),
131     timing_script(false),
132     base_uri_unix(false),
133     unix_addr{},
134     rps(0.),
135     no_udp_gso(false),
136     max_udp_payload_size(0),
137     ktls(false) {}
138 
~Config()139 Config::~Config() {
140   if (addrs) {
141     if (base_uri_unix) {
142       delete addrs;
143     } else {
144       freeaddrinfo(addrs);
145     }
146   }
147 
148   if (data_fd != -1) {
149     close(data_fd);
150   }
151 }
152 
is_rate_mode() const153 bool Config::is_rate_mode() const { return (this->rate != 0); }
is_timing_based_mode() const154 bool Config::is_timing_based_mode() const { return (this->duration > 0); }
has_base_uri() const155 bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
rps_enabled() const156 bool Config::rps_enabled() const { return this->rps > 0.0; }
is_quic() const157 bool Config::is_quic() const {
158 #ifdef ENABLE_HTTP3
159   return !alpn_list.empty() &&
160          (alpn_list[0] == NGHTTP3_ALPN_H3 || alpn_list[0] == "\x5h3-29");
161 #else  // !ENABLE_HTTP3
162   return false;
163 #endif // !ENABLE_HTTP3
164 }
165 Config config;
166 
167 namespace {
168 constexpr size_t MAX_SAMPLES = 1000000;
169 } // namespace
170 
Stats(size_t req_todo,size_t nclients)171 Stats::Stats(size_t req_todo, size_t nclients)
172   : req_todo(req_todo),
173     req_started(0),
174     req_done(0),
175     req_success(0),
176     req_status_success(0),
177     req_failed(0),
178     req_error(0),
179     req_timedout(0),
180     bytes_total(0),
181     bytes_head(0),
182     bytes_head_decomp(0),
183     bytes_body(0),
184     status(),
185     udp_dgram_recv(0),
186     udp_dgram_sent(0) {}
187 
Stream()188 Stream::Stream() : req_stat{}, status_success(-1) {}
189 
190 namespace {
191 std::random_device rd;
192 } // namespace
193 
194 namespace {
195 std::mt19937 gen(rd());
196 } // namespace
197 
198 namespace {
sampling_init(Sampling & smp,size_t max_samples)199 void sampling_init(Sampling &smp, size_t max_samples) {
200   smp.n = 0;
201   smp.max_samples = max_samples;
202 }
203 } // namespace
204 
205 namespace {
writecb(struct ev_loop * loop,ev_io * w,int revents)206 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
207   auto client = static_cast<Client *>(w->data);
208   client->restart_timeout();
209   auto rv = client->do_write();
210   if (rv == Client::ERR_CONNECT_FAIL) {
211     client->disconnect();
212     // Try next address
213     client->current_addr = nullptr;
214     rv = client->connect();
215     if (rv != 0) {
216       client->fail();
217       client->worker->free_client(client);
218       delete client;
219       return;
220     }
221     return;
222   }
223   if (rv != 0) {
224     client->fail();
225     client->worker->free_client(client);
226     delete client;
227   }
228 }
229 } // namespace
230 
231 namespace {
readcb(struct ev_loop * loop,ev_io * w,int revents)232 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
233   auto client = static_cast<Client *>(w->data);
234   client->restart_timeout();
235   if (client->do_read() != 0) {
236     if (client->try_again_or_fail() == 0) {
237       return;
238     }
239     client->worker->free_client(client);
240     delete client;
241     return;
242   }
243   client->signal_write();
244 }
245 } // namespace
246 
247 namespace {
248 // Called every rate_period when rate mode is being used
rate_period_timeout_w_cb(struct ev_loop * loop,ev_timer * w,int revents)249 void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
250   auto worker = static_cast<Worker *>(w->data);
251   auto nclients_per_second = worker->rate;
252   auto conns_remaining = worker->nclients - worker->nconns_made;
253   auto nclients = std::min(nclients_per_second, conns_remaining);
254 
255   for (size_t i = 0; i < nclients; ++i) {
256     auto req_todo = worker->nreqs_per_client;
257     if (worker->nreqs_rem > 0) {
258       ++req_todo;
259       --worker->nreqs_rem;
260     }
261     auto client =
262       std::make_unique<Client>(worker->next_client_id++, worker, req_todo);
263 
264     ++worker->nconns_made;
265 
266     if (client->connect() != 0) {
267       std::cerr << "client could not connect to host" << std::endl;
268       client->fail();
269     } else {
270       if (worker->config->is_timing_based_mode()) {
271         worker->clients.push_back(client.release());
272       } else {
273         client.release();
274       }
275     }
276     worker->report_rate_progress();
277   }
278   if (!worker->config->is_timing_based_mode()) {
279     if (worker->nconns_made >= worker->nclients) {
280       ev_timer_stop(worker->loop, w);
281     }
282   } else {
283     // To check whether all created clients are pushed correctly
284     assert(worker->nclients == worker->clients.size());
285   }
286 }
287 } // namespace
288 
289 namespace {
290 // Called when the duration for infinite number of requests are over
duration_timeout_cb(struct ev_loop * loop,ev_timer * w,int revents)291 void duration_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
292   auto worker = static_cast<Worker *>(w->data);
293 
294   worker->current_phase = Phase::DURATION_OVER;
295 
296   std::cout << "Main benchmark duration is over for thread #" << worker->id
297             << ". Stopping all clients." << std::endl;
298   worker->stop_all_clients();
299   std::cout << "Stopped all clients for thread #" << worker->id << std::endl;
300 }
301 } // namespace
302 
303 namespace {
304 // Called when the warmup duration for infinite number of requests are over
warmup_timeout_cb(struct ev_loop * loop,ev_timer * w,int revents)305 void warmup_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
306   auto worker = static_cast<Worker *>(w->data);
307 
308   std::cout << "Warm-up phase is over for thread #" << worker->id << "."
309             << std::endl;
310   std::cout << "Main benchmark duration is started for thread #" << worker->id
311             << "." << std::endl;
312   assert(worker->stats.req_started == 0);
313   assert(worker->stats.req_done == 0);
314 
315   for (auto client : worker->clients) {
316     if (client) {
317       assert(client->req_todo == 0);
318       assert(client->req_left == 1);
319       assert(client->req_inflight == 0);
320       assert(client->req_started == 0);
321       assert(client->req_done == 0);
322 
323       client->record_client_start_time();
324       client->clear_connect_times();
325       client->record_connect_start_time();
326     }
327   }
328 
329   worker->current_phase = Phase::MAIN_DURATION;
330 
331   ev_timer_start(worker->loop, &worker->duration_watcher);
332 }
333 } // namespace
334 
335 namespace {
rps_cb(struct ev_loop * loop,ev_timer * w,int revents)336 void rps_cb(struct ev_loop *loop, ev_timer *w, int revents) {
337   auto client = static_cast<Client *>(w->data);
338   auto &session = client->session;
339 
340   assert(!config.timing_script);
341 
342   if (client->req_left == 0) {
343     ev_timer_stop(loop, w);
344     return;
345   }
346 
347   auto now = std::chrono::steady_clock::now();
348   auto d = now - client->rps_duration_started;
349   auto n = static_cast<size_t>(
350     round(std::chrono::duration<double>(d).count() * config.rps));
351   client->rps_req_pending += n;
352   client->rps_duration_started +=
353     util::duration_from(static_cast<double>(n) / config.rps);
354 
355   if (client->rps_req_pending == 0) {
356     return;
357   }
358 
359   auto nreq = session->max_concurrent_streams() - client->rps_req_inflight;
360   if (nreq == 0) {
361     return;
362   }
363 
364   nreq = config.is_timing_based_mode() ? std::max(nreq, client->req_left)
365                                        : std::min(nreq, client->req_left);
366   nreq = std::min(nreq, client->rps_req_pending);
367 
368   client->rps_req_inflight += nreq;
369   client->rps_req_pending -= nreq;
370 
371   for (; nreq > 0; --nreq) {
372     if (client->submit_request() != 0) {
373       client->process_request_failure();
374       break;
375     }
376   }
377 
378   client->signal_write();
379 }
380 } // namespace
381 
382 namespace {
383 // Called when an a connection has been inactive for a set period of time
384 // or a fixed amount of time after all requests have been made on a
385 // connection
conn_timeout_cb(EV_P_ ev_timer * w,int revents)386 void conn_timeout_cb(EV_P_ ev_timer *w, int revents) {
387   auto client = static_cast<Client *>(w->data);
388 
389   ev_timer_stop(client->worker->loop, &client->conn_inactivity_watcher);
390   ev_timer_stop(client->worker->loop, &client->conn_active_watcher);
391 
392   if (util::check_socket_connected(client->fd)) {
393     client->timeout();
394   }
395 }
396 } // namespace
397 
398 namespace {
check_stop_client_request_timeout(Client * client,ev_timer * w)399 bool check_stop_client_request_timeout(Client *client, ev_timer *w) {
400   if (client->req_left == 0) {
401     // no more requests to make, stop timer
402     ev_timer_stop(client->worker->loop, w);
403     return true;
404   }
405 
406   return false;
407 }
408 } // namespace
409 
410 namespace {
client_request_timeout_cb(struct ev_loop * loop,ev_timer * w,int revents)411 void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
412   auto client = static_cast<Client *>(w->data);
413 
414   if (client->streams.size() >= config.max_concurrent_streams) {
415     ev_timer_stop(client->worker->loop, w);
416     return;
417   }
418 
419   if (client->submit_request() != 0) {
420     ev_timer_stop(client->worker->loop, w);
421     client->process_request_failure();
422     return;
423   }
424   client->signal_write();
425 
426   if (check_stop_client_request_timeout(client, w)) {
427     return;
428   }
429 
430   auto duration =
431     config.timings[client->reqidx] - config.timings[client->reqidx - 1];
432 
433   while (duration < std::chrono::duration<double>(1e-9)) {
434     if (client->submit_request() != 0) {
435       ev_timer_stop(client->worker->loop, w);
436       client->process_request_failure();
437       return;
438     }
439     client->signal_write();
440     if (check_stop_client_request_timeout(client, w)) {
441       return;
442     }
443 
444     duration =
445       config.timings[client->reqidx] - config.timings[client->reqidx - 1];
446   }
447 
448   client->request_timeout_watcher.repeat = util::ev_tstamp_from(duration);
449   ev_timer_again(client->worker->loop, &client->request_timeout_watcher);
450 }
451 } // namespace
452 
Client(uint32_t id,Worker * worker,size_t req_todo)453 Client::Client(uint32_t id, Worker *worker, size_t req_todo)
454   : wb(&worker->mcpool),
455     cstat{},
456     worker(worker),
457     ssl(nullptr),
458 #ifdef ENABLE_HTTP3
459     quic{},
460 #endif // ENABLE_HTTP3
461     next_addr(config.addrs),
462     current_addr(nullptr),
463     reqidx(0),
464     state(CLIENT_IDLE),
465     req_todo(req_todo),
466     req_left(req_todo),
467     req_inflight(0),
468     req_started(0),
469     req_done(0),
470     id(id),
471     fd(-1),
472     local_addr{},
473     new_connection_requested(false),
474     final(false),
475     rps_req_pending(0),
476     rps_req_inflight(0) {
477   if (req_todo == 0) { // this means infinite number of requests are to be made
478     // This ensures that number of requests are unbounded
479     // Just a positive number is fine, we chose the first positive number
480     req_left = 1;
481   }
482   ev_io_init(&wev, writecb, 0, EV_WRITE);
483   ev_io_init(&rev, readcb, 0, EV_READ);
484 
485   wev.data = this;
486   rev.data = this;
487 
488   ev_timer_init(&conn_inactivity_watcher, conn_timeout_cb, 0.,
489                 worker->config->conn_inactivity_timeout);
490   conn_inactivity_watcher.data = this;
491 
492   ev_timer_init(&conn_active_watcher, conn_timeout_cb,
493                 worker->config->conn_active_timeout, 0.);
494   conn_active_watcher.data = this;
495 
496   ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.);
497   request_timeout_watcher.data = this;
498 
499   ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
500   rps_watcher.data = this;
501 
502 #ifdef ENABLE_HTTP3
503   ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
504   quic.pkt_timer.data = this;
505 
506   if (config.is_quic()) {
507     quic.tx.data = std::make_unique<uint8_t[]>(64_k);
508   }
509 
510   ngtcp2_ccerr_default(&quic.last_error);
511 #endif // ENABLE_HTTP3
512 }
513 
~Client()514 Client::~Client() {
515   disconnect();
516 
517 #ifdef ENABLE_HTTP3
518   if (config.is_quic()) {
519     quic_free();
520   }
521 #endif // ENABLE_HTTP3
522 
523   if (ssl) {
524     SSL_free(ssl);
525   }
526 
527   worker->sample_client_stat(&cstat);
528   ++worker->client_smp.n;
529 }
530 
do_read()531 int Client::do_read() { return readfn(*this); }
do_write()532 int Client::do_write() { return writefn(*this); }
533 
make_socket(addrinfo * addr)534 int Client::make_socket(addrinfo *addr) {
535   int rv;
536 
537   if (config.is_quic()) {
538 #ifdef ENABLE_HTTP3
539     fd = util::create_nonblock_udp_socket(addr->ai_family);
540     if (fd == -1) {
541       return -1;
542     }
543 
544 #  ifdef UDP_GRO
545     int val = 1;
546     if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) != 0) {
547       std::cerr << "setsockopt UDP_GRO failed" << std::endl;
548       return -1;
549     }
550 #  endif // UDP_GRO
551 
552     rv = util::bind_any_addr_udp(fd, addr->ai_family);
553     if (rv != 0) {
554       close(fd);
555       fd = -1;
556       return -1;
557     }
558 
559     socklen_t addrlen = sizeof(local_addr.su.storage);
560     rv = getsockname(fd, &local_addr.su.sa, &addrlen);
561     if (rv == -1) {
562       return -1;
563     }
564     local_addr.len = addrlen;
565 
566     if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr,
567                   addr->ai_addrlen) != 0) {
568       std::cerr << "quic_init failed" << std::endl;
569       return -1;
570     }
571 #endif // ENABLE_HTTP3
572   } else {
573     fd = util::create_nonblock_socket(addr->ai_family);
574     if (fd == -1) {
575       return -1;
576     }
577     if (config.scheme == "https") {
578       if (!ssl) {
579         ssl = SSL_new(worker->ssl_ctx);
580       }
581 
582       SSL_set_connect_state(ssl);
583     }
584   }
585 
586   if (ssl) {
587     if (!config.sni.empty()) {
588       SSL_set_tlsext_host_name(ssl, config.sni.c_str());
589     } else if (!util::numeric_host(config.host.c_str())) {
590       SSL_set_tlsext_host_name(ssl, config.host.c_str());
591     }
592   }
593 
594   if (config.is_quic()) {
595     return 0;
596   }
597 
598   rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
599   if (rv != 0 && errno != EINPROGRESS) {
600     if (ssl) {
601       SSL_free(ssl);
602       ssl = nullptr;
603     }
604     close(fd);
605     fd = -1;
606     return -1;
607   }
608   return 0;
609 }
610 
connect()611 int Client::connect() {
612   int rv;
613 
614   if (!worker->config->is_timing_based_mode() ||
615       worker->current_phase == Phase::MAIN_DURATION) {
616     record_client_start_time();
617     clear_connect_times();
618     record_connect_start_time();
619   } else if (worker->current_phase == Phase::INITIAL_IDLE) {
620     worker->current_phase = Phase::WARM_UP;
621     std::cout << "Warm-up started for thread #" << worker->id << "."
622               << std::endl;
623     ev_timer_start(worker->loop, &worker->warmup_watcher);
624   }
625 
626   if (worker->config->conn_inactivity_timeout > 0.) {
627     ev_timer_again(worker->loop, &conn_inactivity_watcher);
628   }
629 
630   if (current_addr) {
631     rv = make_socket(current_addr);
632     if (rv == -1) {
633       return -1;
634     }
635   } else {
636     addrinfo *addr = nullptr;
637     while (next_addr) {
638       addr = next_addr;
639       next_addr = next_addr->ai_next;
640       rv = make_socket(addr);
641       if (rv == 0) {
642         break;
643       }
644     }
645 
646     if (fd == -1) {
647       return -1;
648     }
649 
650     assert(addr);
651 
652     current_addr = addr;
653   }
654 
655   ev_io_set(&rev, fd, EV_READ);
656   ev_io_set(&wev, fd, EV_WRITE);
657 
658   ev_io_start(worker->loop, &wev);
659 
660   if (config.is_quic()) {
661 #ifdef ENABLE_HTTP3
662     ev_io_start(worker->loop, &rev);
663 
664     readfn = &Client::read_quic;
665     writefn = &Client::write_quic;
666 #endif // ENABLE_HTTP3
667   } else {
668     writefn = &Client::connected;
669   }
670 
671   return 0;
672 }
673 
timeout()674 void Client::timeout() {
675   process_timedout_streams();
676 
677   disconnect();
678 }
679 
restart_timeout()680 void Client::restart_timeout() {
681   if (worker->config->conn_inactivity_timeout > 0.) {
682     ev_timer_again(worker->loop, &conn_inactivity_watcher);
683   }
684 }
685 
try_again_or_fail()686 int Client::try_again_or_fail() {
687   disconnect();
688 
689   if (new_connection_requested) {
690     new_connection_requested = false;
691 
692     if (req_left) {
693       if (worker->current_phase == Phase::MAIN_DURATION) {
694         // At the moment, we don't have a facility to re-start request
695         // already in in-flight.  Make them fail.
696         worker->stats.req_failed += req_inflight;
697         worker->stats.req_error += req_inflight;
698 
699         req_inflight = 0;
700       } else if (worker->current_phase == Phase::DURATION_OVER) {
701         // fix a race condition when h2load is sending connection: close over h1
702         // prevents new clients from spawning after the test should have ended.
703         return -1;
704       }
705 
706       // Keep using current address
707       if (connect() == 0) {
708         return 0;
709       }
710       std::cerr << "client could not connect to host" << std::endl;
711     }
712   }
713 
714   process_abandoned_streams();
715 
716   return -1;
717 }
718 
fail()719 void Client::fail() {
720   disconnect();
721 
722   process_abandoned_streams();
723 }
724 
disconnect()725 void Client::disconnect() {
726   record_client_end_time();
727 
728 #ifdef ENABLE_HTTP3
729   if (config.is_quic()) {
730     quic_close_connection();
731   }
732 #endif // ENABLE_HTTP3
733 
734 #ifdef ENABLE_HTTP3
735   ev_timer_stop(worker->loop, &quic.pkt_timer);
736 #endif // ENABLE_HTTP3
737   ev_timer_stop(worker->loop, &conn_inactivity_watcher);
738   ev_timer_stop(worker->loop, &conn_active_watcher);
739   ev_timer_stop(worker->loop, &rps_watcher);
740   ev_timer_stop(worker->loop, &request_timeout_watcher);
741   streams.clear();
742   session.reset();
743   wb.reset();
744   state = CLIENT_IDLE;
745   ev_io_stop(worker->loop, &wev);
746   ev_io_stop(worker->loop, &rev);
747   if (ssl) {
748     if (config.is_quic()) {
749       SSL_free(ssl);
750       ssl = nullptr;
751     } else {
752       SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
753       ERR_clear_error();
754 
755       if (SSL_shutdown(ssl) != 1) {
756         SSL_free(ssl);
757         ssl = nullptr;
758       }
759     }
760   }
761   if (fd != -1) {
762     shutdown(fd, SHUT_WR);
763     close(fd);
764     fd = -1;
765   }
766 
767   final = false;
768 }
769 
submit_request()770 int Client::submit_request() {
771   if (session->submit_request() != 0) {
772     return -1;
773   }
774 
775   if (worker->current_phase != Phase::MAIN_DURATION) {
776     return 0;
777   }
778 
779   ++worker->stats.req_started;
780   ++req_started;
781   ++req_inflight;
782   if (!worker->config->is_timing_based_mode()) {
783     --req_left;
784   }
785   // if an active timeout is set and this is the last request to be submitted
786   // on this connection, start the active timeout.
787   if (worker->config->conn_active_timeout > 0. && req_left == 0) {
788     ev_timer_start(worker->loop, &conn_active_watcher);
789   }
790 
791   return 0;
792 }
793 
process_timedout_streams()794 void Client::process_timedout_streams() {
795   if (worker->current_phase != Phase::MAIN_DURATION) {
796     return;
797   }
798 
799   for (auto &p : streams) {
800     auto &req_stat = p.second.req_stat;
801     if (!req_stat.completed) {
802       req_stat.stream_close_time = std::chrono::steady_clock::now();
803     }
804   }
805 
806   worker->stats.req_timedout += req_inflight;
807 
808   process_abandoned_streams();
809 }
810 
process_abandoned_streams()811 void Client::process_abandoned_streams() {
812   if (worker->current_phase != Phase::MAIN_DURATION) {
813     return;
814   }
815 
816   auto req_abandoned = req_inflight + req_left;
817 
818   worker->stats.req_failed += req_abandoned;
819   worker->stats.req_error += req_abandoned;
820 
821   req_inflight = 0;
822   req_left = 0;
823 }
824 
process_request_failure()825 void Client::process_request_failure() {
826   if (worker->current_phase != Phase::MAIN_DURATION) {
827     return;
828   }
829 
830   worker->stats.req_failed += req_left;
831   worker->stats.req_error += req_left;
832 
833   req_left = 0;
834 
835   if (req_inflight == 0) {
836     terminate_session();
837   }
838   std::cout << "Process Request Failure:" << worker->stats.req_failed
839             << std::endl;
840 }
841 
842 #ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
843 namespace {
print_server_tmp_key(SSL * ssl)844 void print_server_tmp_key(SSL *ssl) {
845   EVP_PKEY *key;
846 
847   if (!SSL_get_server_tmp_key(ssl, &key)) {
848     return;
849   }
850 
851   auto key_del = defer(EVP_PKEY_free, key);
852 
853   std::cout << "Server Temp Key: ";
854 
855   auto pkey_id = EVP_PKEY_id(key);
856   switch (pkey_id) {
857   case EVP_PKEY_RSA:
858     std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
859     break;
860   case EVP_PKEY_DH:
861     std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
862     break;
863   case EVP_PKEY_EC: {
864 #  if OPENSSL_3_0_0_API
865     std::array<char, 64> curve_name;
866     const char *cname;
867     if (!EVP_PKEY_get_utf8_string_param(key, "group", curve_name.data(),
868                                         curve_name.size(), nullptr)) {
869       cname = "<unknown>";
870     } else {
871       cname = curve_name.data();
872     }
873 #  else  // !OPENSSL_3_0_0_API
874     auto ec = EVP_PKEY_get1_EC_KEY(key);
875     auto ec_del = defer(EC_KEY_free, ec);
876     auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
877     auto cname = EC_curve_nid2nist(nid);
878     if (!cname) {
879       cname = OBJ_nid2sn(nid);
880     }
881 #  endif // !OPENSSL_3_0_0_API
882 
883     std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
884               << std::endl;
885     break;
886   }
887   default:
888     std::cout << OBJ_nid2sn(pkey_id) << " " << EVP_PKEY_bits(key) << " bits"
889               << std::endl;
890     break;
891   }
892 }
893 } // namespace
894 #endif // !NGHTTP2_OPENSSL_IS_BORINGSSL
895 
report_tls_info()896 void Client::report_tls_info() {
897   if (worker->id == 0 && !worker->tls_info_report_done) {
898     worker->tls_info_report_done = true;
899     auto cipher = SSL_get_current_cipher(ssl);
900     std::cout << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n"
901               << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
902 #ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
903     print_server_tmp_key(ssl);
904 #endif // !NGHTTP2_OPENSSL_IS_BORINGSSL
905   }
906 }
907 
report_app_info()908 void Client::report_app_info() {
909   if (worker->id == 0 && !worker->app_info_report_done) {
910     worker->app_info_report_done = true;
911     std::cout << "Application protocol: " << selected_proto << std::endl;
912   }
913 }
914 
terminate_session()915 void Client::terminate_session() {
916 #ifdef ENABLE_HTTP3
917   if (config.is_quic()) {
918     quic.close_requested = true;
919   }
920 #endif // ENABLE_HTTP3
921   if (session) {
922     session->terminate();
923   }
924   // http1 session needs writecb to tear down session.
925   signal_write();
926 }
927 
on_request(int32_t stream_id)928 void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
929 
on_header(int32_t stream_id,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen)930 void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
931                        const uint8_t *value, size_t valuelen) {
932   auto itr = streams.find(stream_id);
933   if (itr == std::end(streams)) {
934     return;
935   }
936   auto &stream = (*itr).second;
937 
938   if (worker->current_phase != Phase::MAIN_DURATION) {
939     // If the stream is for warm-up phase, then mark as a success
940     // But we do not update the count for 2xx, 3xx, etc status codes
941     // Same has been done in on_status_code function
942     stream.status_success = 1;
943     return;
944   }
945 
946   if (stream.status_success == -1 && namelen == 7 &&
947       ":status"_sr == StringRef{name, namelen}) {
948     int status = 0;
949     for (size_t i = 0; i < valuelen; ++i) {
950       if ('0' <= value[i] && value[i] <= '9') {
951         status *= 10;
952         status += value[i] - '0';
953         if (status > 999) {
954           stream.status_success = 0;
955           return;
956         }
957       } else {
958         break;
959       }
960     }
961 
962     if (status < 200) {
963       return;
964     }
965 
966     stream.req_stat.status = status;
967     if (status >= 200 && status < 300) {
968       ++worker->stats.status[2];
969       stream.status_success = 1;
970     } else if (status < 400) {
971       ++worker->stats.status[3];
972       stream.status_success = 1;
973     } else if (status < 600) {
974       ++worker->stats.status[status / 100];
975       stream.status_success = 0;
976     } else {
977       stream.status_success = 0;
978     }
979   }
980 }
981 
on_status_code(int32_t stream_id,uint16_t status)982 void Client::on_status_code(int32_t stream_id, uint16_t status) {
983   auto itr = streams.find(stream_id);
984   if (itr == std::end(streams)) {
985     return;
986   }
987   auto &stream = (*itr).second;
988 
989   if (worker->current_phase != Phase::MAIN_DURATION) {
990     stream.status_success = 1;
991     return;
992   }
993 
994   stream.req_stat.status = status;
995   if (status >= 200 && status < 300) {
996     ++worker->stats.status[2];
997     stream.status_success = 1;
998   } else if (status < 400) {
999     ++worker->stats.status[3];
1000     stream.status_success = 1;
1001   } else if (status < 600) {
1002     ++worker->stats.status[status / 100];
1003     stream.status_success = 0;
1004   } else {
1005     stream.status_success = 0;
1006   }
1007 }
1008 
on_stream_close(int32_t stream_id,bool success,bool final)1009 void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
1010   if (worker->current_phase == Phase::MAIN_DURATION) {
1011     if (req_inflight > 0) {
1012       --req_inflight;
1013     }
1014     auto req_stat = get_req_stat(stream_id);
1015     if (!req_stat) {
1016       return;
1017     }
1018 
1019     req_stat->stream_close_time = std::chrono::steady_clock::now();
1020     if (success) {
1021       req_stat->completed = true;
1022       ++worker->stats.req_success;
1023       ++cstat.req_success;
1024 
1025       if (streams[stream_id].status_success == 1) {
1026         ++worker->stats.req_status_success;
1027       } else {
1028         ++worker->stats.req_failed;
1029       }
1030 
1031       worker->sample_req_stat(req_stat);
1032 
1033       // Count up in successful cases only
1034       ++worker->request_times_smp.n;
1035     } else {
1036       ++worker->stats.req_failed;
1037       ++worker->stats.req_error;
1038     }
1039     ++worker->stats.req_done;
1040     ++req_done;
1041 
1042     if (worker->config->log_fd != -1) {
1043       auto start = std::chrono::duration_cast<std::chrono::microseconds>(
1044         req_stat->request_wall_time.time_since_epoch());
1045       auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
1046         req_stat->stream_close_time - req_stat->request_time);
1047 
1048       std::array<uint8_t, 256> buf;
1049       auto p = std::begin(buf);
1050       p = util::utos(p, start.count());
1051       *p++ = '\t';
1052       if (success) {
1053         p = util::utos(p, req_stat->status);
1054       } else {
1055         *p++ = '-';
1056         *p++ = '1';
1057       }
1058       *p++ = '\t';
1059       p = util::utos(p, delta.count());
1060       *p++ = '\n';
1061 
1062       auto nwrite = static_cast<size_t>(std::distance(std::begin(buf), p));
1063       assert(nwrite <= buf.size());
1064       while (write(worker->config->log_fd, buf.data(), nwrite) == -1 &&
1065              errno == EINTR)
1066         ;
1067     }
1068   }
1069 
1070   worker->report_progress();
1071   streams.erase(stream_id);
1072   if (req_left == 0 && req_inflight == 0) {
1073     terminate_session();
1074     return;
1075   }
1076 
1077   if (!final && req_left > 0) {
1078     if (config.timing_script) {
1079       if (!ev_is_active(&request_timeout_watcher)) {
1080         ev_feed_event(worker->loop, &request_timeout_watcher, EV_TIMER);
1081       }
1082     } else if (!config.rps_enabled()) {
1083       if (submit_request() != 0) {
1084         process_request_failure();
1085       }
1086     } else if (rps_req_pending) {
1087       --rps_req_pending;
1088       if (submit_request() != 0) {
1089         process_request_failure();
1090       }
1091     } else {
1092       assert(rps_req_inflight);
1093       --rps_req_inflight;
1094     }
1095   }
1096 }
1097 
get_req_stat(int32_t stream_id)1098 RequestStat *Client::get_req_stat(int32_t stream_id) {
1099   auto it = streams.find(stream_id);
1100   if (it == std::end(streams)) {
1101     return nullptr;
1102   }
1103 
1104   return &(*it).second.req_stat;
1105 }
1106 
connection_made()1107 int Client::connection_made() {
1108   if (ssl) {
1109     report_tls_info();
1110 
1111     const unsigned char *next_proto = nullptr;
1112     unsigned int next_proto_len;
1113 
1114     SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
1115 
1116     if (next_proto) {
1117       auto proto = StringRef{next_proto, next_proto_len};
1118       if (config.is_quic()) {
1119 #ifdef ENABLE_HTTP3
1120         assert(session);
1121         if ("h3"_sr != proto && "h3-29"_sr != proto) {
1122           return -1;
1123         }
1124 #endif // ENABLE_HTTP3
1125       } else if (util::check_h2_is_selected(proto)) {
1126         session = std::make_unique<Http2Session>(this);
1127       } else if (NGHTTP2_H1_1 == proto) {
1128         session = std::make_unique<Http1Session>(this);
1129       }
1130 
1131       // Just assign next_proto to selected_proto anyway to show the
1132       // negotiation result.
1133       selected_proto = proto;
1134     } else if (config.is_quic()) {
1135       std::cerr << "QUIC requires ALPN negotiation" << std::endl;
1136       return -1;
1137     } else {
1138       std::cout << "No protocol negotiated. Fallback behaviour may be activated"
1139                 << std::endl;
1140 
1141       for (const auto &proto : config.alpn_list) {
1142         if (NGHTTP2_H1_1_ALPN == proto) {
1143           std::cout << "Server does not support ALPN. Falling back to HTTP/1.1."
1144                     << std::endl;
1145           session = std::make_unique<Http1Session>(this);
1146           selected_proto = NGHTTP2_H1_1;
1147           break;
1148         }
1149       }
1150     }
1151 
1152     if (!selected_proto.empty()) {
1153       report_app_info();
1154     }
1155 
1156     if (!session) {
1157       std::cout
1158         << "No supported protocol was negotiated. Supported protocols were:"
1159         << std::endl;
1160       for (const auto &proto : config.alpn_list) {
1161         std::cout << proto.substr(1) << std::endl;
1162       }
1163       disconnect();
1164       return -1;
1165     }
1166   } else {
1167     switch (config.no_tls_proto) {
1168     case Config::PROTO_HTTP2:
1169       session = std::make_unique<Http2Session>(this);
1170       selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
1171       break;
1172     case Config::PROTO_HTTP1_1:
1173       session = std::make_unique<Http1Session>(this);
1174       selected_proto = NGHTTP2_H1_1;
1175       break;
1176     default:
1177       // unreachable
1178       assert(0);
1179     }
1180 
1181     report_app_info();
1182   }
1183 
1184   state = CLIENT_CONNECTED;
1185 
1186   session->on_connect();
1187 
1188   record_connect_time();
1189 
1190   if (config.rps_enabled()) {
1191     rps_watcher.repeat = std::max(0.01, 1. / config.rps);
1192     ev_timer_again(worker->loop, &rps_watcher);
1193     rps_duration_started = std::chrono::steady_clock::now();
1194   }
1195 
1196   if (config.rps_enabled()) {
1197     assert(req_left);
1198 
1199     ++rps_req_inflight;
1200 
1201     if (submit_request() != 0) {
1202       process_request_failure();
1203     }
1204   } else if (!config.timing_script) {
1205     auto nreq = config.is_timing_based_mode()
1206                   ? std::max(req_left, session->max_concurrent_streams())
1207                   : std::min(req_left, session->max_concurrent_streams());
1208 
1209     for (; nreq > 0; --nreq) {
1210       if (submit_request() != 0) {
1211         process_request_failure();
1212         break;
1213       }
1214     }
1215   } else {
1216     auto duration = config.timings[reqidx];
1217 
1218     while (duration < std::chrono::duration<double>(1e-9)) {
1219       if (submit_request() != 0) {
1220         process_request_failure();
1221         break;
1222       }
1223       duration = config.timings[reqidx];
1224       if (reqidx == 0) {
1225         // if reqidx wraps around back to 0, we uses up all lines and
1226         // should break
1227         break;
1228       }
1229     }
1230 
1231     if (duration >= std::chrono::duration<double>(1e-9)) {
1232       // double check since we may have break due to reqidx wraps
1233       // around back to 0
1234       request_timeout_watcher.repeat = util::ev_tstamp_from(duration);
1235       ev_timer_again(worker->loop, &request_timeout_watcher);
1236     }
1237   }
1238   signal_write();
1239 
1240   return 0;
1241 }
1242 
on_read(const uint8_t * data,size_t len)1243 int Client::on_read(const uint8_t *data, size_t len) {
1244   auto rv = session->on_read(data, len);
1245   if (rv != 0) {
1246     return -1;
1247   }
1248   if (worker->current_phase == Phase::MAIN_DURATION) {
1249     worker->stats.bytes_total += len;
1250   }
1251   signal_write();
1252   return 0;
1253 }
1254 
on_write()1255 int Client::on_write() {
1256   if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
1257     return 0;
1258   }
1259 
1260   if (session->on_write() != 0) {
1261     return -1;
1262   }
1263   return 0;
1264 }
1265 
read_clear()1266 int Client::read_clear() {
1267   uint8_t buf[8_k];
1268 
1269   for (;;) {
1270     ssize_t nread;
1271     while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
1272       ;
1273     if (nread == -1) {
1274       if (errno == EAGAIN || errno == EWOULDBLOCK) {
1275         return 0;
1276       }
1277       return -1;
1278     }
1279 
1280     if (nread == 0) {
1281       return -1;
1282     }
1283 
1284     if (on_read(buf, nread) != 0) {
1285       return -1;
1286     }
1287   }
1288 
1289   return 0;
1290 }
1291 
write_clear()1292 int Client::write_clear() {
1293   std::array<struct iovec, 2> iov;
1294 
1295   for (;;) {
1296     if (on_write() != 0) {
1297       return -1;
1298     }
1299 
1300     auto iovcnt = wb.riovec(iov.data(), iov.size());
1301 
1302     if (iovcnt == 0) {
1303       break;
1304     }
1305 
1306     ssize_t nwrite;
1307     while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
1308       ;
1309 
1310     if (nwrite == -1) {
1311       if (errno == EAGAIN || errno == EWOULDBLOCK) {
1312         ev_io_start(worker->loop, &wev);
1313         return 0;
1314       }
1315       return -1;
1316     }
1317 
1318     wb.drain(nwrite);
1319   }
1320 
1321   ev_io_stop(worker->loop, &wev);
1322 
1323   return 0;
1324 }
1325 
connected()1326 int Client::connected() {
1327   if (!util::check_socket_connected(fd)) {
1328     return ERR_CONNECT_FAIL;
1329   }
1330   ev_io_start(worker->loop, &rev);
1331   ev_io_stop(worker->loop, &wev);
1332 
1333   if (ssl) {
1334     SSL_set_fd(ssl, fd);
1335 
1336     readfn = &Client::tls_handshake;
1337     writefn = &Client::tls_handshake;
1338 
1339     return do_write();
1340   }
1341 
1342   readfn = &Client::read_clear;
1343   writefn = &Client::write_clear;
1344 
1345   if (connection_made() != 0) {
1346     return -1;
1347   }
1348 
1349   return 0;
1350 }
1351 
tls_handshake()1352 int Client::tls_handshake() {
1353   ERR_clear_error();
1354 
1355   auto rv = SSL_do_handshake(ssl);
1356 
1357   if (rv <= 0) {
1358     auto err = SSL_get_error(ssl, rv);
1359     switch (err) {
1360     case SSL_ERROR_WANT_READ:
1361       ev_io_stop(worker->loop, &wev);
1362       return 0;
1363     case SSL_ERROR_WANT_WRITE:
1364       ev_io_start(worker->loop, &wev);
1365       return 0;
1366     default:
1367       return -1;
1368     }
1369   }
1370 
1371   ev_io_stop(worker->loop, &wev);
1372 
1373   readfn = &Client::read_tls;
1374   writefn = &Client::write_tls;
1375 
1376   if (connection_made() != 0) {
1377     return -1;
1378   }
1379 
1380   return 0;
1381 }
1382 
read_tls()1383 int Client::read_tls() {
1384   uint8_t buf[8_k];
1385 
1386   ERR_clear_error();
1387 
1388   for (;;) {
1389     auto rv = SSL_read(ssl, buf, sizeof(buf));
1390 
1391     if (rv <= 0) {
1392       auto err = SSL_get_error(ssl, rv);
1393       switch (err) {
1394       case SSL_ERROR_WANT_READ:
1395         return 0;
1396       case SSL_ERROR_WANT_WRITE:
1397         // renegotiation started
1398         return -1;
1399       default:
1400         return -1;
1401       }
1402     }
1403 
1404     if (on_read(buf, rv) != 0) {
1405       return -1;
1406     }
1407   }
1408 }
1409 
write_tls()1410 int Client::write_tls() {
1411   ERR_clear_error();
1412 
1413   struct iovec iov;
1414 
1415   for (;;) {
1416     if (on_write() != 0) {
1417       return -1;
1418     }
1419 
1420     auto iovcnt = wb.riovec(&iov, 1);
1421 
1422     if (iovcnt == 0) {
1423       break;
1424     }
1425 
1426     auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
1427 
1428     if (rv <= 0) {
1429       auto err = SSL_get_error(ssl, rv);
1430       switch (err) {
1431       case SSL_ERROR_WANT_READ:
1432         // renegotiation started
1433         return -1;
1434       case SSL_ERROR_WANT_WRITE:
1435         ev_io_start(worker->loop, &wev);
1436         return 0;
1437       default:
1438         return -1;
1439       }
1440     }
1441 
1442     wb.drain(rv);
1443   }
1444 
1445   ev_io_stop(worker->loop, &wev);
1446 
1447   return 0;
1448 }
1449 
1450 #ifdef ENABLE_HTTP3
1451 // Returns 1 if sendmsg is blocked.
write_udp(const sockaddr * addr,socklen_t addrlen,const uint8_t * data,size_t datalen,size_t gso_size)1452 int Client::write_udp(const sockaddr *addr, socklen_t addrlen,
1453                       const uint8_t *data, size_t datalen, size_t gso_size) {
1454   iovec msg_iov;
1455   msg_iov.iov_base = const_cast<uint8_t *>(data);
1456   msg_iov.iov_len = datalen;
1457 
1458   msghdr msg{};
1459   msg.msg_name = const_cast<sockaddr *>(addr);
1460   msg.msg_namelen = addrlen;
1461   msg.msg_iov = &msg_iov;
1462   msg.msg_iovlen = 1;
1463 
1464 #  ifdef UDP_SEGMENT
1465   std::array<uint8_t, CMSG_SPACE(sizeof(uint16_t))> msg_ctrl{};
1466   if (gso_size && datalen > gso_size) {
1467     msg.msg_control = msg_ctrl.data();
1468     msg.msg_controllen = msg_ctrl.size();
1469 
1470     auto cm = CMSG_FIRSTHDR(&msg);
1471     cm->cmsg_level = SOL_UDP;
1472     cm->cmsg_type = UDP_SEGMENT;
1473     cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
1474     uint16_t n = gso_size;
1475     memcpy(CMSG_DATA(cm), &n, sizeof(n));
1476   }
1477 #  endif // UDP_SEGMENT
1478 
1479   auto nwrite = sendmsg(fd, &msg, 0);
1480   if (nwrite < 0) {
1481     if (errno == EAGAIN || errno == EWOULDBLOCK) {
1482       return 1;
1483     }
1484 
1485     std::cerr << "sendmsg: errno=" << errno << std::endl;
1486   } else if (gso_size) {
1487     worker->stats.udp_dgram_sent += (datalen + gso_size - 1) / gso_size;
1488   } else {
1489     ++worker->stats.udp_dgram_sent;
1490   }
1491 
1492   ev_io_stop(worker->loop, &wev);
1493 
1494   return 0;
1495 }
1496 #endif // ENABLE_HTTP3
1497 
record_request_time(RequestStat * req_stat)1498 void Client::record_request_time(RequestStat *req_stat) {
1499   req_stat->request_time = std::chrono::steady_clock::now();
1500   req_stat->request_wall_time = std::chrono::system_clock::now();
1501 }
1502 
record_connect_start_time()1503 void Client::record_connect_start_time() {
1504   cstat.connect_start_time = std::chrono::steady_clock::now();
1505 }
1506 
record_connect_time()1507 void Client::record_connect_time() {
1508   cstat.connect_time = std::chrono::steady_clock::now();
1509 }
1510 
record_ttfb()1511 void Client::record_ttfb() {
1512   if (recorded(cstat.ttfb)) {
1513     return;
1514   }
1515 
1516   cstat.ttfb = std::chrono::steady_clock::now();
1517 }
1518 
clear_connect_times()1519 void Client::clear_connect_times() {
1520   cstat.connect_start_time = std::chrono::steady_clock::time_point();
1521   cstat.connect_time = std::chrono::steady_clock::time_point();
1522   cstat.ttfb = std::chrono::steady_clock::time_point();
1523 }
1524 
record_client_start_time()1525 void Client::record_client_start_time() {
1526   // Record start time only once at the very first connection is going
1527   // to be made.
1528   if (recorded(cstat.client_start_time)) {
1529     return;
1530   }
1531 
1532   cstat.client_start_time = std::chrono::steady_clock::now();
1533 }
1534 
record_client_end_time()1535 void Client::record_client_end_time() {
1536   // Unlike client_start_time, we overwrite client_end_time.  This
1537   // handles multiple connect/disconnect for HTTP/1.1 benchmark.
1538   cstat.client_end_time = std::chrono::steady_clock::now();
1539 }
1540 
signal_write()1541 void Client::signal_write() { ev_io_start(worker->loop, &wev); }
1542 
try_new_connection()1543 void Client::try_new_connection() { new_connection_requested = true; }
1544 
1545 namespace {
get_ev_loop_flags()1546 int get_ev_loop_flags() {
1547   if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
1548     return ev_recommended_backends() | EVBACKEND_KQUEUE;
1549   }
1550 
1551   return 0;
1552 }
1553 } // namespace
1554 
Worker(uint32_t id,SSL_CTX * ssl_ctx,size_t req_todo,size_t nclients,size_t rate,size_t max_samples,Config * config)1555 Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
1556                size_t rate, size_t max_samples, Config *config)
1557   : randgen(util::make_mt19937()),
1558     stats(req_todo, nclients),
1559     loop(ev_loop_new(get_ev_loop_flags())),
1560     ssl_ctx(ssl_ctx),
1561     config(config),
1562     id(id),
1563     tls_info_report_done(false),
1564     app_info_report_done(false),
1565     nconns_made(0),
1566     nclients(nclients),
1567     nreqs_per_client(req_todo / nclients),
1568     nreqs_rem(req_todo % nclients),
1569     rate(rate),
1570     max_samples(max_samples),
1571     next_client_id(0) {
1572   if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
1573     progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
1574   } else {
1575     progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
1576   }
1577 
1578   // Below timeout is not needed in case of timing-based benchmarking
1579   // create timer that will go off every rate_period
1580   ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
1581                 config->rate_period);
1582   timeout_watcher.data = this;
1583 
1584   if (config->is_timing_based_mode()) {
1585     stats.req_stats.reserve(std::max(req_todo, max_samples));
1586     stats.client_stats.reserve(std::max(nclients, max_samples));
1587   } else {
1588     stats.req_stats.reserve(std::min(req_todo, max_samples));
1589     stats.client_stats.reserve(std::min(nclients, max_samples));
1590   }
1591 
1592   sampling_init(request_times_smp, max_samples);
1593   sampling_init(client_smp, max_samples);
1594 
1595   ev_timer_init(&duration_watcher, duration_timeout_cb, config->duration, 0.);
1596   duration_watcher.data = this;
1597 
1598   ev_timer_init(&warmup_watcher, warmup_timeout_cb, config->warm_up_time, 0.);
1599   warmup_watcher.data = this;
1600 
1601   if (config->is_timing_based_mode()) {
1602     current_phase = Phase::INITIAL_IDLE;
1603   } else {
1604     current_phase = Phase::MAIN_DURATION;
1605   }
1606 }
1607 
~Worker()1608 Worker::~Worker() {
1609   ev_timer_stop(loop, &timeout_watcher);
1610   ev_timer_stop(loop, &duration_watcher);
1611   ev_timer_stop(loop, &warmup_watcher);
1612   ev_loop_destroy(loop);
1613 }
1614 
stop_all_clients()1615 void Worker::stop_all_clients() {
1616   for (auto client : clients) {
1617     if (client) {
1618       client->terminate_session();
1619     }
1620   }
1621 }
1622 
free_client(Client * deleted_client)1623 void Worker::free_client(Client *deleted_client) {
1624   for (auto &client : clients) {
1625     if (client == deleted_client) {
1626       client->req_todo = client->req_done;
1627       stats.req_todo += client->req_todo;
1628       auto index = &client - &clients[0];
1629       clients[index] = nullptr;
1630       return;
1631     }
1632   }
1633 }
1634 
run()1635 void Worker::run() {
1636   if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
1637     for (size_t i = 0; i < nclients; ++i) {
1638       auto req_todo = nreqs_per_client;
1639       if (nreqs_rem > 0) {
1640         ++req_todo;
1641         --nreqs_rem;
1642       }
1643 
1644       auto client = std::make_unique<Client>(next_client_id++, this, req_todo);
1645       if (client->connect() != 0) {
1646         std::cerr << "client could not connect to host" << std::endl;
1647         client->fail();
1648       } else {
1649         client.release();
1650       }
1651     }
1652   } else if (config->is_rate_mode()) {
1653     ev_timer_again(loop, &timeout_watcher);
1654 
1655     // call callback so that we don't waste the first rate_period
1656     rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
1657   } else {
1658     // call the callback to start for one single time
1659     rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
1660   }
1661   ev_run(loop, 0);
1662 }
1663 
1664 namespace {
1665 template <typename Stats, typename Stat>
sample(Sampling & smp,Stats & stats,Stat * s)1666 void sample(Sampling &smp, Stats &stats, Stat *s) {
1667   ++smp.n;
1668   if (stats.size() < smp.max_samples) {
1669     stats.push_back(*s);
1670     return;
1671   }
1672   auto d = std::uniform_int_distribution<unsigned long>(0, smp.n - 1);
1673   auto i = d(gen);
1674   if (i < smp.max_samples) {
1675     stats[i] = *s;
1676   }
1677 }
1678 } // namespace
1679 
sample_req_stat(RequestStat * req_stat)1680 void Worker::sample_req_stat(RequestStat *req_stat) {
1681   sample(request_times_smp, stats.req_stats, req_stat);
1682 }
1683 
sample_client_stat(ClientStat * cstat)1684 void Worker::sample_client_stat(ClientStat *cstat) {
1685   sample(client_smp, stats.client_stats, cstat);
1686 }
1687 
report_progress()1688 void Worker::report_progress() {
1689   if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval ||
1690       config->is_timing_based_mode()) {
1691     return;
1692   }
1693 
1694   std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
1695             << std::endl;
1696 }
1697 
report_rate_progress()1698 void Worker::report_rate_progress() {
1699   if (id != 0 || nconns_made % progress_interval) {
1700     return;
1701   }
1702 
1703   std::cout << "progress: " << nconns_made * 100 / nclients
1704             << "% of clients started" << std::endl;
1705 }
1706 
1707 namespace {
1708 // Returns percentage of number of samples within mean +/- sd.
within_sd(const std::vector<double> & samples,double mean,double sd)1709 double within_sd(const std::vector<double> &samples, double mean, double sd) {
1710   if (samples.size() == 0) {
1711     return 0.0;
1712   }
1713   auto lower = mean - sd;
1714   auto upper = mean + sd;
1715   auto m = std::count_if(
1716     std::begin(samples), std::end(samples),
1717     [&lower, &upper](double t) { return lower <= t && t <= upper; });
1718   return (m / static_cast<double>(samples.size())) * 100;
1719 }
1720 } // namespace
1721 
1722 namespace {
1723 // Computes statistics using |samples|. The min, max, mean, sd, and
1724 // percentage of number of samples within mean +/- sd are computed.
1725 // If |sampling| is true, this computes sample variance.  Otherwise,
1726 // population variance.
compute_time_stat(const std::vector<double> & samples,bool sampling=false)1727 SDStat compute_time_stat(const std::vector<double> &samples,
1728                          bool sampling = false) {
1729   if (samples.empty()) {
1730     return {0.0, 0.0, 0.0, 0.0, 0.0};
1731   }
1732   // standard deviation calculated using Rapid calculation method:
1733   // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
1734   double a = 0, q = 0;
1735   size_t n = 0;
1736   double sum = 0;
1737   auto res = SDStat{std::numeric_limits<double>::max(),
1738                     std::numeric_limits<double>::min()};
1739   for (const auto &t : samples) {
1740     ++n;
1741     res.min = std::min(res.min, t);
1742     res.max = std::max(res.max, t);
1743     sum += t;
1744 
1745     auto na = a + (t - a) / n;
1746     q += (t - a) * (t - na);
1747     a = na;
1748   }
1749 
1750   assert(n > 0);
1751   res.mean = sum / n;
1752   res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
1753   res.within_sd = within_sd(samples, res.mean, res.sd);
1754 
1755   return res;
1756 }
1757 } // namespace
1758 
1759 namespace {
1760 SDStats
process_time_stats(const std::vector<std::unique_ptr<Worker>> & workers)1761 process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
1762   auto request_times_sampling = false;
1763   auto client_times_sampling = false;
1764   size_t nrequest_times = 0;
1765   size_t nclient_times = 0;
1766   for (const auto &w : workers) {
1767     nrequest_times += w->stats.req_stats.size();
1768     request_times_sampling = w->request_times_smp.n > w->stats.req_stats.size();
1769 
1770     nclient_times += w->stats.client_stats.size();
1771     client_times_sampling = w->client_smp.n > w->stats.client_stats.size();
1772   }
1773 
1774   std::vector<double> request_times;
1775   request_times.reserve(nrequest_times);
1776 
1777   std::vector<double> connect_times, ttfb_times, rps_values;
1778   connect_times.reserve(nclient_times);
1779   ttfb_times.reserve(nclient_times);
1780   rps_values.reserve(nclient_times);
1781 
1782   for (const auto &w : workers) {
1783     for (const auto &req_stat : w->stats.req_stats) {
1784       if (!req_stat.completed) {
1785         continue;
1786       }
1787       request_times.push_back(
1788         std::chrono::duration_cast<std::chrono::duration<double>>(
1789           req_stat.stream_close_time - req_stat.request_time)
1790           .count());
1791     }
1792 
1793     const auto &stat = w->stats;
1794 
1795     for (const auto &cstat : stat.client_stats) {
1796       if (recorded(cstat.client_start_time) &&
1797           recorded(cstat.client_end_time)) {
1798         auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
1799                    cstat.client_end_time - cstat.client_start_time)
1800                    .count();
1801         if (t > 1e-9) {
1802           rps_values.push_back(cstat.req_success / t);
1803         }
1804       }
1805 
1806       // We will get connect event before FFTB.
1807       if (!recorded(cstat.connect_start_time) ||
1808           !recorded(cstat.connect_time)) {
1809         continue;
1810       }
1811 
1812       connect_times.push_back(
1813         std::chrono::duration_cast<std::chrono::duration<double>>(
1814           cstat.connect_time - cstat.connect_start_time)
1815           .count());
1816 
1817       if (!recorded(cstat.ttfb)) {
1818         continue;
1819       }
1820 
1821       ttfb_times.push_back(
1822         std::chrono::duration_cast<std::chrono::duration<double>>(
1823           cstat.ttfb - cstat.connect_start_time)
1824           .count());
1825     }
1826   }
1827 
1828   return {compute_time_stat(request_times, request_times_sampling),
1829           compute_time_stat(connect_times, client_times_sampling),
1830           compute_time_stat(ttfb_times, client_times_sampling),
1831           compute_time_stat(rps_values, client_times_sampling)};
1832 }
1833 } // namespace
1834 
1835 namespace {
resolve_host()1836 void resolve_host() {
1837   if (config.base_uri_unix) {
1838     auto res = std::make_unique<addrinfo>();
1839     res->ai_family = config.unix_addr.sun_family;
1840     res->ai_socktype = SOCK_STREAM;
1841     res->ai_addrlen = sizeof(config.unix_addr);
1842     res->ai_addr =
1843       static_cast<struct sockaddr *>(static_cast<void *>(&config.unix_addr));
1844 
1845     config.addrs = res.release();
1846     return;
1847   };
1848 
1849   int rv;
1850   addrinfo hints{}, *res;
1851 
1852   hints.ai_family = AF_UNSPEC;
1853   hints.ai_socktype = SOCK_STREAM;
1854   hints.ai_protocol = 0;
1855   hints.ai_flags = AI_ADDRCONFIG;
1856 
1857   const auto &resolve_host =
1858     config.connect_to_host.empty() ? config.host : config.connect_to_host;
1859   auto port =
1860     config.connect_to_port == 0 ? config.port : config.connect_to_port;
1861 
1862   rv =
1863     getaddrinfo(resolve_host.c_str(), util::utos(port).c_str(), &hints, &res);
1864   if (rv != 0) {
1865     std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
1866     exit(EXIT_FAILURE);
1867   }
1868   if (res == nullptr) {
1869     std::cerr << "No address returned" << std::endl;
1870     exit(EXIT_FAILURE);
1871   }
1872   config.addrs = res;
1873 }
1874 } // namespace
1875 
1876 namespace {
get_reqline(const char * uri,const http_parser_url & u)1877 std::string get_reqline(const char *uri, const http_parser_url &u) {
1878   std::string reqline;
1879 
1880   if (util::has_uri_field(u, UF_PATH)) {
1881     reqline = util::get_uri_field(uri, u, UF_PATH);
1882   } else {
1883     reqline = "/";
1884   }
1885 
1886   if (util::has_uri_field(u, UF_QUERY)) {
1887     reqline += '?';
1888     reqline += util::get_uri_field(uri, u, UF_QUERY);
1889   }
1890 
1891   return reqline;
1892 }
1893 } // namespace
1894 
1895 namespace {
1896 constexpr auto UNIX_PATH_PREFIX = "unix:"_sr;
1897 } // namespace
1898 
1899 namespace {
parse_base_uri(const StringRef & base_uri)1900 bool parse_base_uri(const StringRef &base_uri) {
1901   http_parser_url u{};
1902   if (http_parser_parse_url(base_uri.data(), base_uri.size(), 0, &u) != 0 ||
1903       !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
1904     return false;
1905   }
1906 
1907   config.scheme = util::get_uri_field(base_uri.data(), u, UF_SCHEMA);
1908   config.host = util::get_uri_field(base_uri.data(), u, UF_HOST);
1909   config.default_port = util::get_default_port(base_uri.data(), u);
1910   if (util::has_uri_field(u, UF_PORT)) {
1911     config.port = u.port;
1912   } else {
1913     config.port = config.default_port;
1914   }
1915 
1916   return true;
1917 }
1918 } // namespace
1919 namespace {
1920 // Use std::vector<std::string>::iterator explicitly, without that,
1921 // http_parser_url u{} fails with clang-3.4.
parse_uris(std::vector<std::string>::iterator first,std::vector<std::string>::iterator last)1922 std::vector<std::string> parse_uris(std::vector<std::string>::iterator first,
1923                                     std::vector<std::string>::iterator last) {
1924   std::vector<std::string> reqlines;
1925 
1926   if (first == last) {
1927     std::cerr << "no URI available" << std::endl;
1928     exit(EXIT_FAILURE);
1929   }
1930 
1931   if (!config.has_base_uri()) {
1932     if (!parse_base_uri(StringRef{*first})) {
1933       std::cerr << "invalid URI: " << *first << std::endl;
1934       exit(EXIT_FAILURE);
1935     }
1936 
1937     config.base_uri = *first;
1938   }
1939 
1940   for (; first != last; ++first) {
1941     http_parser_url u{};
1942 
1943     auto uri = (*first).c_str();
1944 
1945     if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0) {
1946       std::cerr << "invalid URI: " << uri << std::endl;
1947       exit(EXIT_FAILURE);
1948     }
1949 
1950     reqlines.push_back(get_reqline(uri, u));
1951   }
1952 
1953   return reqlines;
1954 }
1955 } // namespace
1956 
1957 namespace {
read_uri_from_file(std::istream & infile)1958 std::vector<std::string> read_uri_from_file(std::istream &infile) {
1959   std::vector<std::string> uris;
1960   std::string line_uri;
1961   while (std::getline(infile, line_uri)) {
1962     uris.push_back(line_uri);
1963   }
1964 
1965   return uris;
1966 }
1967 } // namespace
1968 
1969 namespace {
read_script_from_file(std::istream & infile,std::vector<std::chrono::steady_clock::duration> & timings,std::vector<std::string> & uris)1970 void read_script_from_file(
1971   std::istream &infile,
1972   std::vector<std::chrono::steady_clock::duration> &timings,
1973   std::vector<std::string> &uris) {
1974   std::string script_line;
1975   int line_count = 0;
1976   while (std::getline(infile, script_line)) {
1977     line_count++;
1978     if (script_line.empty()) {
1979       std::cerr << "Empty line detected at line " << line_count
1980                 << ". Ignoring and continuing." << std::endl;
1981       continue;
1982     }
1983 
1984     std::size_t pos = script_line.find("\t");
1985     if (pos == std::string::npos) {
1986       std::cerr << "Invalid line format detected, no tab character at line "
1987                 << line_count << ". \n\t" << script_line << std::endl;
1988       exit(EXIT_FAILURE);
1989     }
1990 
1991     const char *start = script_line.c_str();
1992     char *end;
1993     auto v = std::strtod(start, &end);
1994 
1995     errno = 0;
1996     if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) {
1997       auto error = errno;
1998       std::cerr << "Time value error at line " << line_count << ". \n\t"
1999                 << "value = " << script_line.substr(0, pos) << std::endl;
2000       if (error != 0) {
2001         std::cerr << "\t" << strerror(error) << std::endl;
2002       }
2003       exit(EXIT_FAILURE);
2004     }
2005 
2006     timings.emplace_back(
2007       std::chrono::duration_cast<std::chrono::steady_clock::duration>(
2008         std::chrono::duration<double, std::milli>(v)));
2009     uris.push_back(script_line.substr(pos + 1, script_line.size()));
2010   }
2011 }
2012 } // namespace
2013 
2014 namespace {
create_worker(uint32_t id,SSL_CTX * ssl_ctx,size_t nreqs,size_t nclients,size_t rate,size_t max_samples)2015 std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
2016                                       size_t nreqs, size_t nclients,
2017                                       size_t rate, size_t max_samples) {
2018   std::stringstream rate_report;
2019   if (config.is_rate_mode() && nclients > rate) {
2020     rate_report << "Up to " << rate << " client(s) will be created every "
2021                 << util::duration_str(config.rate_period) << " ";
2022   }
2023 
2024   if (config.is_timing_based_mode()) {
2025     std::cout << "spawning thread #" << id << ": " << nclients
2026               << " total client(s). Timing-based test with "
2027               << config.warm_up_time << "s of warm-up time and "
2028               << config.duration << "s of main duration for measurements."
2029               << std::endl;
2030   } else {
2031     std::cout << "spawning thread #" << id << ": " << nclients
2032               << " total client(s). " << rate_report.str() << nreqs
2033               << " total requests" << std::endl;
2034   }
2035 
2036   if (config.is_rate_mode()) {
2037     return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate,
2038                                     max_samples, &config);
2039   } else {
2040     // Here rate is same as client because the rate_timeout callback
2041     // will be called only once
2042     return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, nclients,
2043                                     max_samples, &config);
2044   }
2045 }
2046 } // namespace
2047 
2048 namespace {
parse_header_table_size(uint32_t & dst,const char * opt,const char * optarg)2049 int parse_header_table_size(uint32_t &dst, const char *opt,
2050                             const char *optarg) {
2051   auto n = util::parse_uint_with_unit(optarg);
2052   if (!n) {
2053     std::cerr << "--" << opt << ": Bad option value: " << optarg << std::endl;
2054     return -1;
2055   }
2056   if (n > std::numeric_limits<uint32_t>::max()) {
2057     std::cerr << "--" << opt
2058               << ": Value too large.  It should be less than or equal to "
2059               << std::numeric_limits<uint32_t>::max() << std::endl;
2060     return -1;
2061   }
2062 
2063   dst = *n;
2064 
2065   return 0;
2066 }
2067 } // namespace
2068 
2069 namespace {
make_http_authority(const Config & config)2070 std::string make_http_authority(const Config &config) {
2071   std::string host;
2072 
2073   if (util::numeric_host(config.host.c_str(), AF_INET6)) {
2074     host += '[';
2075     host += config.host;
2076     host += ']';
2077   } else {
2078     host = config.host;
2079   }
2080 
2081   if (config.port != config.default_port) {
2082     host += ':';
2083     host += util::utos(config.port);
2084   }
2085 
2086   return host;
2087 }
2088 } // namespace
2089 
2090 namespace {
print_version(std::ostream & out)2091 void print_version(std::ostream &out) {
2092   out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
2093 }
2094 } // namespace
2095 
2096 namespace {
print_usage(std::ostream & out)2097 void print_usage(std::ostream &out) {
2098   out << R"(Usage: h2load [OPTIONS]... [URI]...
2099 benchmarking tool for HTTP/2 server)"
2100       << std::endl;
2101 }
2102 } // namespace
2103 
2104 namespace {
2105 constexpr auto DEFAULT_ALPN_LIST = "h2,h2-16,h2-14,http/1.1"_sr;
2106 } // namespace
2107 
2108 namespace {
print_help(std::ostream & out)2109 void print_help(std::ostream &out) {
2110   print_usage(out);
2111 
2112   auto config = Config();
2113 
2114   out << R"(
2115   <URI>       Specify URI to access.   Multiple URIs can be specified.
2116               URIs are used  in this order for each  client.  All URIs
2117               are used, then  first URI is used and then  2nd URI, and
2118               so  on.  The  scheme, host  and port  in the  subsequent
2119               URIs, if present,  are ignored.  Those in  the first URI
2120               are used solely.  Definition of a base URI overrides all
2121               scheme, host or port values.
2122 Options:
2123   -n, --requests=<N>
2124               Number of  requests across all  clients.  If it  is used
2125               with --timing-script-file option,  this option specifies
2126               the number of requests  each client performs rather than
2127               the number of requests  across all clients.  This option
2128               is ignored if timing-based  benchmarking is enabled (see
2129               --duration option).
2130               Default: )"
2131       << config.nreqs << R"(
2132   -c, --clients=<N>
2133               Number  of concurrent  clients.   With  -r option,  this
2134               specifies the maximum number of connections to be made.
2135               Default: )"
2136       << config.nclients << R"(
2137   -t, --threads=<N>
2138               Number of native threads.
2139               Default: )"
2140       << config.nthreads << R"(
2141   -i, --input-file=<PATH>
2142               Path of a file with multiple URIs are separated by EOLs.
2143               This option will disable URIs getting from command-line.
2144               If '-' is given as <PATH>, URIs will be read from stdin.
2145               URIs are used  in this order for each  client.  All URIs
2146               are used, then  first URI is used and then  2nd URI, and
2147               so  on.  The  scheme, host  and port  in the  subsequent
2148               URIs, if present,  are ignored.  Those in  the first URI
2149               are used solely.  Definition of a base URI overrides all
2150               scheme, host or port values.
2151   -m, --max-concurrent-streams=<N>
2152               Max  concurrent  streams  to issue  per  session.   When
2153               http/1.1  is used,  this  specifies the  number of  HTTP
2154               pipelining requests in-flight.
2155               Default: 1
2156   -f, --max-frame-size=<SIZE>
2157               Maximum frame size that the local endpoint is willing to
2158               receive.
2159               Default: )"
2160       << util::utos_unit(config.max_frame_size) << R"(
2161   -w, --window-bits=<N>
2162               Sets the stream level initial window size to (2**<N>)-1.
2163               For QUIC, <N> is capped to 26 (roughly 64MiB).
2164               Default: )"
2165       << config.window_bits << R"(
2166   -W, --connection-window-bits=<N>
2167               Sets  the  connection  level   initial  window  size  to
2168               (2**<N>)-1.
2169               Default: )"
2170       << config.connection_window_bits << R"(
2171   -H, --header=<HEADER>
2172               Add/Override a header to the requests.
2173   --ciphers=<SUITE>
2174               Set  allowed cipher  list  for TLSv1.2  or earlier.   The
2175               format of the string is described in OpenSSL ciphers(1).
2176               Default: )"
2177       << config.ciphers << R"(
2178   --tls13-ciphers=<SUITE>
2179               Set allowed cipher list for  TLSv1.3.  The format of the
2180               string is described in OpenSSL ciphers(1).
2181               Default: )"
2182       << config.tls13_ciphers << R"(
2183   -p, --no-tls-proto=<PROTOID>
2184               Specify ALPN identifier of the  protocol to be used when
2185               accessing http URI without SSL/TLS.
2186               Available protocols: )"
2187       << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1 << R"(
2188               Default: )"
2189       << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
2190   -d, --data=<PATH>
2191               Post FILE to  server.  The request method  is changed to
2192               POST.   For  http/1.1 connection,  if  -d  is used,  the
2193               maximum number of in-flight pipelined requests is set to
2194               1.
2195   -r, --rate=<N>
2196               Specifies  the  fixed  rate  at  which  connections  are
2197               created.   The   rate  must   be  a   positive  integer,
2198               representing the  number of  connections to be  made per
2199               rate period.   The maximum  number of connections  to be
2200               made  is  given  in  -c   option.   This  rate  will  be
2201               distributed among  threads as  evenly as  possible.  For
2202               example,  with   -t2  and   -r4,  each  thread   gets  2
2203               connections per period.  When the rate is 0, the program
2204               will run  as it  normally does, creating  connections at
2205               whatever variable rate it  wants.  The default value for
2206               this option is 0.  -r and -D are mutually exclusive.
2207   --rate-period=<DURATION>
2208               Specifies the time  period between creating connections.
2209               The period  must be a positive  number, representing the
2210               length of the period in time.  This option is ignored if
2211               the rate option is not used.  The default value for this
2212               option is 1s.
2213   -D, --duration=<DURATION>
2214               Specifies the main duration for the measurements in case
2215               of timing-based  benchmarking.  -D  and -r  are mutually
2216               exclusive.
2217   --warm-up-time=<DURATION>
2218               Specifies the  time  period  before  starting the actual
2219               measurements, in  case  of  timing-based benchmarking.
2220               Needs to provided along with -D option.
2221   -T, --connection-active-timeout=<DURATION>
2222               Specifies  the maximum  time that  h2load is  willing to
2223               keep a  connection open,  regardless of the  activity on
2224               said connection.  <DURATION> must be a positive integer,
2225               specifying the amount of time  to wait.  When no timeout
2226               value is  set (either  active or inactive),  h2load will
2227               keep  a  connection  open indefinitely,  waiting  for  a
2228               response.
2229   -N, --connection-inactivity-timeout=<DURATION>
2230               Specifies the amount  of time that h2load  is willing to
2231               wait to see activity  on a given connection.  <DURATION>
2232               must  be a  positive integer,  specifying the  amount of
2233               time  to wait.   When no  timeout value  is set  (either
2234               active or inactive), h2load  will keep a connection open
2235               indefinitely, waiting for a response.
2236   --timing-script-file=<PATH>
2237               Path of a file containing one or more lines separated by
2238               EOLs.  Each script line is composed of two tab-separated
2239               fields.  The first field represents the time offset from
2240               the start of execution, expressed as a positive value of
2241               milliseconds  with microsecond  resolution.  The  second
2242               field represents the URI.  This option will disable URIs
2243               getting from  command-line.  If '-' is  given as <PATH>,
2244               script lines will be read  from stdin.  Script lines are
2245               used in order for each client.   If -n is given, it must
2246               be less  than or  equal to the  number of  script lines,
2247               larger values are clamped to the number of script lines.
2248               If -n is not given,  the number of requests will default
2249               to the  number of  script lines.   The scheme,  host and
2250               port defined in  the first URI are  used solely.  Values
2251               contained  in  other  URIs,  if  present,  are  ignored.
2252               Definition of a  base URI overrides all  scheme, host or
2253               port   values.   --timing-script-file   and  --rps   are
2254               mutually exclusive.
2255   -B, --base-uri=(<URI>|unix:<PATH>)
2256               Specify URI from which the scheme, host and port will be
2257               used  for  all requests.   The  base  URI overrides  all
2258               values  defined either  at  the command  line or  inside
2259               input files.  If argument  starts with "unix:", then the
2260               rest  of the  argument will  be treated  as UNIX  domain
2261               socket path.   The connection is made  through that path
2262               instead of TCP.   In this case, scheme  is inferred from
2263               the first  URI appeared  in the  command line  or inside
2264               input files as usual.
2265   --alpn-list=<LIST>
2266               Comma delimited list of  ALPN protocol identifier sorted
2267               in the  order of preference.  That  means most desirable
2268               protocol comes  first.  The parameter must  be delimited
2269               by a single comma only  and any white spaces are treated
2270               as a part of protocol string.
2271               Default: )"
2272       << DEFAULT_ALPN_LIST << R"(
2273   --h1        Short        hand        for        --alpn-list=http/1.1
2274               --no-tls-proto=http/1.1,    which   effectively    force
2275               http/1.1 for both http and https URI.
2276   --header-table-size=<SIZE>
2277               Specify decoder header table size.
2278               Default: )"
2279       << util::utos_unit(config.header_table_size) << R"(
2280   --encoder-header-table-size=<SIZE>
2281               Specify encoder header table size.  The decoder (server)
2282               specifies  the maximum  dynamic table  size it  accepts.
2283               Then the negotiated dynamic table size is the minimum of
2284               this option value and the value which server specified.
2285               Default: )"
2286       << util::utos_unit(config.encoder_header_table_size) << R"(
2287   --log-file=<PATH>
2288               Write per-request information to a file as tab-separated
2289               columns: start  time as  microseconds since  epoch; HTTP
2290               status code;  microseconds until end of  response.  More
2291               columns may be added later.  Rows are ordered by end-of-
2292               response  time when  using  one worker  thread, but  may
2293               appear slightly  out of order with  multiple threads due
2294               to buffering.  Status code is -1 for failed streams.
2295   --qlog-file-base=<PATH>
2296               Enable qlog output and specify base file name for qlogs.
2297               Qlog is emitted  for each connection.  For  a given base
2298               name   "base",    each   output   file    name   becomes
2299               "base.M.N.sqlog" where M is worker ID and N is client ID
2300               (e.g. "base.0.3.sqlog").  Only effective in QUIC runs.
2301   --connect-to=<HOST>[:<PORT>]
2302               Host and port to connect  instead of using the authority
2303               in <URI>.
2304   --rps=<N>   Specify request  per second for each  client.  --rps and
2305               --timing-script-file are mutually exclusive.
2306   --groups=<GROUPS>
2307               Specify the supported groups.
2308               Default: )"
2309       << config.groups << R"(
2310   --no-udp-gso
2311               Disable UDP GSO.
2312   --max-udp-payload-size=<SIZE>
2313               Specify the maximum outgoing UDP datagram payload size.
2314   --ktls      Enable ktls.
2315   --sni=<DNSNAME>
2316               Send  <DNSNAME> in  TLS  SNI, overriding  the host  name
2317               specified in URI.
2318   -v, --verbose
2319               Output debug information.
2320   --version   Display version information and exit.
2321   -h, --help  Display this help and exit.
2322 
2323 --
2324 
2325   The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2326   10 * 1024).  Units are K, M and G (powers of 1024).
2327 
2328   The <DURATION> argument is an integer and an optional unit (e.g., 1s
2329   is 1 second and 500ms is 500 milliseconds).  Units are h, m, s or ms
2330   (hours, minutes, seconds and milliseconds, respectively).  If a unit
2331   is omitted, a second is used as unit.)"
2332       << std::endl;
2333 }
2334 } // namespace
2335 
main(int argc,char ** argv)2336 int main(int argc, char **argv) {
2337   std::string datafile;
2338   std::string logfile;
2339   bool nreqs_set_manually = false;
2340   while (1) {
2341     static int flag = 0;
2342     constexpr static option long_options[] = {
2343       {"requests", required_argument, nullptr, 'n'},
2344       {"clients", required_argument, nullptr, 'c'},
2345       {"data", required_argument, nullptr, 'd'},
2346       {"threads", required_argument, nullptr, 't'},
2347       {"max-concurrent-streams", required_argument, nullptr, 'm'},
2348       {"window-bits", required_argument, nullptr, 'w'},
2349       {"max-frame-size", required_argument, nullptr, 'f'},
2350       {"connection-window-bits", required_argument, nullptr, 'W'},
2351       {"input-file", required_argument, nullptr, 'i'},
2352       {"header", required_argument, nullptr, 'H'},
2353       {"no-tls-proto", required_argument, nullptr, 'p'},
2354       {"verbose", no_argument, nullptr, 'v'},
2355       {"help", no_argument, nullptr, 'h'},
2356       {"version", no_argument, &flag, 1},
2357       {"ciphers", required_argument, &flag, 2},
2358       {"rate", required_argument, nullptr, 'r'},
2359       {"connection-active-timeout", required_argument, nullptr, 'T'},
2360       {"connection-inactivity-timeout", required_argument, nullptr, 'N'},
2361       {"duration", required_argument, nullptr, 'D'},
2362       {"timing-script-file", required_argument, &flag, 3},
2363       {"base-uri", required_argument, nullptr, 'B'},
2364       {"npn-list", required_argument, &flag, 4},
2365       {"rate-period", required_argument, &flag, 5},
2366       {"h1", no_argument, &flag, 6},
2367       {"header-table-size", required_argument, &flag, 7},
2368       {"encoder-header-table-size", required_argument, &flag, 8},
2369       {"warm-up-time", required_argument, &flag, 9},
2370       {"log-file", required_argument, &flag, 10},
2371       {"connect-to", required_argument, &flag, 11},
2372       {"rps", required_argument, &flag, 12},
2373       {"groups", required_argument, &flag, 13},
2374       {"tls13-ciphers", required_argument, &flag, 14},
2375       {"no-udp-gso", no_argument, &flag, 15},
2376       {"qlog-file-base", required_argument, &flag, 16},
2377       {"max-udp-payload-size", required_argument, &flag, 17},
2378       {"ktls", no_argument, &flag, 18},
2379       {"alpn-list", required_argument, &flag, 19},
2380       {"sni", required_argument, &flag, 20},
2381       {nullptr, 0, nullptr, 0}};
2382     int option_index = 0;
2383     auto c = getopt_long(argc, argv,
2384                          "hvW:c:d:m:n:p:t:w:f:H:i:r:T:N:D:B:", long_options,
2385                          &option_index);
2386     if (c == -1) {
2387       break;
2388     }
2389     switch (c) {
2390     case 'n': {
2391       auto n = util::parse_uint(optarg);
2392       if (!n) {
2393         std::cerr << "-n: bad option value: " << optarg << std::endl;
2394         exit(EXIT_FAILURE);
2395       }
2396       config.nreqs = *n;
2397       nreqs_set_manually = true;
2398       break;
2399     }
2400     case 'c': {
2401       auto n = util::parse_uint(optarg);
2402       if (!n) {
2403         std::cerr << "-c: bad option value: " << optarg << std::endl;
2404         exit(EXIT_FAILURE);
2405       }
2406       config.nclients = *n;
2407       break;
2408     }
2409     case 'd':
2410       datafile = optarg;
2411       break;
2412     case 't': {
2413 #ifdef NOTHREADS
2414       std::cerr << "-t: WARNING: Threading disabled at build time, "
2415                 << "no threads created." << std::endl;
2416 #else
2417       auto n = util::parse_uint(optarg);
2418       if (!n) {
2419         std::cerr << "-t: bad option value: " << optarg << std::endl;
2420         exit(EXIT_FAILURE);
2421       }
2422       config.nthreads = *n;
2423 #endif // NOTHREADS
2424       break;
2425     }
2426     case 'm': {
2427       auto n = util::parse_uint(optarg);
2428       if (!n) {
2429         std::cerr << "-m: bad option value: " << optarg << std::endl;
2430         exit(EXIT_FAILURE);
2431       }
2432       config.max_concurrent_streams = *n;
2433       break;
2434     }
2435     case 'w':
2436     case 'W': {
2437       auto n = util::parse_uint(optarg);
2438       if (!n || n > 30) {
2439         std::cerr << "-" << static_cast<char>(c)
2440                   << ": specify the integer in the range [0, 30], inclusive"
2441                   << std::endl;
2442         exit(EXIT_FAILURE);
2443       }
2444       if (c == 'w') {
2445         config.window_bits = *n;
2446       } else {
2447         config.connection_window_bits = *n;
2448       }
2449       break;
2450     }
2451     case 'f': {
2452       auto n = util::parse_uint_with_unit(optarg);
2453       if (!n) {
2454         std::cerr << "--max-frame-size: bad option value: " << optarg
2455                   << std::endl;
2456         exit(EXIT_FAILURE);
2457       }
2458       if (static_cast<uint64_t>(*n) < 16_k) {
2459         std::cerr << "--max-frame-size: minimum 16384" << std::endl;
2460         exit(EXIT_FAILURE);
2461       }
2462       if (static_cast<uint64_t>(*n) > 16_m - 1) {
2463         std::cerr << "--max-frame-size: maximum 16777215" << std::endl;
2464         exit(EXIT_FAILURE);
2465       }
2466       config.max_frame_size = *n;
2467       break;
2468     }
2469     case 'H': {
2470       char *header = optarg;
2471       // Skip first possible ':' in the header name
2472       char *value = strchr(optarg + 1, ':');
2473       if (!value || (header[0] == ':' && header + 1 == value)) {
2474         std::cerr << "-H: invalid header: " << optarg << std::endl;
2475         exit(EXIT_FAILURE);
2476       }
2477       *value = 0;
2478       value++;
2479       while (isspace(*value)) {
2480         value++;
2481       }
2482       if (*value == 0) {
2483         // This could also be a valid case for suppressing a header
2484         // similar to curl
2485         std::cerr << "-H: invalid header - value missing: " << optarg
2486                   << std::endl;
2487         exit(EXIT_FAILURE);
2488       }
2489       // Note that there is no processing currently to handle multiple
2490       // message-header fields with the same field name
2491       config.custom_headers.emplace_back(header, value);
2492       util::inp_strlower(config.custom_headers.back().name);
2493       break;
2494     }
2495     case 'i':
2496       config.ifile = optarg;
2497       break;
2498     case 'p': {
2499       auto proto = StringRef{optarg};
2500       if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID ""_sr, proto)) {
2501         config.no_tls_proto = Config::PROTO_HTTP2;
2502       } else if (util::strieq(NGHTTP2_H1_1, proto)) {
2503         config.no_tls_proto = Config::PROTO_HTTP1_1;
2504       } else {
2505         std::cerr << "-p: unsupported protocol " << proto << std::endl;
2506         exit(EXIT_FAILURE);
2507       }
2508       break;
2509     }
2510     case 'r': {
2511       auto n = util::parse_uint(optarg);
2512       if (!n) {
2513         std::cerr << "-r: bad option value: " << optarg << std::endl;
2514         exit(EXIT_FAILURE);
2515       }
2516       if (n == 0) {
2517         std::cerr << "-r: the rate at which connections are made "
2518                   << "must be positive." << std::endl;
2519         exit(EXIT_FAILURE);
2520       }
2521       config.rate = *n;
2522       break;
2523     }
2524     case 'T': {
2525       auto d = util::parse_duration_with_unit(optarg);
2526       if (!d) {
2527         std::cerr << "-T: bad value for the conn_active_timeout wait time: "
2528                   << optarg << std::endl;
2529         exit(EXIT_FAILURE);
2530       }
2531       config.conn_active_timeout = *d;
2532       break;
2533     }
2534     case 'N': {
2535       auto d = util::parse_duration_with_unit(optarg);
2536       if (!d) {
2537         std::cerr << "-N: bad value for the conn_inactivity_timeout wait time: "
2538                   << optarg << std::endl;
2539         exit(EXIT_FAILURE);
2540       }
2541       config.conn_inactivity_timeout = *d;
2542       break;
2543     }
2544     case 'B': {
2545       auto arg = StringRef{optarg};
2546       config.base_uri = "";
2547       config.base_uri_unix = false;
2548 
2549       if (util::istarts_with(arg, UNIX_PATH_PREFIX)) {
2550         // UNIX domain socket path
2551         sockaddr_un un;
2552 
2553         auto path =
2554           StringRef{std::begin(arg) + UNIX_PATH_PREFIX.size(), std::end(arg)};
2555 
2556         if (path.size() == 0 || path.size() + 1 > sizeof(un.sun_path)) {
2557           std::cerr << "--base-uri: invalid UNIX domain socket path: " << arg
2558                     << std::endl;
2559           exit(EXIT_FAILURE);
2560         }
2561 
2562         config.base_uri_unix = true;
2563 
2564         auto &unix_addr = config.unix_addr;
2565         std::copy(std::begin(path), std::end(path), unix_addr.sun_path);
2566         unix_addr.sun_path[path.size()] = '\0';
2567         unix_addr.sun_family = AF_UNIX;
2568 
2569         break;
2570       }
2571 
2572       if (!parse_base_uri(arg)) {
2573         std::cerr << "--base-uri: invalid base URI: " << arg << std::endl;
2574         exit(EXIT_FAILURE);
2575       }
2576 
2577       config.base_uri = arg;
2578       break;
2579     }
2580     case 'D': {
2581       auto d = util::parse_duration_with_unit(optarg);
2582       if (!d) {
2583         std::cerr << "-D: value error " << optarg << std::endl;
2584         exit(EXIT_FAILURE);
2585       }
2586       config.duration = *d;
2587       break;
2588     }
2589     case 'v':
2590       config.verbose = true;
2591       break;
2592     case 'h':
2593       print_help(std::cout);
2594       exit(EXIT_SUCCESS);
2595     case '?':
2596       util::show_candidates(argv[optind - 1], long_options);
2597       exit(EXIT_FAILURE);
2598     case 0:
2599       switch (flag) {
2600       case 1:
2601         // version option
2602         print_version(std::cout);
2603         exit(EXIT_SUCCESS);
2604       case 2:
2605         // ciphers option
2606         config.ciphers = optarg;
2607         break;
2608       case 3:
2609         // timing-script option
2610         config.ifile = optarg;
2611         config.timing_script = true;
2612         break;
2613       case 5: {
2614         // rate-period
2615         auto d = util::parse_duration_with_unit(optarg);
2616         if (!d) {
2617           std::cerr << "--rate-period: value error " << optarg << std::endl;
2618           exit(EXIT_FAILURE);
2619         }
2620         config.rate_period = *d;
2621         break;
2622       }
2623       case 6:
2624         // --h1
2625         config.alpn_list = util::parse_config_str_list("http/1.1"_sr);
2626         config.no_tls_proto = Config::PROTO_HTTP1_1;
2627         break;
2628       case 7:
2629         // --header-table-size
2630         if (parse_header_table_size(config.header_table_size,
2631                                     "header-table-size", optarg) != 0) {
2632           exit(EXIT_FAILURE);
2633         }
2634         break;
2635       case 8:
2636         // --encoder-header-table-size
2637         if (parse_header_table_size(config.encoder_header_table_size,
2638                                     "encoder-header-table-size", optarg) != 0) {
2639           exit(EXIT_FAILURE);
2640         }
2641         break;
2642       case 9: {
2643         // --warm-up-time
2644         auto d = util::parse_duration_with_unit(optarg);
2645         if (!d) {
2646           std::cerr << "--warm-up-time: value error " << optarg << std::endl;
2647           exit(EXIT_FAILURE);
2648         }
2649         config.warm_up_time = *d;
2650         break;
2651       }
2652       case 10:
2653         // --log-file
2654         logfile = optarg;
2655         break;
2656       case 11: {
2657         // --connect-to
2658         auto p = util::split_hostport(StringRef{optarg});
2659         int64_t port = 0;
2660         if (p.first.empty() ||
2661             (!p.second.empty() &&
2662              (port = util::parse_uint(p.second).value_or(-1)) == -1)) {
2663           std::cerr << "--connect-to: Invalid value " << optarg << std::endl;
2664           exit(EXIT_FAILURE);
2665         }
2666         config.connect_to_host = p.first;
2667         config.connect_to_port = port;
2668         break;
2669       }
2670       case 12: {
2671         char *end;
2672         auto v = std::strtod(optarg, &end);
2673         if (end == optarg || *end != '\0' || !std::isfinite(v) ||
2674             1. / v < 1e-6) {
2675           std::cerr << "--rps: Invalid value " << optarg << std::endl;
2676           exit(EXIT_FAILURE);
2677         }
2678         config.rps = v;
2679         break;
2680       }
2681       case 13:
2682         // --groups
2683         config.groups = optarg;
2684         break;
2685       case 14:
2686         // --tls13-ciphers
2687         config.tls13_ciphers = optarg;
2688         break;
2689       case 15:
2690         // --no-udp-gso
2691         config.no_udp_gso = true;
2692         break;
2693       case 16:
2694         // --qlog-file-base
2695         config.qlog_file_base = optarg;
2696         break;
2697       case 17: {
2698         // --max-udp-payload-size
2699         auto n = util::parse_uint_with_unit(optarg);
2700         if (!n) {
2701           std::cerr << "--max-udp-payload-size: bad option value: " << optarg
2702                     << std::endl;
2703           exit(EXIT_FAILURE);
2704         }
2705         if (static_cast<uint64_t>(*n) > 64_k) {
2706           std::cerr << "--max-udp-payload-size: must not exceed 65536"
2707                     << std::endl;
2708           exit(EXIT_FAILURE);
2709         }
2710         config.max_udp_payload_size = *n;
2711         break;
2712       }
2713       case 18:
2714         // --ktls
2715         config.ktls = true;
2716         break;
2717       case 4:
2718         // npn-list option
2719         std::cerr << "--npn-list: deprecated.  Use --alpn-list instead."
2720                   << std::endl;
2721         // fall through
2722       case 19:
2723         // alpn-list option
2724         config.alpn_list = util::parse_config_str_list(StringRef{optarg});
2725         break;
2726       case 20:
2727         // --sni
2728         config.sni = optarg;
2729         break;
2730       }
2731       break;
2732     default:
2733       break;
2734     }
2735   }
2736 
2737   if (argc == optind) {
2738     if (config.ifile.empty()) {
2739       std::cerr << "no URI or input file given" << std::endl;
2740       exit(EXIT_FAILURE);
2741     }
2742   }
2743 
2744   if (config.nclients == 0) {
2745     std::cerr << "-c: the number of clients must be strictly greater than 0."
2746               << std::endl;
2747     exit(EXIT_FAILURE);
2748   }
2749 
2750   if (config.alpn_list.empty()) {
2751     config.alpn_list = util::parse_config_str_list(DEFAULT_ALPN_LIST);
2752   }
2753 
2754   // serialize the APLN tokens
2755   for (auto &proto : config.alpn_list) {
2756     proto.insert(proto.begin(), static_cast<unsigned char>(proto.size()));
2757   }
2758 
2759   std::vector<std::string> reqlines;
2760 
2761   if (config.ifile.empty()) {
2762     std::vector<std::string> uris;
2763     std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
2764     reqlines = parse_uris(std::begin(uris), std::end(uris));
2765   } else {
2766     std::vector<std::string> uris;
2767     if (!config.timing_script) {
2768       if (config.ifile == "-") {
2769         uris = read_uri_from_file(std::cin);
2770       } else {
2771         std::ifstream infile(config.ifile);
2772         if (!infile) {
2773           std::cerr << "cannot read input file: " << config.ifile << std::endl;
2774           exit(EXIT_FAILURE);
2775         }
2776 
2777         uris = read_uri_from_file(infile);
2778       }
2779     } else {
2780       if (config.ifile == "-") {
2781         read_script_from_file(std::cin, config.timings, uris);
2782       } else {
2783         std::ifstream infile(config.ifile);
2784         if (!infile) {
2785           std::cerr << "cannot read input file: " << config.ifile << std::endl;
2786           exit(EXIT_FAILURE);
2787         }
2788 
2789         read_script_from_file(infile, config.timings, uris);
2790       }
2791 
2792       if (nreqs_set_manually) {
2793         if (config.nreqs > uris.size()) {
2794           std::cerr << "-n: the number of requests must be less than or equal "
2795                        "to the number of timing script entries. Setting number "
2796                        "of requests to "
2797                     << uris.size() << std::endl;
2798 
2799           config.nreqs = uris.size();
2800         }
2801       } else {
2802         config.nreqs = uris.size();
2803       }
2804     }
2805 
2806     reqlines = parse_uris(std::begin(uris), std::end(uris));
2807   }
2808 
2809   if (reqlines.empty()) {
2810     std::cerr << "No URI given" << std::endl;
2811     exit(EXIT_FAILURE);
2812   }
2813 
2814   if (config.is_timing_based_mode() && config.is_rate_mode()) {
2815     std::cerr << "-r, -D: they are mutually exclusive." << std::endl;
2816     exit(EXIT_FAILURE);
2817   }
2818 
2819   if (config.timing_script && config.rps_enabled()) {
2820     std::cerr << "--timing-script-file, --rps: they are mutually exclusive."
2821               << std::endl;
2822     exit(EXIT_FAILURE);
2823   }
2824 
2825   if (config.nreqs == 0 && !config.is_timing_based_mode()) {
2826     std::cerr << "-n: the number of requests must be strictly greater than 0 "
2827                  "if timing-based test is not being run."
2828               << std::endl;
2829     exit(EXIT_FAILURE);
2830   }
2831 
2832   if (config.max_concurrent_streams == 0) {
2833     std::cerr << "-m: the max concurrent streams must be strictly greater "
2834               << "than 0." << std::endl;
2835     exit(EXIT_FAILURE);
2836   }
2837 
2838   if (config.nthreads == 0) {
2839     std::cerr << "-t: the number of threads must be strictly greater than 0."
2840               << std::endl;
2841     exit(EXIT_FAILURE);
2842   }
2843 
2844   if (config.nthreads > std::thread::hardware_concurrency()) {
2845     std::cerr << "-t: warning: the number of threads is greater than hardware "
2846               << "cores." << std::endl;
2847   }
2848 
2849   // With timing script, we don't distribute config.nreqs to each
2850   // client or thread.
2851   if (!config.timing_script && config.nreqs < config.nclients &&
2852       !config.is_timing_based_mode()) {
2853     std::cerr << "-n, -c: the number of requests must be greater than or "
2854               << "equal to the clients." << std::endl;
2855     exit(EXIT_FAILURE);
2856   }
2857 
2858   if (config.nclients < config.nthreads) {
2859     std::cerr << "-c, -t: the number of clients must be greater than or equal "
2860               << "to the number of threads." << std::endl;
2861     exit(EXIT_FAILURE);
2862   }
2863 
2864   if (config.is_timing_based_mode()) {
2865     config.nreqs = 0;
2866   }
2867 
2868   if (config.is_rate_mode()) {
2869     if (config.rate < config.nthreads) {
2870       std::cerr << "-r, -t: the connection rate must be greater than or equal "
2871                 << "to the number of threads." << std::endl;
2872       exit(EXIT_FAILURE);
2873     }
2874 
2875     if (config.rate > config.nclients) {
2876       std::cerr << "-r, -c: the connection rate must be smaller than or equal "
2877                    "to the number of clients."
2878                 << std::endl;
2879       exit(EXIT_FAILURE);
2880     }
2881   }
2882 
2883   if (!datafile.empty()) {
2884     config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
2885     if (config.data_fd == -1) {
2886       std::cerr << "-d: Could not open file " << datafile << std::endl;
2887       exit(EXIT_FAILURE);
2888     }
2889     struct stat data_stat;
2890     if (fstat(config.data_fd, &data_stat) == -1) {
2891       std::cerr << "-d: Could not stat file " << datafile << std::endl;
2892       exit(EXIT_FAILURE);
2893     }
2894     config.data_length = data_stat.st_size;
2895     auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED,
2896                      config.data_fd, 0);
2897     if (addr == MAP_FAILED) {
2898       std::cerr << "-d: Could not mmap file " << datafile << std::endl;
2899       exit(EXIT_FAILURE);
2900     }
2901     config.data = static_cast<uint8_t *>(addr);
2902   }
2903 
2904   if (!logfile.empty()) {
2905     config.log_fd = open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND,
2906                          S_IRUSR | S_IWUSR | S_IRGRP);
2907     if (config.log_fd == -1) {
2908       std::cerr << "--log-file: Could not open file " << logfile << std::endl;
2909       exit(EXIT_FAILURE);
2910     }
2911   }
2912 
2913   if (!config.qlog_file_base.empty() && !config.is_quic()) {
2914     std::cerr << "Warning: --qlog-file-base: only effective in quic, ignoring."
2915               << std::endl;
2916   }
2917 
2918   struct sigaction act {};
2919   act.sa_handler = SIG_IGN;
2920   sigaction(SIGPIPE, &act, nullptr);
2921 
2922   auto ssl_ctx = SSL_CTX_new(TLS_client_method());
2923   if (!ssl_ctx) {
2924     std::cerr << "Failed to create SSL_CTX: "
2925               << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2926     exit(EXIT_FAILURE);
2927   }
2928 
2929   auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2930                   SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2931                   SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2932 
2933 #ifdef SSL_OP_ENABLE_KTLS
2934   if (config.ktls) {
2935     ssl_opts |= SSL_OP_ENABLE_KTLS;
2936   }
2937 #endif // SSL_OP_ENABLE_KTLS
2938 
2939   SSL_CTX_set_options(ssl_ctx, ssl_opts);
2940   SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2941   SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2942 
2943   if (config.is_quic()) {
2944 #ifdef ENABLE_HTTP3
2945 #  ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
2946     if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) {
2947       std::cerr << "ngtcp2_crypto_quictls_configure_client_context failed"
2948                 << std::endl;
2949       exit(EXIT_FAILURE);
2950     }
2951 #  endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
2952 #  ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
2953     if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
2954       std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed"
2955                 << std::endl;
2956       exit(EXIT_FAILURE);
2957     }
2958 #  endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
2959 #  ifdef HAVE_LIBNGTCP2_CRYPTO_WOLFSSL
2960     if (ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
2961       std::cerr << "ngtcp2_crypto_wolfssl_configure_client_context failed"
2962                 << std::endl;
2963       exit(EXIT_FAILURE);
2964     }
2965 #  endif // HAVE_LIBNGTCP2_CRYPTO_WOLFSSL
2966 #endif   // ENABLE_HTTP3
2967   } else if (nghttp2::tls::ssl_ctx_set_proto_versions(
2968                ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2969                nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2970     std::cerr << "Could not set TLS versions" << std::endl;
2971     exit(EXIT_FAILURE);
2972   }
2973 
2974   if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) {
2975     std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers
2976               << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
2977               << std::endl;
2978     exit(EXIT_FAILURE);
2979   }
2980 
2981 #if defined(NGHTTP2_GENUINE_OPENSSL) ||                                        \
2982   defined(NGHTTP2_OPENSSL_IS_LIBRESSL) || defined(NGHTTP2_OPENSSL_IS_WOLFSSL)
2983   if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) {
2984     std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers
2985               << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
2986               << std::endl;
2987     exit(EXIT_FAILURE);
2988   }
2989 #endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL ||
2990        // NGHTTP2_OPENSSL_IS_WOLFSSL
2991 
2992 #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL
2993   // Passing X25519 to SSL_CTX_set1_groups_list fails for some reason.
2994   if (SSL_CTX_set1_curves_list(
2995         ssl_ctx, const_cast<char *>(config.groups.c_str())) != 1) {
2996     std::cerr << "SSL_CTX_set1_curves_list failed: "
2997               << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2998     exit(EXIT_FAILURE);
2999   }
3000 #else  // !NGHTTP2_OPENSSL_IS_WOLFSSL
3001   if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) {
3002     std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
3003     exit(EXIT_FAILURE);
3004   }
3005 #endif // !NGHTTP2_OPENSSL_IS_WOLFSSL
3006 
3007   std::vector<unsigned char> proto_list;
3008   for (const auto &proto : config.alpn_list) {
3009     std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list));
3010   }
3011 
3012   SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
3013 
3014   if (tls::setup_keylog_callback(ssl_ctx) != 0) {
3015     std::cerr << "Failed to setup keylog" << std::endl;
3016 
3017     exit(EXIT_FAILURE);
3018   }
3019 
3020 #if defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && defined(HAVE_LIBBROTLI)
3021   if (!SSL_CTX_add_cert_compression_alg(
3022         ssl_ctx, nghttp2::tls::CERTIFICATE_COMPRESSION_ALGO_BROTLI,
3023         nghttp2::tls::cert_compress, nghttp2::tls::cert_decompress)) {
3024     std::cerr << "SSL_CTX_add_cert_compression_alg failed" << std::endl;
3025     exit(EXIT_FAILURE);
3026   }
3027 #endif // NGHTTP2_OPENSSL_IS_BORINGSSL && HAVE_LIBBROTLI
3028 
3029   std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
3030   Headers shared_nva;
3031   shared_nva.emplace_back(":scheme", config.scheme);
3032   shared_nva.emplace_back(":authority", make_http_authority(config));
3033   shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
3034   shared_nva.emplace_back("user-agent", user_agent);
3035 
3036   // list header fields that can be overridden.
3037   auto override_hdrs = std::to_array<std::string_view>(
3038     {":authority", "host", ":method", ":scheme", "user-agent"});
3039 
3040   for (auto &kv : config.custom_headers) {
3041     if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
3042                   kv.name) != std::end(override_hdrs)) {
3043       // override header
3044       for (auto &nv : shared_nva) {
3045         if ((nv.name == ":authority" && kv.name == "host") ||
3046             (nv.name == kv.name)) {
3047           nv.value = kv.value;
3048         }
3049       }
3050     } else {
3051       // add additional headers
3052       shared_nva.push_back(kv);
3053     }
3054   }
3055 
3056   std::string content_length_str;
3057   if (config.data_fd != -1) {
3058     content_length_str = util::utos(config.data_length);
3059   }
3060 
3061   auto method_it =
3062     std::find_if(std::begin(shared_nva), std::end(shared_nva),
3063                  [](const Header &nv) { return nv.name == ":method"; });
3064   assert(method_it != std::end(shared_nva));
3065 
3066   config.h1reqs.reserve(reqlines.size());
3067   config.nva.reserve(reqlines.size());
3068 
3069   for (auto &req : reqlines) {
3070     // For HTTP/1.1
3071     auto h1req = (*method_it).value;
3072     h1req += ' ';
3073     h1req += req;
3074     h1req += " HTTP/1.1\r\n";
3075     for (auto &nv : shared_nva) {
3076       if (nv.name == ":authority") {
3077         h1req += "Host: ";
3078         h1req += nv.value;
3079         h1req += "\r\n";
3080         continue;
3081       }
3082       if (nv.name[0] == ':') {
3083         continue;
3084       }
3085       h1req += nv.name;
3086       h1req += ": ";
3087       h1req += nv.value;
3088       h1req += "\r\n";
3089     }
3090 
3091     if (!content_length_str.empty()) {
3092       h1req += "Content-Length: ";
3093       h1req += content_length_str;
3094       h1req += "\r\n";
3095     }
3096     h1req += "\r\n";
3097 
3098     config.h1reqs.push_back(std::move(h1req));
3099 
3100     // For nghttp2
3101     std::vector<nghttp2_nv> nva;
3102     // 2 for :path, and possible content-length
3103     nva.reserve(2 + shared_nva.size());
3104 
3105     nva.push_back(http2::make_field_v(":path"_sr, req));
3106 
3107     for (auto &nv : shared_nva) {
3108       nva.push_back(http2::make_field_nv(nv.name, nv.value));
3109     }
3110 
3111     if (!content_length_str.empty()) {
3112       nva.push_back(
3113         http2::make_field_nv("content-length"_sr, content_length_str));
3114     }
3115 
3116     config.nva.push_back(std::move(nva));
3117   }
3118 
3119   // Don't DOS our server!
3120   if (config.host == "nghttp2.org") {
3121     std::cerr << "Using h2load against public server " << config.host
3122               << " should be prohibited." << std::endl;
3123     exit(EXIT_FAILURE);
3124   }
3125 
3126   resolve_host();
3127 
3128   std::cout << "starting benchmark..." << std::endl;
3129 
3130   std::vector<std::unique_ptr<Worker>> workers;
3131   workers.reserve(config.nthreads);
3132 
3133 #ifndef NOTHREADS
3134   size_t nreqs_per_thread = 0;
3135   size_t nreqs_rem = 0;
3136 
3137   if (!config.timing_script) {
3138     nreqs_per_thread = config.nreqs / config.nthreads;
3139     nreqs_rem = config.nreqs % config.nthreads;
3140   }
3141 
3142   auto nclients_per_thread = config.nclients / config.nthreads;
3143   auto nclients_rem = config.nclients % config.nthreads;
3144 
3145   auto rate_per_thread = config.rate / config.nthreads;
3146   auto rate_per_thread_rem = config.rate % config.nthreads;
3147 
3148   size_t max_samples_per_thread =
3149     std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
3150 
3151   std::mutex mu;
3152   std::condition_variable cv;
3153   auto ready = false;
3154 
3155   std::vector<std::future<void>> futures;
3156   for (size_t i = 0; i < config.nthreads; ++i) {
3157     auto rate = rate_per_thread;
3158     if (rate_per_thread_rem > 0) {
3159       --rate_per_thread_rem;
3160       ++rate;
3161     }
3162     auto nclients = nclients_per_thread;
3163     if (nclients_rem > 0) {
3164       --nclients_rem;
3165       ++nclients;
3166     }
3167 
3168     size_t nreqs;
3169     if (config.timing_script) {
3170       // With timing script, each client issues config.nreqs requests.
3171       // We divide nreqs by number of clients in Worker ctor to
3172       // distribute requests to those clients evenly, so multiply
3173       // config.nreqs here by config.nclients.
3174       nreqs = config.nreqs * nclients;
3175     } else {
3176       nreqs = nreqs_per_thread;
3177       if (nreqs_rem > 0) {
3178         --nreqs_rem;
3179         ++nreqs;
3180       }
3181     }
3182 
3183     workers.push_back(
3184       create_worker(i, ssl_ctx, nreqs, nclients, rate, max_samples_per_thread));
3185     auto &worker = workers.back();
3186     futures.push_back(
3187       std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
3188         {
3189           std::unique_lock<std::mutex> ulk(mu);
3190           cv.wait(ulk, [&ready] { return ready; });
3191         }
3192         worker->run();
3193 
3194 #  ifdef NGHTTP2_OPENSSL_IS_WOLFSSL
3195         wc_ecc_fp_free();
3196 #  endif // NGHTTP2_OPENSSL_IS_WOLFSSL
3197       }));
3198   }
3199 
3200   {
3201     std::lock_guard<std::mutex> lg(mu);
3202     ready = true;
3203     cv.notify_all();
3204   }
3205 
3206   auto start = std::chrono::steady_clock::now();
3207 
3208   for (auto &fut : futures) {
3209     fut.get();
3210   }
3211 
3212 #else  // NOTHREADS
3213   auto rate = config.rate;
3214   auto nclients = config.nclients;
3215   auto nreqs =
3216     config.timing_script ? config.nreqs * config.nclients : config.nreqs;
3217 
3218   workers.push_back(
3219     create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
3220 
3221   auto start = std::chrono::steady_clock::now();
3222 
3223   workers.back()->run();
3224 #endif // NOTHREADS
3225 
3226   auto end = std::chrono::steady_clock::now();
3227   auto duration =
3228     std::chrono::duration_cast<std::chrono::microseconds>(end - start);
3229 
3230   Stats stats(0, 0);
3231   for (const auto &w : workers) {
3232     const auto &s = w->stats;
3233 
3234     stats.req_todo += s.req_todo;
3235     stats.req_started += s.req_started;
3236     stats.req_done += s.req_done;
3237     stats.req_timedout += s.req_timedout;
3238     stats.req_success += s.req_success;
3239     stats.req_status_success += s.req_status_success;
3240     stats.req_failed += s.req_failed;
3241     stats.req_error += s.req_error;
3242     stats.bytes_total += s.bytes_total;
3243     stats.bytes_head += s.bytes_head;
3244     stats.bytes_head_decomp += s.bytes_head_decomp;
3245     stats.bytes_body += s.bytes_body;
3246     stats.udp_dgram_recv += s.udp_dgram_recv;
3247     stats.udp_dgram_sent += s.udp_dgram_sent;
3248 
3249     for (size_t i = 0; i < stats.status.size(); ++i) {
3250       stats.status[i] += s.status[i];
3251     }
3252   }
3253 
3254   auto ts = process_time_stats(workers);
3255 
3256   // Requests which have not been issued due to connection errors, are
3257   // counted towards req_failed and req_error.
3258   auto req_not_issued =
3259     (stats.req_todo - stats.req_status_success - stats.req_failed);
3260   stats.req_failed += req_not_issued;
3261   stats.req_error += req_not_issued;
3262 
3263   // UI is heavily inspired by weighttp[1] and wrk[2]
3264   //
3265   // [1] https://github.com/lighttpd/weighttp
3266   // [2] https://github.com/wg/wrk
3267   double rps = 0;
3268   int64_t bps = 0;
3269   if (duration.count() > 0) {
3270     if (config.is_timing_based_mode()) {
3271       // we only want to consider the main duration if warm-up is given
3272       rps = stats.req_success / config.duration;
3273       bps = stats.bytes_total / config.duration;
3274     } else {
3275       auto secd = std::chrono::duration_cast<
3276         std::chrono::duration<double, std::chrono::seconds::period>>(duration);
3277       rps = stats.req_success / secd.count();
3278       bps = stats.bytes_total / secd.count();
3279     }
3280   }
3281 
3282   double header_space_savings = 0.;
3283   if (stats.bytes_head_decomp > 0) {
3284     header_space_savings =
3285       1. - static_cast<double>(stats.bytes_head) / stats.bytes_head_decomp;
3286   }
3287 
3288   std::cout << std::fixed << std::setprecision(2) << R"(
3289 finished in )"
3290             << util::format_duration(duration) << ", " << rps << " req/s, "
3291             << util::utos_funit(bps) << R"(B/s
3292 requests: )" << stats.req_todo
3293             << " total, " << stats.req_started << " started, " << stats.req_done
3294             << " done, " << stats.req_status_success << " succeeded, "
3295             << stats.req_failed << " failed, " << stats.req_error
3296             << " errored, " << stats.req_timedout << R"( timeout
3297 status codes: )"
3298             << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
3299             << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
3300 traffic: )" << util::utos_funit(stats.bytes_total)
3301             << "B (" << stats.bytes_total << ") total, "
3302             << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
3303             << ") headers (space savings " << header_space_savings * 100
3304             << "%), " << util::utos_funit(stats.bytes_body) << "B ("
3305             << stats.bytes_body << R"() data)" << std::endl;
3306 #ifdef ENABLE_HTTP3
3307   if (config.is_quic()) {
3308     std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, "
3309               << stats.udp_dgram_recv << " received" << std::endl;
3310   }
3311 #endif // ENABLE_HTTP3
3312   std::cout
3313     << R"(                     min         max         mean         sd        +/- sd
3314 time for request: )"
3315     << std::setw(10) << util::format_duration(ts.request.min) << "  "
3316     << std::setw(10) << util::format_duration(ts.request.max) << "  "
3317     << std::setw(10) << util::format_duration(ts.request.mean) << "  "
3318     << std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9)
3319     << util::dtos(ts.request.within_sd) << "%"
3320     << "\ntime for connect: " << std::setw(10)
3321     << util::format_duration(ts.connect.min) << "  " << std::setw(10)
3322     << util::format_duration(ts.connect.max) << "  " << std::setw(10)
3323     << util::format_duration(ts.connect.mean) << "  " << std::setw(10)
3324     << util::format_duration(ts.connect.sd) << std::setw(9)
3325     << util::dtos(ts.connect.within_sd) << "%"
3326     << "\ntime to 1st byte: " << std::setw(10)
3327     << util::format_duration(ts.ttfb.min) << "  " << std::setw(10)
3328     << util::format_duration(ts.ttfb.max) << "  " << std::setw(10)
3329     << util::format_duration(ts.ttfb.mean) << "  " << std::setw(10)
3330     << util::format_duration(ts.ttfb.sd) << std::setw(9)
3331     << util::dtos(ts.ttfb.within_sd) << "%"
3332     << "\nreq/s           : " << std::setw(10) << ts.rps.min << "  "
3333     << std::setw(10) << ts.rps.max << "  " << std::setw(10) << ts.rps.mean
3334     << "  " << std::setw(10) << ts.rps.sd << std::setw(9)
3335     << util::dtos(ts.rps.within_sd) << "%" << std::endl;
3336 
3337   SSL_CTX_free(ssl_ctx);
3338 
3339   if (config.log_fd != -1) {
3340     close(config.log_fd);
3341   }
3342 
3343   return 0;
3344 }
3345 
3346 } // namespace h2load
3347 
main(int argc,char ** argv)3348 int main(int argc, char **argv) { return h2load::main(argc, argv); }
3349