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