From cd4758227d0e30f2ebe4e50292a2d9a9ea024896 Mon Sep 17 00:00:00 2001 From: Lucas Pardue Date: Fri, 31 Jul 2015 17:49:59 +0000 Subject: [PATCH] Add Timing-script and base URI support --- src/h2load.cc | 246 ++++++++++++++++++++++++++++++++++++++++++-------- src/h2load.h | 5 + 2 files changed, 211 insertions(+), 40 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 226f7674..7c537622 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -76,7 +76,7 @@ Config::Config() max_concurrent_streams(-1), window_bits(30), connection_window_bits(30), rate(0), nconns(0), conn_active_timeout(0), conn_inactivity_timeout(0), no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0), default_port(0), - verbose(false) {} + verbose(false), timing_script(false) {} Config::~Config() { freeaddrinfo(addrs); @@ -87,7 +87,7 @@ Config::~Config() { } bool Config::is_rate_mode() const { return (this->rate != 0); } - +bool Config::has_base_uri() const { return (!this->base_uri.empty()); } Config config; namespace { @@ -196,6 +196,51 @@ void conn_timeout_cb(EV_P_ ev_timer *w, int revents) { client->timeout(); } } +}//namespace + +namespace { +bool check_stop_client_request_timeout(Client *client, ev_timer *w) { + auto nreq = client->req_todo - client->req_started; + + if (nreq == 0 || + client->streams.size() >= (size_t)config.max_concurrent_streams) { + // no more requests to make, stop timer + ev_timer_stop(client->worker->loop, w); + return true; + } + + return false; +} +} // namespace + +namespace { +void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + + client->submit_request(); + client->signal_write(); + + if (check_stop_client_request_timeout(client, w)) { + return; + } + + ev_tstamp duration = + config.timings[client->reqidx] - config.timings[client->reqidx - 1]; + + while (duration < 1e-9) { + client->submit_request(); + client->signal_write(); + if (check_stop_client_request_timeout(client, w)) { + return; + } + + duration = + config.timings[client->reqidx] - config.timings[client->reqidx - 1]; + } + + client->request_timeout_watcher.repeat = duration; + ev_timer_again(client->worker->loop, &client->request_timeout_watcher); +} } // namespace Client::Client(Worker *worker, size_t req_todo) @@ -215,9 +260,15 @@ Client::Client(Worker *worker, size_t req_todo) ev_timer_init(&conn_active_watcher, conn_timeout_cb, worker->config->conn_active_timeout, 0.); conn_active_watcher.data = this; + + ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.); + request_timeout_watcher.data = this; } -Client::~Client() { disconnect(); } +Client::~Client() { + ev_timer_stop(worker->loop, &request_timeout_watcher); + disconnect(); +} int Client::do_read() { return readfn(*this); } int Client::do_write() { return writefn(*this); } @@ -473,9 +524,11 @@ void Client::on_stream_close(int32_t stream_id, bool success, return; } - if (req_started < req_todo) { - submit_request(); - return; + if (!config.timing_script) { + if (req_started < req_todo) { + submit_request(); + return; + } } } @@ -547,13 +600,25 @@ int Client::connection_made() { record_connect_time(&worker->stats); - auto nreq = - std::min(req_todo - req_started, (size_t)config.max_concurrent_streams); + if (!config.timing_script) { + auto nreq = + std::min(req_todo - req_started, (size_t)config.max_concurrent_streams); - for (; nreq > 0; --nreq) { - submit_request(); + for (; nreq > 0; --nreq) { + submit_request(); + } + } else { + + ev_tstamp duration = config.timings[reqidx]; + + while (duration < 1e-9) { + submit_request(); + duration = config.timings[reqidx]; + } + + request_timeout_watcher.repeat = duration; + ev_timer_again(worker->loop, &request_timeout_watcher); } - signal_write(); return 0; @@ -1003,6 +1068,26 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out, } } // namespace +namespace { +bool parse_base_uri(std::string base_uri) { + http_parser_url u{}; + if (http_parser_parse_url(base_uri.c_str(), base_uri.size(), 0, &u) != 0 || + !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) { + return false; + } + + config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA); + config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST); + config.default_port = util::get_default_port(base_uri.c_str(), u); + if (util::has_uri_field(u, UF_PORT)) { + config.port = u.port; + } else { + config.port = config.default_port; + } + + return true; +} +} // namespace namespace { // Use std::vector::iterator explicitly, without that, // http_parser_url u{} fails with clang-3.4. @@ -1015,30 +1100,16 @@ std::vector parse_uris(std::vector::iterator first, exit(EXIT_FAILURE); } - auto uri = (*first).c_str(); + if (!config.has_base_uri()) { - // First URI is treated specially. We use scheme, host and port of - // this URI and ignore those in the remaining URIs if present. - http_parser_url u{}; - if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0 || - !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) { - std::cerr << "invalid URI: " << uri << std::endl; - exit(EXIT_FAILURE); + if (!parse_base_uri(*first)) { + std::cerr << "invalid URI: " << *first << std::endl; + exit(EXIT_FAILURE); + } + + config.base_uri = *first; } - ++first; - - config.scheme = util::get_uri_field(uri, u, UF_SCHEMA); - config.host = util::get_uri_field(uri, u, UF_HOST); - config.default_port = util::get_default_port(uri, u); - if (util::has_uri_field(u, UF_PORT)) { - config.port = u.port; - } else { - config.port = config.default_port; - } - - reqlines.push_back(get_reqline(uri, u)); - for (; first != last; ++first) { http_parser_url u{}; @@ -1068,6 +1139,43 @@ std::vector read_uri_from_file(std::istream &infile) { } } // namespace +namespace { +void read_script_from_file(std::istream &infile, + std::vector &timings, + std::vector &uris) { + std::string script_line; + int line_count = 0; + while (std::getline(infile, script_line)) { + line_count++; + if (script_line.empty()) { + std::cerr << "Empty line detected at line " << line_count + << ". Ignoring and continuing." << std::endl; + continue; + } + + std::size_t pos = script_line.find("\t"); + if (pos == std::string::npos) { + std::cerr << "Invalid line format detected, no tab character at line " + << line_count << ". \n\t" << script_line << std::endl; + exit(EXIT_FAILURE); + } + + const char *start = script_line.c_str(); + char *end; + auto v = std::strtod(start, &end); + + if (end == start || errno != 0) { + std::cerr << "Time value error at line " << line_count << ". \n\t" + << script_line.substr(0, pos) << std::endl; + exit(EXIT_FAILURE); + } + + timings.push_back(v / 1000.0); + uris.push_back(script_line.substr(pos + 1, script_line.size())); + } +} +} // namespace + namespace { void print_version(std::ostream &out) { out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl; @@ -1093,7 +1201,8 @@ void print_help(std::ostream &out) { are used, then first URI is used and then 2nd URI, and so on. The scheme, host and port in the subsequent URIs, if present, are ignored. Those in the first URI - are used solely. + are used solely. Definition of a base URI overrides all + scheme, host or port values. Options: -n, --requests= Number of requests. @@ -1112,7 +1221,8 @@ Options: are used, then first URI is used and then 2nd URI, and so on. The scheme, host and port in the subsequent URIs, if present, are ignored. Those in the first URI - are used solely. + are used solely. Definition of a base URI overrides all + scheme, host or port values. -m, --max-concurrent-streams=(auto|) Max concurrent streams to issue per session. If "auto" is given, the number of given URIs is used. @@ -1181,6 +1291,29 @@ Options: wait. When no timeout value is set (either active or inactive), h2load will keep a connection open indefinitely, waiting for a response. +======= + --timing-script-file= + Path of a file containing one or more lines separated by + EOLs. Each script line is composed of two tab-separated + fields. The first field represents the time offset from + the start of execution, expressed as milliseconds with + microsecond resolution. The second field represents the + URI. This option will disable URIs getting from + command-line. If '-' is given as , script lines + will be read from stdin. Script lines are used in order + for each client. If -n is given, it must be less than + or equal to the number of script lines, larger values are + clamped to the number of script lines. If -n is + not given, the number of requests will default to the + number of script lines. The scheme, host and port defined + in the first URI are used solely. Values contained in + other URIs, if present, are ignored. Definition of a + base URI overrides all scheme, host or port values. + -B, --base-uri= + Specify URI from which the scheme, host and port will be + used for all requests. The base URI overrides all values + defined either at the command line or inside input files. +>>>>>>> Add Timing-script and base URI support -v, --verbose Output debug information. --version Display version information and exit. @@ -1220,9 +1353,11 @@ int main(int argc, char **argv) { {"num-conns", required_argument, nullptr, 'C'}, {"connection-active-timeout", required_argument, nullptr, 'T'}, {"connection-inactivity-timeout", required_argument, nullptr, 'N'}, + {"timing-script-file", required_argument, &flag, 3}, + {"base-uri", required_argument, nullptr, 'B'}, {nullptr, 0, nullptr, 0}}; int option_index = 0; - auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:C:T:N:", + auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:C:T:N:B:", long_options, &option_index); if (c == -1) { break; @@ -1298,10 +1433,9 @@ int main(int argc, char **argv) { util::inp_strlower(config.custom_headers.back().name); break; } - case 'i': { - config.ifile = std::string(optarg); + case 'i': + config.ifile = optarg; break; - } case 'p': if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) { config.no_tls_proto = Config::PROTO_HTTP2; @@ -1350,6 +1484,13 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } break; + case 'B': + if (!parse_base_uri(optarg)) { + std::cerr << "invalid base URI: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.base_uri = optarg; + break; case 'v': config.verbose = true; break; @@ -1369,6 +1510,10 @@ int main(int argc, char **argv) { // ciphers option config.ciphers = optarg; break; + case 3: + config.ifile = optarg; + config.timing_script = true; + break; } break; default: @@ -1515,7 +1660,11 @@ int main(int argc, char **argv) { } else { std::vector uris; if (config.ifile == "-") { - uris = read_uri_from_file(std::cin); + if (!config.timing_script) { + uris = read_uri_from_file(std::cin); + } else { + read_script_from_file(std::cin, config.timings, uris); + } } else { std::ifstream infile(config.ifile); if (!infile) { @@ -1523,7 +1672,23 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - uris = read_uri_from_file(infile); + if (!config.timing_script) { + uris = read_uri_from_file(infile); + } else { + read_script_from_file(infile, config.timings, uris); + if (nreqs_set_manually) { + if (config.nreqs > uris.size()) { + std::cerr + << "-n: the number of requests must be less than or equal " + "to the number of timing script entries. Setting number " + "of requests to " << uris.size() << std::endl; + + config.nreqs = uris.size(); + } + } else { + config.nreqs = uris.size(); + } + } } reqlines = parse_uris(std::begin(uris), std::end(uris)); @@ -1593,6 +1758,7 @@ int main(int argc, char **argv) { shared_nva.emplace_back(":authority", config.host); } shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST"); + shared_nva.emplace_back("user-agent", "h2load nghttp2/" NGHTTP2_VERSION); // list overridalbe headers auto override_hdrs = diff --git a/src/h2load.h b/src/h2load.h index 2a6c27c0..07fa2dcb 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -62,6 +62,7 @@ struct Worker; struct Config { std::vector> nva; std::vector> nv; + std::vector timings; nghttp2::Headers custom_headers; std::string scheme; std::string host; @@ -91,11 +92,14 @@ struct Config { uint16_t port; uint16_t default_port; bool verbose; + bool timing_script; + std::string base_uri; Config(); ~Config(); bool is_rate_mode() const; + bool has_base_uri() const; }; struct RequestStat { @@ -208,6 +212,7 @@ struct Client { std::function readfn, writefn; Worker *worker; SSL *ssl; + ev_timer request_timeout_watcher; addrinfo *next_addr; size_t reqidx; ClientState state;