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