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