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