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