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