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