Imported Upstream version 1.0.0
[platform/upstream/nghttp2.git] / src / h2load.cc
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
48 #ifdef HAVE_SPDYLAY
49 #include <spdylay/spdylay.h>
50 #endif // HAVE_SPDYLAY
51
52 #include <openssl/err.h>
53 #include <openssl/conf.h>
54
55 #include "http-parser/http_parser.h"
56
57 #include "h2load_http2_session.h"
58 #ifdef HAVE_SPDYLAY
59 #include "h2load_spdy_session.h"
60 #endif // HAVE_SPDYLAY
61 #include "ssl.h"
62 #include "http2.h"
63 #include "util.h"
64 #include "template.h"
65
66 #ifndef O_BINARY
67 #define O_BINARY (0)
68 #endif // O_BINARY
69
70 using namespace nghttp2;
71
72 namespace h2load {
73
74 Config::Config()
75     : data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
76       max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
77       no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0), default_port(0),
78       verbose(false) {}
79
80 Config::~Config() {
81   freeaddrinfo(addrs);
82
83   if (data_fd != -1) {
84     close(data_fd);
85   }
86 }
87
88 Config config;
89
90 namespace {
91 void debug(const char *format, ...) {
92   if (config.verbose) {
93     fprintf(stderr, "[DEBUG] ");
94     va_list ap;
95     va_start(ap, format);
96     vfprintf(stderr, format, ap);
97     va_end(ap);
98   }
99 }
100 } // namespace
101
102 namespace {
103 void debug_nextproto_error() {
104 #ifdef HAVE_SPDYLAY
105   debug("no supported protocol was negotiated, expected: %s, "
106         "spdy/2, spdy/3, spdy/3.1\n",
107         NGHTTP2_PROTO_VERSION_ID);
108 #else  // !HAVE_SPDYLAY
109   debug("no supported protocol was negotiated, expected: %s\n",
110         NGHTTP2_PROTO_VERSION_ID);
111 #endif // !HAVE_SPDYLAY
112 }
113 } // namespace
114
115 RequestStat::RequestStat() : data_offset(0), completed(false) {}
116
117 Stats::Stats(size_t req_todo)
118     : req_todo(0), req_started(0), req_done(0), req_success(0),
119       req_status_success(0), req_failed(0), req_error(0), bytes_total(0),
120       bytes_head(0), bytes_body(0), status(), req_stats(req_todo) {}
121
122 Stream::Stream() : status_success(-1) {}
123
124 namespace {
125 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
126   auto client = static_cast<Client *>(w->data);
127   auto rv = client->do_write();
128   if (rv == Client::ERR_CONNECT_FAIL) {
129     client->disconnect();
130     rv = client->connect();
131     if (rv != 0) {
132       client->fail();
133       return;
134     }
135     return;
136   }
137   if (rv != 0) {
138     client->fail();
139   }
140 }
141 } // namespace
142
143 namespace {
144 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
145   auto client = static_cast<Client *>(w->data);
146   if (client->do_read() != 0) {
147     client->fail();
148     return;
149   }
150   writecb(loop, &client->wev, revents);
151   // client->disconnect() and client->fail() may be called
152 }
153 } // namespace
154
155 Client::Client(Worker *worker, size_t req_todo)
156     : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
157       state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo),
158       req_started(0), req_done(0), fd(-1) {
159   ev_io_init(&wev, writecb, 0, EV_WRITE);
160   ev_io_init(&rev, readcb, 0, EV_READ);
161
162   wev.data = this;
163   rev.data = this;
164 }
165
166 Client::~Client() { disconnect(); }
167
168 int Client::do_read() { return readfn(*this); }
169 int Client::do_write() { return writefn(*this); }
170
171 int Client::connect() {
172   record_start_time(&worker->stats);
173
174   while (next_addr) {
175     auto addr = next_addr;
176     next_addr = next_addr->ai_next;
177     fd = util::create_nonblock_socket(addr->ai_family);
178     if (fd == -1) {
179       continue;
180     }
181     if (config.scheme == "https") {
182       ssl = SSL_new(worker->ssl_ctx);
183
184       auto config = worker->config;
185
186       if (!util::numeric_host(config->host.c_str())) {
187         SSL_set_tlsext_host_name(ssl, config->host.c_str());
188       }
189
190       SSL_set_fd(ssl, fd);
191       SSL_set_connect_state(ssl);
192     }
193
194     auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
195     if (rv != 0 && errno != EINPROGRESS) {
196       if (ssl) {
197         SSL_free(ssl);
198         ssl = nullptr;
199       }
200       close(fd);
201       fd = -1;
202       continue;
203     }
204     break;
205   }
206
207   if (fd == -1) {
208     return -1;
209   }
210
211   writefn = &Client::connected;
212
213   ev_io_set(&rev, fd, EV_READ);
214   ev_io_set(&wev, fd, EV_WRITE);
215
216   ev_io_start(worker->loop, &wev);
217
218   return 0;
219 }
220
221 void Client::fail() {
222   process_abandoned_streams();
223
224   disconnect();
225 }
226
227 void Client::disconnect() {
228   streams.clear();
229   session.reset();
230   state = CLIENT_IDLE;
231   ev_io_stop(worker->loop, &wev);
232   ev_io_stop(worker->loop, &rev);
233   if (ssl) {
234     SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
235     ERR_clear_error();
236     SSL_shutdown(ssl);
237     SSL_free(ssl);
238     ssl = nullptr;
239   }
240   if (fd != -1) {
241     shutdown(fd, SHUT_WR);
242     close(fd);
243     fd = -1;
244   }
245 }
246
247 void Client::submit_request() {
248   auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
249   session->submit_request(req_stat);
250   ++req_started;
251 }
252
253 void Client::process_abandoned_streams() {
254   auto req_abandoned = req_todo - req_done;
255
256   worker->stats.req_failed += req_abandoned;
257   worker->stats.req_error += req_abandoned;
258   worker->stats.req_done += req_abandoned;
259
260   req_done = req_todo;
261 }
262
263 void Client::report_progress() {
264   if (worker->id == 0 &&
265       worker->stats.req_done % worker->progress_interval == 0) {
266     std::cout << "progress: "
267               << worker->stats.req_done * 100 / worker->stats.req_todo
268               << "% done" << std::endl;
269   }
270 }
271
272 namespace {
273 const char *get_tls_protocol(SSL *ssl) {
274   auto session = SSL_get_session(ssl);
275
276   switch (session->ssl_version) {
277   case SSL2_VERSION:
278     return "SSLv2";
279   case SSL3_VERSION:
280     return "SSLv3";
281   case TLS1_2_VERSION:
282     return "TLSv1.2";
283   case TLS1_1_VERSION:
284     return "TLSv1.1";
285   case TLS1_VERSION:
286     return "TLSv1";
287   default:
288     return "unknown";
289   }
290 }
291 } // namespace
292
293 namespace {
294 void print_server_tmp_key(SSL *ssl) {
295 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
296   EVP_PKEY *key;
297
298   if (!SSL_get_server_tmp_key(ssl, &key)) {
299     return;
300   }
301
302   auto key_del = defer(EVP_PKEY_free, key);
303
304   std::cout << "Server Temp Key: ";
305
306   switch (EVP_PKEY_id(key)) {
307   case EVP_PKEY_RSA:
308     std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
309     break;
310   case EVP_PKEY_DH:
311     std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
312     break;
313   case EVP_PKEY_EC: {
314     auto ec = EVP_PKEY_get1_EC_KEY(key);
315     auto ec_del = defer(EC_KEY_free, ec);
316     auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
317     auto cname = EC_curve_nid2nist(nid);
318     if (!cname) {
319       cname = OBJ_nid2sn(nid);
320     }
321
322     std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
323               << std::endl;
324     break;
325   }
326   }
327 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
328 }
329 } // namespace
330
331 void Client::report_tls_info() {
332   if (worker->id == 0 && !worker->tls_info_report_done) {
333     worker->tls_info_report_done = true;
334     auto cipher = SSL_get_current_cipher(ssl);
335     std::cout << "Protocol: " << get_tls_protocol(ssl) << "\n"
336               << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
337     print_server_tmp_key(ssl);
338   }
339 }
340
341 void Client::terminate_session() { session->terminate(); }
342
343 void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
344
345 void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
346                        const uint8_t *value, size_t valuelen) {
347   auto itr = streams.find(stream_id);
348   if (itr == std::end(streams)) {
349     return;
350   }
351   auto &stream = (*itr).second;
352   if (stream.status_success == -1 && namelen == 7 &&
353       util::streq_l(":status", name, namelen)) {
354     int status = 0;
355     for (size_t i = 0; i < valuelen; ++i) {
356       if ('0' <= value[i] && value[i] <= '9') {
357         status *= 10;
358         status += value[i] - '0';
359         if (status > 999) {
360           stream.status_success = 0;
361           return;
362         }
363       } else {
364         break;
365       }
366     }
367
368     if (status >= 200 && status < 300) {
369       ++worker->stats.status[2];
370       stream.status_success = 1;
371     } else if (status < 400) {
372       ++worker->stats.status[3];
373       stream.status_success = 1;
374     } else if (status < 600) {
375       ++worker->stats.status[status / 100];
376       stream.status_success = 0;
377     } else {
378       stream.status_success = 0;
379     }
380   }
381 }
382
383 void Client::on_stream_close(int32_t stream_id, bool success,
384                              RequestStat *req_stat) {
385   req_stat->stream_close_time = std::chrono::steady_clock::now();
386   if (success) {
387     req_stat->completed = true;
388     ++worker->stats.req_success;
389   }
390   ++worker->stats.req_done;
391   ++req_done;
392   if (success && streams[stream_id].status_success == 1) {
393     ++worker->stats.req_status_success;
394   } else {
395     ++worker->stats.req_failed;
396   }
397   report_progress();
398   streams.erase(stream_id);
399   if (req_done == req_todo) {
400     terminate_session();
401     return;
402   }
403
404   if (req_started < req_todo) {
405     submit_request();
406     return;
407   }
408 }
409
410 int Client::connection_made() {
411   if (ssl) {
412     report_tls_info();
413
414     const unsigned char *next_proto = nullptr;
415     unsigned int next_proto_len;
416     SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
417     for (int i = 0; i < 2; ++i) {
418       if (next_proto) {
419         if (util::check_h2_is_selected(next_proto, next_proto_len)) {
420           session = make_unique<Http2Session>(this);
421         } else {
422 #ifdef HAVE_SPDYLAY
423           auto spdy_version =
424               spdylay_npn_get_version(next_proto, next_proto_len);
425           if (spdy_version) {
426             session = make_unique<SpdySession>(this, spdy_version);
427           } else {
428             debug_nextproto_error();
429             fail();
430             return -1;
431           }
432 #else  // !HAVE_SPDYLAY
433           debug_nextproto_error();
434           fail();
435           return -1;
436 #endif // !HAVE_SPDYLAY
437         }
438       }
439
440 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
441       SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
442 #else  // OPENSSL_VERSION_NUMBER < 0x10002000L
443       break;
444 #endif // OPENSSL_VERSION_NUMBER < 0x10002000L
445     }
446
447     if (!next_proto) {
448       debug_nextproto_error();
449       fail();
450       return -1;
451     }
452   } else {
453     switch (config.no_tls_proto) {
454     case Config::PROTO_HTTP2:
455       session = make_unique<Http2Session>(this);
456       break;
457 #ifdef HAVE_SPDYLAY
458     case Config::PROTO_SPDY2:
459       session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY2);
460       break;
461     case Config::PROTO_SPDY3:
462       session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3);
463       break;
464     case Config::PROTO_SPDY3_1:
465       session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3_1);
466       break;
467 #endif // HAVE_SPDYLAY
468     default:
469       // unreachable
470       assert(0);
471     }
472   }
473
474   state = CLIENT_CONNECTED;
475
476   session->on_connect();
477
478   record_connect_time(&worker->stats);
479
480   auto nreq =
481       std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
482
483   for (; nreq > 0; --nreq) {
484     submit_request();
485   }
486
487   signal_write();
488
489   return 0;
490 }
491
492 int Client::on_read(const uint8_t *data, size_t len) {
493   auto rv = session->on_read(data, len);
494   if (rv != 0) {
495     return -1;
496   }
497   worker->stats.bytes_total += len;
498   signal_write();
499   return 0;
500 }
501
502 int Client::on_write() {
503   if (session->on_write() != 0) {
504     return -1;
505   }
506   return 0;
507 }
508
509 int Client::read_clear() {
510   uint8_t buf[8192];
511
512   for (;;) {
513     ssize_t nread;
514     while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
515       ;
516     if (nread == -1) {
517       if (errno == EAGAIN || errno == EWOULDBLOCK) {
518         return 0;
519       }
520       return -1;
521     }
522
523     if (nread == 0) {
524       return -1;
525     }
526
527     if (on_read(buf, nread) != 0) {
528       return -1;
529     }
530
531     if (!first_byte_received) {
532       first_byte_received = true;
533       record_ttfb(&worker->stats);
534     }
535   }
536
537   return 0;
538 }
539
540 int Client::write_clear() {
541   for (;;) {
542     if (wb.rleft() > 0) {
543       ssize_t nwrite;
544       while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
545         ;
546       if (nwrite == -1) {
547         if (errno == EAGAIN || errno == EWOULDBLOCK) {
548           ev_io_start(worker->loop, &wev);
549           return 0;
550         }
551         return -1;
552       }
553       wb.drain(nwrite);
554       continue;
555     }
556     wb.reset();
557     if (on_write() != 0) {
558       return -1;
559     }
560     if (wb.rleft() == 0) {
561       break;
562     }
563   }
564
565   ev_io_stop(worker->loop, &wev);
566
567   return 0;
568 }
569
570 int Client::connected() {
571   if (!util::check_socket_connected(fd)) {
572     return ERR_CONNECT_FAIL;
573   }
574   ev_io_start(worker->loop, &rev);
575   ev_io_stop(worker->loop, &wev);
576
577   if (ssl) {
578     readfn = &Client::tls_handshake;
579     writefn = &Client::tls_handshake;
580
581     return do_write();
582   }
583
584   readfn = &Client::read_clear;
585   writefn = &Client::write_clear;
586
587   if (connection_made() != 0) {
588     return -1;
589   }
590
591   return 0;
592 }
593
594 int Client::tls_handshake() {
595   ERR_clear_error();
596
597   auto rv = SSL_do_handshake(ssl);
598
599   if (rv == 0) {
600     return -1;
601   }
602
603   if (rv < 0) {
604     auto err = SSL_get_error(ssl, rv);
605     switch (err) {
606     case SSL_ERROR_WANT_READ:
607       ev_io_stop(worker->loop, &wev);
608       return 0;
609     case SSL_ERROR_WANT_WRITE:
610       ev_io_start(worker->loop, &wev);
611       return 0;
612     default:
613       return -1;
614     }
615   }
616
617   ev_io_stop(worker->loop, &wev);
618
619   readfn = &Client::read_tls;
620   writefn = &Client::write_tls;
621
622   if (connection_made() != 0) {
623     return -1;
624   }
625
626   return 0;
627 }
628
629 int Client::read_tls() {
630   uint8_t buf[8192];
631
632   ERR_clear_error();
633
634   for (;;) {
635     auto rv = SSL_read(ssl, buf, sizeof(buf));
636
637     if (rv == 0) {
638       return -1;
639     }
640
641     if (rv < 0) {
642       auto err = SSL_get_error(ssl, rv);
643       switch (err) {
644       case SSL_ERROR_WANT_READ:
645         return 0;
646       case SSL_ERROR_WANT_WRITE:
647         // renegotiation started
648         return -1;
649       default:
650         return -1;
651       }
652     }
653
654     if (on_read(buf, rv) != 0) {
655       return -1;
656     }
657
658     if (!first_byte_received) {
659       first_byte_received = true;
660       record_ttfb(&worker->stats);
661     }
662   }
663 }
664
665 int Client::write_tls() {
666   ERR_clear_error();
667
668   for (;;) {
669     if (wb.rleft() > 0) {
670       auto rv = SSL_write(ssl, wb.pos, wb.rleft());
671
672       if (rv == 0) {
673         return -1;
674       }
675
676       if (rv < 0) {
677         auto err = SSL_get_error(ssl, rv);
678         switch (err) {
679         case SSL_ERROR_WANT_READ:
680           // renegotiation started
681           return -1;
682         case SSL_ERROR_WANT_WRITE:
683           ev_io_start(worker->loop, &wev);
684           return 0;
685         default:
686           return -1;
687         }
688       }
689
690       wb.drain(rv);
691
692       continue;
693     }
694     wb.reset();
695     if (on_write() != 0) {
696       return -1;
697     }
698     if (wb.rleft() == 0) {
699       break;
700     }
701   }
702
703   ev_io_stop(worker->loop, &wev);
704
705   return 0;
706 }
707
708 void Client::record_request_time(RequestStat *req_stat) {
709   req_stat->request_time = std::chrono::steady_clock::now();
710 }
711
712 void Client::record_start_time(Stats *stat) {
713   stat->start_times.push_back(std::chrono::steady_clock::now());
714 }
715
716 void Client::record_connect_time(Stats *stat) {
717   stat->connect_times.push_back(std::chrono::steady_clock::now());
718 }
719
720 void Client::record_ttfb(Stats *stat) {
721   stat->ttfbs.push_back(std::chrono::steady_clock::now());
722 }
723
724 void Client::signal_write() { ev_io_start(worker->loop, &wev); }
725
726 Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
727                Config *config)
728     : stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
729       id(id), tls_info_report_done(false) {
730   stats.req_todo = req_todo;
731   progress_interval = std::max((size_t)1, req_todo / 10);
732
733   auto nreqs_per_client = req_todo / nclients;
734   auto nreqs_rem = req_todo % nclients;
735
736   for (size_t i = 0; i < nclients; ++i) {
737     auto req_todo = nreqs_per_client;
738     if (nreqs_rem > 0) {
739       ++req_todo;
740       --nreqs_rem;
741     }
742     clients.push_back(make_unique<Client>(this, req_todo));
743   }
744 }
745
746 Worker::~Worker() {
747   // first clear clients so that io watchers are stopped before
748   // destructing ev_loop.
749   clients.clear();
750   ev_loop_destroy(loop);
751 }
752
753 void Worker::run() {
754   for (auto &client : clients) {
755     if (client->connect() != 0) {
756       std::cerr << "client could not connect to host" << std::endl;
757       client->fail();
758     }
759   }
760   ev_run(loop, 0);
761 }
762
763 namespace {
764 // Returns percentage of number of samples within mean +/- sd.
765 template <typename Duration>
766 double within_sd(const std::vector<Duration> &samples, const Duration &mean,
767                  const Duration &sd) {
768   if (samples.size() == 0) {
769     return 0.0;
770   }
771   auto lower = mean - sd;
772   auto upper = mean + sd;
773   auto m = std::count_if(
774       std::begin(samples), std::end(samples),
775       [&lower, &upper](const Duration &t) { return lower <= t && t <= upper; });
776   return (m / static_cast<double>(samples.size())) * 100;
777 }
778 } // namespace
779
780 namespace {
781 // Computes statistics using |samples|. The min, max, mean, sd, and
782 // percentage of number of samples within mean +/- sd are computed.
783 template <typename Duration>
784 TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
785   if (samples.size() == 0) {
786     return {Duration::zero(), Duration::zero(), Duration::zero(),
787             Duration::zero(), 0.0};
788   }
789   // standard deviation calculated using Rapid calculation method:
790   // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
791   double a = 0, q = 0;
792   size_t n = 0;
793   int64_t sum = 0;
794   auto res = TimeStat<Duration>{Duration::max(), Duration::min()};
795   for (const auto &t : samples) {
796     ++n;
797     res.min = std::min(res.min, t);
798     res.max = std::max(res.max, t);
799     sum += t.count();
800
801     auto na = a + (t.count() - a) / n;
802     q += (t.count() - a) * (t.count() - na);
803     a = na;
804   }
805
806   res.mean = Duration(sum / n);
807   res.sd = Duration(static_cast<typename Duration::rep>(sqrt(q / n)));
808   res.within_sd = within_sd(samples, res.mean, res.sd);
809
810   return res;
811 }
812 } // namespace
813
814 namespace {
815 TimeStats
816 process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
817   size_t nrequest_times = 0, nttfb_times = 0;
818   for (const auto &w : workers) {
819     nrequest_times += w->stats.req_stats.size();
820     nttfb_times += w->stats.ttfbs.size();
821   }
822
823   std::vector<std::chrono::microseconds> request_times;
824   request_times.reserve(nrequest_times);
825   std::vector<std::chrono::microseconds> connect_times, ttfb_times;
826   connect_times.reserve(nttfb_times);
827   ttfb_times.reserve(nttfb_times);
828
829   for (const auto &w : workers) {
830     for (const auto &req_stat : w->stats.req_stats) {
831       if (!req_stat.completed) {
832         continue;
833       }
834       request_times.push_back(
835           std::chrono::duration_cast<std::chrono::microseconds>(
836               req_stat.stream_close_time - req_stat.request_time));
837     }
838
839     const auto &stat = w->stats;
840     // rule out cases where we started but didn't connect or get the
841     // first byte (errors).  We will get connect event before FFTB.
842     assert(stat.start_times.size() >= stat.ttfbs.size());
843     assert(stat.connect_times.size() >= stat.ttfbs.size());
844     for (size_t i = 0; i < stat.ttfbs.size(); ++i) {
845       connect_times.push_back(
846           std::chrono::duration_cast<std::chrono::microseconds>(
847               stat.connect_times[i] - stat.start_times[i]));
848
849       ttfb_times.push_back(
850           std::chrono::duration_cast<std::chrono::microseconds>(
851               stat.ttfbs[i] - stat.start_times[i]));
852     }
853   }
854
855   return {compute_time_stat(request_times), compute_time_stat(connect_times),
856           compute_time_stat(ttfb_times)};
857 }
858 } // namespace
859
860 namespace {
861 void resolve_host() {
862   int rv;
863   addrinfo hints, *res;
864
865   memset(&hints, 0, sizeof(hints));
866   hints.ai_family = AF_UNSPEC;
867   hints.ai_socktype = SOCK_STREAM;
868   hints.ai_protocol = 0;
869   hints.ai_flags = AI_ADDRCONFIG;
870
871   rv = getaddrinfo(config.host.c_str(), util::utos(config.port).c_str(), &hints,
872                    &res);
873   if (rv != 0) {
874     std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
875     exit(EXIT_FAILURE);
876   }
877   if (res == nullptr) {
878     std::cerr << "No address returned" << std::endl;
879     exit(EXIT_FAILURE);
880   }
881   config.addrs = res;
882 }
883 } // namespace
884
885 namespace {
886 std::string get_reqline(const char *uri, const http_parser_url &u) {
887   std::string reqline;
888
889   if (util::has_uri_field(u, UF_PATH)) {
890     reqline = util::get_uri_field(uri, u, UF_PATH);
891   } else {
892     reqline = "/";
893   }
894
895   if (util::has_uri_field(u, UF_QUERY)) {
896     reqline += "?";
897     reqline += util::get_uri_field(uri, u, UF_QUERY);
898   }
899
900   return reqline;
901 }
902 } // namespace
903
904 namespace {
905 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
906                                 unsigned char *outlen, const unsigned char *in,
907                                 unsigned int inlen, void *arg) {
908   if (util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
909                       inlen)) {
910     return SSL_TLSEXT_ERR_OK;
911   }
912 #ifdef HAVE_SPDYLAY
913   if (spdylay_select_next_protocol(out, outlen, in, inlen) > 0) {
914     return SSL_TLSEXT_ERR_OK;
915   }
916 #endif
917   return SSL_TLSEXT_ERR_NOACK;
918 }
919 } // namespace
920
921 namespace {
922 template <typename Iterator>
923 std::vector<std::string> parse_uris(Iterator first, Iterator last) {
924   std::vector<std::string> reqlines;
925
926   // First URI is treated specially.  We use scheme, host and port of
927   // this URI and ignore those in the remaining URIs if present.
928   http_parser_url u;
929   memset(&u, 0, sizeof(u));
930
931   if (first == last) {
932     std::cerr << "no URI available" << std::endl;
933     exit(EXIT_FAILURE);
934   }
935
936   auto uri = (*first).c_str();
937   ++first;
938
939   if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0 ||
940       !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
941     std::cerr << "invalid URI: " << uri << std::endl;
942     exit(EXIT_FAILURE);
943   }
944
945   config.scheme = util::get_uri_field(uri, u, UF_SCHEMA);
946   config.host = util::get_uri_field(uri, u, UF_HOST);
947   config.default_port = util::get_default_port(uri, u);
948   if (util::has_uri_field(u, UF_PORT)) {
949     config.port = u.port;
950   } else {
951     config.port = config.default_port;
952   }
953
954   reqlines.push_back(get_reqline(uri, u));
955
956   for (; first != last; ++first) {
957     http_parser_url u;
958     memset(&u, 0, sizeof(u));
959
960     auto uri = (*first).c_str();
961
962     if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0) {
963       std::cerr << "invalid URI: " << uri << std::endl;
964       exit(EXIT_FAILURE);
965     }
966
967     reqlines.push_back(get_reqline(uri, u));
968   }
969
970   return reqlines;
971 }
972 } // namespace
973
974 namespace {
975 std::vector<std::string> read_uri_from_file(std::istream &infile) {
976   std::vector<std::string> uris;
977   std::string line_uri;
978   while (std::getline(infile, line_uri)) {
979     uris.push_back(line_uri);
980   }
981
982   return uris;
983 }
984 } // namespace
985
986 namespace {
987 void print_version(std::ostream &out) {
988   out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
989 }
990 } // namespace
991
992 namespace {
993 void print_usage(std::ostream &out) {
994   out << R"(Usage: h2load [OPTIONS]... [URI]...
995 benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
996 }
997 } // namespace
998
999 namespace {
1000 void print_help(std::ostream &out) {
1001   print_usage(out);
1002
1003   out << R"(
1004   <URI>       Specify URI to access.   Multiple URIs can be specified.
1005               URIs are used  in this order for each  client.  All URIs
1006               are used, then  first URI is used and then  2nd URI, and
1007               so  on.  The  scheme, host  and port  in the  subsequent
1008               URIs, if present,  are ignored.  Those in  the first URI
1009               are used solely.
1010 Options:
1011   -n, --requests=<N>
1012               Number of requests.
1013               Default: )" << config.nreqs << R"(
1014   -c, --clients=<N>
1015               Number of concurrent clients.
1016               Default: )" << config.nclients << R"(
1017   -t, --threads=<N>
1018               Number of native threads.
1019               Default: )" << config.nthreads << R"(
1020   -i, --input-file=<FILE>
1021               Path of a file with multiple URIs are separated by EOLs.
1022               This option will disable URIs getting from command-line.
1023               If '-' is given as <FILE>, URIs will be read from stdin.
1024               URIs are used  in this order for each  client.  All URIs
1025               are used, then  first URI is used and then  2nd URI, and
1026               so  on.  The  scheme, host  and port  in the  subsequent
1027               URIs, if present,  are ignored.  Those in  the first URI
1028               are used solely.
1029   -m, --max-concurrent-streams=(auto|<N>)
1030               Max concurrent streams to  issue per session.  If "auto"
1031               is given, the number of given URIs is used.
1032               Default: auto
1033   -w, --window-bits=<N>
1034               Sets the stream level initial window size to (2**<N>)-1.
1035               For SPDY, 2**<N> is used instead.
1036               Default: )" << config.window_bits << R"(
1037   -W, --connection-window-bits=<N>
1038               Sets  the  connection  level   initial  window  size  to
1039               (2**<N>)-1.  For SPDY, if <N>  is strictly less than 16,
1040               this option  is ignored.   Otherwise 2**<N> is  used for
1041               SPDY.
1042               Default: )" << config.connection_window_bits << R"(
1043   -H, --header=<HEADER>
1044               Add/Override a header to the requests.
1045   -p, --no-tls-proto=<PROTOID>
1046               Specify ALPN identifier of the  protocol to be used when
1047               accessing http URI without SSL/TLS.)";
1048 #ifdef HAVE_SPDYLAY
1049   out << R"(
1050               Available protocols: spdy/2, spdy/3, spdy/3.1 and )";
1051 #else  // !HAVE_SPDYLAY
1052   out << R"(
1053               Available protocol: )";
1054 #endif // !HAVE_SPDYLAY
1055   out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
1056               Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
1057   -d, --data=<FILE>
1058               Post FILE to  server.  The request method  is changed to
1059               POST.
1060   -v, --verbose
1061               Output debug information.
1062   --version   Display version information and exit.
1063   -h, --help  Display this help and exit.)" << std::endl;
1064 }
1065 } // namespace
1066
1067 int main(int argc, char **argv) {
1068   std::string datafile;
1069   while (1) {
1070     static int flag = 0;
1071     static option long_options[] = {
1072         {"requests", required_argument, nullptr, 'n'},
1073         {"clients", required_argument, nullptr, 'c'},
1074         {"data", required_argument, nullptr, 'd'},
1075         {"threads", required_argument, nullptr, 't'},
1076         {"max-concurrent-streams", required_argument, nullptr, 'm'},
1077         {"window-bits", required_argument, nullptr, 'w'},
1078         {"connection-window-bits", required_argument, nullptr, 'W'},
1079         {"input-file", required_argument, nullptr, 'i'},
1080         {"header", required_argument, nullptr, 'H'},
1081         {"no-tls-proto", required_argument, nullptr, 'p'},
1082         {"verbose", no_argument, nullptr, 'v'},
1083         {"help", no_argument, nullptr, 'h'},
1084         {"version", no_argument, &flag, 1},
1085         {nullptr, 0, nullptr, 0}};
1086     int option_index = 0;
1087     auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:", long_options,
1088                          &option_index);
1089     if (c == -1) {
1090       break;
1091     }
1092     switch (c) {
1093     case 'n':
1094       config.nreqs = strtoul(optarg, nullptr, 10);
1095       break;
1096     case 'c':
1097       config.nclients = strtoul(optarg, nullptr, 10);
1098       break;
1099     case 'd':
1100       datafile = optarg;
1101       break;
1102     case 't':
1103 #ifdef NOTHREADS
1104       std::cerr << "-t: WARNING: Threading disabled at build time, "
1105                 << "no threads created." << std::endl;
1106 #else
1107       config.nthreads = strtoul(optarg, nullptr, 10);
1108 #endif // NOTHREADS
1109       break;
1110     case 'm':
1111       if (util::strieq("auto", optarg)) {
1112         config.max_concurrent_streams = -1;
1113       } else {
1114         config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
1115       }
1116       break;
1117     case 'w':
1118     case 'W': {
1119       errno = 0;
1120       char *endptr = nullptr;
1121       auto n = strtoul(optarg, &endptr, 10);
1122       if (errno == 0 && *endptr == '\0' && n < 31) {
1123         if (c == 'w') {
1124           config.window_bits = n;
1125         } else {
1126           config.connection_window_bits = n;
1127         }
1128       } else {
1129         std::cerr << "-" << static_cast<char>(c)
1130                   << ": specify the integer in the range [0, 30], inclusive"
1131                   << std::endl;
1132         exit(EXIT_FAILURE);
1133       }
1134       break;
1135     }
1136     case 'H': {
1137       char *header = optarg;
1138       // Skip first possible ':' in the header name
1139       char *value = strchr(optarg + 1, ':');
1140       if (!value || (header[0] == ':' && header + 1 == value)) {
1141         std::cerr << "-H: invalid header: " << optarg << std::endl;
1142         exit(EXIT_FAILURE);
1143       }
1144       *value = 0;
1145       value++;
1146       while (isspace(*value)) {
1147         value++;
1148       }
1149       if (*value == 0) {
1150         // This could also be a valid case for suppressing a header
1151         // similar to curl
1152         std::cerr << "-H: invalid header - value missing: " << optarg
1153                   << std::endl;
1154         exit(EXIT_FAILURE);
1155       }
1156       // Note that there is no processing currently to handle multiple
1157       // message-header fields with the same field name
1158       config.custom_headers.emplace_back(header, value);
1159       util::inp_strlower(config.custom_headers.back().name);
1160       break;
1161     }
1162     case 'i': {
1163       config.ifile = std::string(optarg);
1164       break;
1165     }
1166     case 'p':
1167       if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
1168         config.no_tls_proto = Config::PROTO_HTTP2;
1169 #ifdef HAVE_SPDYLAY
1170       } else if (util::strieq("spdy/2", optarg)) {
1171         config.no_tls_proto = Config::PROTO_SPDY2;
1172       } else if (util::strieq("spdy/3", optarg)) {
1173         config.no_tls_proto = Config::PROTO_SPDY3;
1174       } else if (util::strieq("spdy/3.1", optarg)) {
1175         config.no_tls_proto = Config::PROTO_SPDY3_1;
1176 #endif // HAVE_SPDYLAY
1177       } else {
1178         std::cerr << "-p: unsupported protocol " << optarg << std::endl;
1179         exit(EXIT_FAILURE);
1180       }
1181       break;
1182     case 'v':
1183       config.verbose = true;
1184       break;
1185     case 'h':
1186       print_help(std::cout);
1187       exit(EXIT_SUCCESS);
1188     case '?':
1189       util::show_candidates(argv[optind - 1], long_options);
1190       exit(EXIT_FAILURE);
1191     case 0:
1192       switch (flag) {
1193       case 1:
1194         // version option
1195         print_version(std::cout);
1196         exit(EXIT_SUCCESS);
1197       }
1198       break;
1199     default:
1200       break;
1201     }
1202   }
1203
1204   if (argc == optind) {
1205     if (config.ifile.empty()) {
1206       std::cerr << "no URI or input file given" << std::endl;
1207       exit(EXIT_FAILURE);
1208     }
1209   }
1210
1211   if (config.nreqs == 0) {
1212     std::cerr << "-n: the number of requests must be strictly greater than 0."
1213               << std::endl;
1214     exit(EXIT_FAILURE);
1215   }
1216
1217   if (config.max_concurrent_streams == 0) {
1218     std::cerr << "-m: the max concurrent streams must be strictly greater "
1219               << "than 0." << std::endl;
1220     exit(EXIT_FAILURE);
1221   }
1222
1223   if (config.nthreads == 0) {
1224     std::cerr << "-t: the number of threads must be strictly greater than 0."
1225               << std::endl;
1226     exit(EXIT_FAILURE);
1227   }
1228
1229   if (config.nreqs < config.nclients) {
1230     std::cerr << "-n, -c: the number of requests must be greater than or "
1231               << "equal to the concurrent clients." << std::endl;
1232     exit(EXIT_FAILURE);
1233   }
1234
1235   if (config.nclients < config.nthreads) {
1236     std::cerr << "-c, -t: the number of client must be greater than or equal "
1237                  "to the number of threads." << std::endl;
1238     exit(EXIT_FAILURE);
1239   }
1240
1241   if (config.nthreads > std::thread::hardware_concurrency()) {
1242     std::cerr << "-t: warning: the number of threads is greater than hardware "
1243               << "cores." << std::endl;
1244   }
1245
1246   if (!datafile.empty()) {
1247     config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
1248     if (config.data_fd == -1) {
1249       std::cerr << "-d: Could not open file " << datafile << std::endl;
1250       exit(EXIT_FAILURE);
1251     }
1252     struct stat data_stat;
1253     if (fstat(config.data_fd, &data_stat) == -1) {
1254       std::cerr << "-d: Could not stat file " << datafile << std::endl;
1255       exit(EXIT_FAILURE);
1256     }
1257     config.data_length = data_stat.st_size;
1258   }
1259
1260   struct sigaction act;
1261   memset(&act, 0, sizeof(struct sigaction));
1262   act.sa_handler = SIG_IGN;
1263   sigaction(SIGPIPE, &act, nullptr);
1264   OPENSSL_config(nullptr);
1265   OpenSSL_add_all_algorithms();
1266   SSL_load_error_strings();
1267   SSL_library_init();
1268
1269 #ifndef NOTHREADS
1270   ssl::LibsslGlobalLock lock;
1271 #endif // NOTHREADS
1272
1273   auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
1274   if (!ssl_ctx) {
1275     std::cerr << "Failed to create SSL_CTX: "
1276               << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1277     exit(EXIT_FAILURE);
1278   }
1279
1280   SSL_CTX_set_options(ssl_ctx,
1281                       SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
1282                           SSL_OP_NO_COMPRESSION |
1283                           SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
1284   SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
1285   SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
1286
1287   if (SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST) == 0) {
1288     std::cerr << "SSL_CTX_set_cipher_list failed: "
1289               << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1290     exit(EXIT_FAILURE);
1291   }
1292
1293   SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
1294                                    nullptr);
1295
1296 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
1297   auto proto_list = util::get_default_alpn();
1298 #ifdef HAVE_SPDYLAY
1299   static const char spdy_proto_list[] = "\x8spdy/3.1\x6spdy/3\x6spdy/2";
1300   std::copy_n(spdy_proto_list, sizeof(spdy_proto_list) - 1,
1301               std::back_inserter(proto_list));
1302 #endif // HAVE_SPDYLAY
1303   SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
1304 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
1305
1306   std::vector<std::string> reqlines;
1307
1308   if (config.ifile.empty()) {
1309     std::vector<std::string> uris;
1310     std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
1311     reqlines = parse_uris(std::begin(uris), std::end(uris));
1312   } else {
1313     std::vector<std::string> uris;
1314     if (config.ifile == "-") {
1315       uris = read_uri_from_file(std::cin);
1316     } else {
1317       std::ifstream infile(config.ifile);
1318       if (!infile) {
1319         std::cerr << "cannot read input file: " << config.ifile << std::endl;
1320         exit(EXIT_FAILURE);
1321       }
1322
1323       uris = read_uri_from_file(infile);
1324     }
1325
1326     reqlines = parse_uris(std::begin(uris), std::end(uris));
1327   }
1328
1329   if (config.max_concurrent_streams == -1) {
1330     config.max_concurrent_streams = reqlines.size();
1331   }
1332
1333   Headers shared_nva;
1334   shared_nva.emplace_back(":scheme", config.scheme);
1335   if (config.port != config.default_port) {
1336     shared_nva.emplace_back(":authority",
1337                             config.host + ":" + util::utos(config.port));
1338   } else {
1339     shared_nva.emplace_back(":authority", config.host);
1340   }
1341   shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
1342
1343   // list overridalbe headers
1344   auto override_hdrs =
1345       make_array<std::string>(":authority", ":host", ":method", ":scheme");
1346
1347   for (auto &kv : config.custom_headers) {
1348     if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
1349                   kv.name) != std::end(override_hdrs)) {
1350       // override header
1351       for (auto &nv : shared_nva) {
1352         if ((nv.name == ":authority" && kv.name == ":host") ||
1353             (nv.name == kv.name)) {
1354           nv.value = kv.value;
1355         }
1356       }
1357     } else {
1358       // add additional headers
1359       shared_nva.push_back(kv);
1360     }
1361   }
1362
1363   for (auto &req : reqlines) {
1364     // For nghttp2
1365     std::vector<nghttp2_nv> nva;
1366
1367     nva.push_back(http2::make_nv_ls(":path", req));
1368
1369     for (auto &nv : shared_nva) {
1370       nva.push_back(http2::make_nv(nv.name, nv.value, false));
1371     }
1372
1373     config.nva.push_back(std::move(nva));
1374
1375     // For spdylay
1376     std::vector<const char *> cva;
1377
1378     cva.push_back(":path");
1379     cva.push_back(req.c_str());
1380
1381     for (auto &nv : shared_nva) {
1382       if (nv.name == ":authority") {
1383         cva.push_back(":host");
1384       } else {
1385         cva.push_back(nv.name.c_str());
1386       }
1387       cva.push_back(nv.value.c_str());
1388     }
1389     cva.push_back(":version");
1390     cva.push_back("HTTP/1.1");
1391     cva.push_back(nullptr);
1392
1393     config.nv.push_back(std::move(cva));
1394   }
1395
1396   resolve_host();
1397
1398   if (config.nclients == 1) {
1399     config.nthreads = 1;
1400   }
1401
1402   size_t nreqs_per_thread = config.nreqs / config.nthreads;
1403   ssize_t nreqs_rem = config.nreqs % config.nthreads;
1404
1405   size_t nclients_per_thread = config.nclients / config.nthreads;
1406   ssize_t nclients_rem = config.nclients % config.nthreads;
1407
1408   std::cout << "starting benchmark..." << std::endl;
1409
1410   auto start = std::chrono::steady_clock::now();
1411
1412   std::vector<std::unique_ptr<Worker>> workers;
1413   workers.reserve(config.nthreads);
1414
1415 #ifndef NOTHREADS
1416   std::vector<std::future<void>> futures;
1417   for (size_t i = 0; i < config.nthreads - 1; ++i) {
1418     auto nreqs = nreqs_per_thread + (nreqs_rem-- > 0);
1419     auto nclients = nclients_per_thread + (nclients_rem-- > 0);
1420     std::cout << "spawning thread #" << i << ": " << nclients
1421               << " concurrent clients, " << nreqs << " total requests"
1422               << std::endl;
1423     workers.push_back(
1424         make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config));
1425     auto &worker = workers.back();
1426     futures.push_back(
1427         std::async(std::launch::async, [&worker]() { worker->run(); }));
1428   }
1429 #endif // NOTHREADS
1430
1431   auto nreqs_last = nreqs_per_thread + (nreqs_rem-- > 0);
1432   auto nclients_last = nclients_per_thread + (nclients_rem-- > 0);
1433   std::cout << "spawning thread #" << (config.nthreads - 1) << ": "
1434             << nclients_last << " concurrent clients, " << nreqs_last
1435             << " total requests" << std::endl;
1436   workers.push_back(make_unique<Worker>(config.nthreads - 1, ssl_ctx,
1437                                         nreqs_last, nclients_last, &config));
1438   workers.back()->run();
1439
1440 #ifndef NOTHREADS
1441   for (auto &fut : futures) {
1442     fut.get();
1443   }
1444 #endif // NOTHREADS
1445
1446   auto end = std::chrono::steady_clock::now();
1447   auto duration =
1448       std::chrono::duration_cast<std::chrono::microseconds>(end - start);
1449
1450   Stats stats(0);
1451   for (const auto &w : workers) {
1452     const auto &s = w->stats;
1453
1454     stats.req_todo += s.req_todo;
1455     stats.req_started += s.req_started;
1456     stats.req_done += s.req_done;
1457     stats.req_success += s.req_success;
1458     stats.req_status_success += s.req_status_success;
1459     stats.req_failed += s.req_failed;
1460     stats.req_error += s.req_error;
1461     stats.bytes_total += s.bytes_total;
1462     stats.bytes_head += s.bytes_head;
1463     stats.bytes_body += s.bytes_body;
1464
1465     for (size_t i = 0; i < stats.status.size(); ++i) {
1466       stats.status[i] += s.status[i];
1467     }
1468   }
1469
1470   auto ts = process_time_stats(workers);
1471
1472   // Requests which have not been issued due to connection errors, are
1473   // counted towards req_failed and req_error.
1474   auto req_not_issued =
1475       stats.req_todo - stats.req_status_success - stats.req_failed;
1476   stats.req_failed += req_not_issued;
1477   stats.req_error += req_not_issued;
1478
1479   // UI is heavily inspired by weighttp[1] and wrk[2]
1480   //
1481   // [1] https://github.com/lighttpd/weighttp
1482   // [2] https://github.com/wg/wrk
1483   size_t rps = 0;
1484   int64_t bps = 0;
1485   if (duration.count() > 0) {
1486     auto secd = static_cast<double>(duration.count()) / (1000 * 1000);
1487     rps = stats.req_success / secd;
1488     bps = stats.bytes_total / secd;
1489   }
1490
1491   std::cout << R"(
1492 finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
1493             << util::utos_with_funit(bps) << R"(B/s
1494 requests: )" << stats.req_todo << " total, " << stats.req_started
1495             << " started, " << stats.req_done << " done, "
1496             << stats.req_status_success << " succeeded, " << stats.req_failed
1497             << " failed, " << stats.req_error << R"( errored
1498 status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
1499             << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
1500 traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
1501             << " bytes headers, " << stats.bytes_body << R"( bytes data
1502                      min         max         mean         sd        +/- sd
1503 time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
1504             << "  " << std::setw(10) << util::format_duration(ts.request.max)
1505             << "  " << std::setw(10) << util::format_duration(ts.request.mean)
1506             << "  " << std::setw(10) << util::format_duration(ts.request.sd)
1507             << std::setw(9) << util::dtos(ts.request.within_sd) << "%"
1508             << "\ntime for connect: " << std::setw(10)
1509             << util::format_duration(ts.connect.min) << "  " << std::setw(10)
1510             << util::format_duration(ts.connect.max) << "  " << std::setw(10)
1511             << util::format_duration(ts.connect.mean) << "  " << std::setw(10)
1512             << util::format_duration(ts.connect.sd) << std::setw(9)
1513             << util::dtos(ts.connect.within_sd) << "%"
1514             << "\ntime to 1st byte: " << std::setw(10)
1515             << util::format_duration(ts.ttfb.min) << "  " << std::setw(10)
1516             << util::format_duration(ts.ttfb.max) << "  " << std::setw(10)
1517             << util::format_duration(ts.ttfb.mean) << "  " << std::setw(10)
1518             << util::format_duration(ts.ttfb.sd) << std::setw(9)
1519             << util::dtos(ts.ttfb.within_sd) << "%" << std::endl;
1520
1521   SSL_CTX_free(ssl_ctx);
1522
1523   return 0;
1524 }
1525
1526 } // namespace h2load
1527
1528 int main(int argc, char **argv) { return h2load::main(argc, argv); }