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