diff --git a/src/h2load.cc b/src/h2load.cc index 7d3d8e04..f498c3de 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -98,6 +98,7 @@ Config::Config() verbose(false), timing_script(false), base_uri_unix(false), + requests_until_reconnect(0), unix_addr{}, rps(0.) {} @@ -215,7 +216,7 @@ void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) { --worker->nreqs_rem; } auto client = - std::make_unique(worker->next_client_id++, worker, req_todo); + std::make_unique(worker->next_client_id++, worker, req_todo, 0); ++worker->nconns_made; @@ -404,7 +405,8 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { } } // namespace -Client::Client(uint32_t id, Worker *worker, size_t req_todo) +Client::Client(uint32_t id, Worker *worker, size_t req_todo, uint32_t + requests_until_reconnect) : wb(&worker->mcpool), cstat{}, worker(worker), @@ -418,6 +420,8 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo) req_inflight(0), req_started(0), req_done(0), + req_until_reconnect(requests_until_reconnect), + req_until_reconnect_rearmed(false), id(id), fd(-1), new_connection_requested(false), @@ -573,12 +577,16 @@ int Client::try_again_or_fail() { if (req_left) { if (worker->current_phase == Phase::MAIN_DURATION) { - // At the moment, we don't have a facility to re-start request - // already in in-flight. Make them fail. - worker->stats.req_failed += req_inflight; - worker->stats.req_error += req_inflight; + if (req_inflight > 0) { + // At the moment, we don't have a facility to re-start request + // already in in-flight. Make them fail. + worker->stats.req_failed += req_inflight; + worker->stats.req_error += req_inflight; - req_inflight = 0; + req_inflight = 0; + } else { + req_until_reconnect_rearmed = false; + } } // Keep using current address @@ -1343,7 +1351,7 @@ int get_ev_loop_flags() { } // namespace Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, - size_t rate, size_t max_samples, Config *config) + uint32_t requests_until_reconnect, size_t rate, size_t max_samples, Config *config) : stats(req_todo, nclients), loop(ev_loop_new(get_ev_loop_flags())), ssl_ctx(ssl_ctx), @@ -1355,6 +1363,7 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, nclients(nclients), nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients), + requests_until_reconnect(requests_until_reconnect), rate(rate), max_samples(max_samples), next_client_id(0) { @@ -1430,7 +1439,8 @@ void Worker::run() { --nreqs_rem; } - auto client = std::make_unique(next_client_id++, this, req_todo); + auto client = std::make_unique(next_client_id++, this, req_todo, + requests_until_reconnect); if (client->connect() != 0) { std::cerr << "client could not connect to host" << std::endl; client->fail(); @@ -1837,14 +1847,20 @@ std::unique_ptr create_worker(uint32_t id, SSL_CTX *ssl_ctx, << " total requests" << std::endl; } + if (config.requests_until_reconnect > 0) { + std::cout << " number of requests until reconnect: " << + config.requests_until_reconnect << std::endl; + } + if (config.is_rate_mode()) { - return std::make_unique(id, ssl_ctx, nreqs, nclients, rate, - max_samples, &config); + return std::make_unique(id, ssl_ctx, nreqs, nclients, + config.requests_until_reconnect, rate, max_samples, &config); } else { // Here rate is same as client because the rate_timeout callback // will be called only once - return std::make_unique(id, ssl_ctx, nreqs, nclients, nclients, - max_samples, &config); + return std::make_unique(id, ssl_ctx, nreqs, nclients, + config.requests_until_reconnect, + nclients, max_samples, &config); } } } // namespace @@ -1921,6 +1937,10 @@ Options: Number of native threads. Default: )" << config.nthreads << R"( + -q, --requests-until-reconnect= + Number of requests to issue per client before a reconnect is + done from that client. Default is 0, which means this behavior + is disabled. -i, --input-file= Path of a file with multiple URIs are separated by EOLs. This option will disable URIs getting from command-line. @@ -2130,10 +2150,11 @@ int main(int argc, char **argv) { {"log-file", required_argument, &flag, 10}, {"connect-to", required_argument, &flag, 11}, {"rps", required_argument, &flag, 12}, + {"requests-until-reconnect", required_argument, nullptr, 'q'}, {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:T:N:D:B:", long_options, + "hvW:c:d:m:n:p:t:w:H:i:r:T:N:D:B:q:", long_options, &option_index); if (c == -1) { break; @@ -2143,6 +2164,9 @@ int main(int argc, char **argv) { config.nreqs = strtoul(optarg, nullptr, 10); nreqs_set_manually = true; break; + case 'q': + config.requests_until_reconnect = strtoul(optarg, nullptr, 10); + break; case 'c': config.nclients = strtoul(optarg, nullptr, 10); break; diff --git a/src/h2load.h b/src/h2load.h index fead67c5..63258d3f 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -104,6 +104,7 @@ struct Config { uint16_t default_port; uint16_t connect_to_port; bool verbose; + uint32_t requests_until_reconnect; bool timing_script; std::string base_uri; // true if UNIX domain socket is used. In this case, base_uri is @@ -268,6 +269,9 @@ struct Worker { ev_timer timeout_watcher; // The next client ID this worker assigns uint32_t next_client_id; + // the number of requests that should be processed before the client + // reconnects + const uint32_t requests_until_reconnect; // Keeps track of the current phase (for timing-based experiment) for the // worker Phase current_phase; @@ -279,7 +283,7 @@ struct Worker { ev_timer warmup_watcher; Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients, - size_t rate, size_t max_samples, Config *config); + uint32_t reqs_until_reconnect, size_t rate, size_t max_samples, Config *config); ~Worker(); Worker(Worker &&o) = default; void run(); @@ -329,6 +333,8 @@ struct Client { size_t req_started; // The number of requests this client has done so far. size_t req_done; + // The number of requests this client has left until a reconnect is done. + uint32_t req_until_reconnect; // The client id per worker uint32_t id; int fd; @@ -336,6 +342,7 @@ struct Client { ev_timer conn_inactivity_watcher; std::string selected_proto; bool new_connection_requested; + bool req_until_reconnect_rearmed; // true if the current connection will be closed, and no more new // request cannot be processed. bool final; @@ -356,7 +363,8 @@ struct Client { enum { ERR_CONNECT_FAIL = -100 }; - Client(uint32_t id, Worker *worker, size_t req_todo); + Client(uint32_t id, Worker *worker, size_t req_todo, uint32_t + requests_until_reconnect); ~Client(); int make_socket(addrinfo *addr); int connect(); diff --git a/src/h2load_http1_session.cc b/src/h2load_http1_session.cc index 14e4d580..31568e55 100644 --- a/src/h2load_http1_session.cc +++ b/src/h2load_http1_session.cc @@ -89,18 +89,25 @@ int htp_msg_completecb(llhttp_t *htp) { session->stream_resp_counter_ += 2; - if (client->final) { + if (client->final || (client->worker->requests_until_reconnect > 0 && + client->req_until_reconnect == 0)) { session->stream_req_counter_ = session->stream_resp_counter_; // Connection is going down. If we have still request to do, // create new connection and keep on doing the job. if (client->req_left) { + client->req_until_reconnect = client->worker->requests_until_reconnect; + client->req_until_reconnect_rearmed = true; client->try_new_connection(); } return HPE_PAUSED; } + if (client->worker->requests_until_reconnect > 0) { + client->req_until_reconnect--; + } + return 0; } } // namespace