Merge branch 'LPardue-h2load'
This commit is contained in:
commit
50ae0ce739
220
src/h2load.cc
220
src/h2load.cc
|
@ -76,7 +76,7 @@ Config::Config()
|
||||||
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
|
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
|
||||||
rate(0), nconns(0), conn_active_timeout(0), conn_inactivity_timeout(0),
|
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),
|
no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0), default_port(0),
|
||||||
verbose(false) {}
|
verbose(false), timing_script(false) {}
|
||||||
|
|
||||||
Config::~Config() {
|
Config::~Config() {
|
||||||
freeaddrinfo(addrs);
|
freeaddrinfo(addrs);
|
||||||
|
@ -87,7 +87,7 @@ Config::~Config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Config::is_rate_mode() const { return (this->rate != 0); }
|
bool Config::is_rate_mode() const { return (this->rate != 0); }
|
||||||
|
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -196,6 +196,51 @@ void conn_timeout_cb(EV_P_ ev_timer *w, int revents) {
|
||||||
client->timeout();
|
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<Client *>(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
|
} // namespace
|
||||||
|
|
||||||
Client::Client(Worker *worker, size_t req_todo)
|
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,
|
ev_timer_init(&conn_active_watcher, conn_timeout_cb,
|
||||||
worker->config->conn_active_timeout, 0.);
|
worker->config->conn_active_timeout, 0.);
|
||||||
conn_active_watcher.data = this;
|
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_read() { return readfn(*this); }
|
||||||
int Client::do_write() { return writefn(*this); }
|
int Client::do_write() { return writefn(*this); }
|
||||||
|
@ -473,10 +524,12 @@ void Client::on_stream_close(int32_t stream_id, bool success,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.timing_script) {
|
||||||
if (req_started < req_todo) {
|
if (req_started < req_todo) {
|
||||||
submit_request();
|
submit_request();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::connection_made() {
|
int Client::connection_made() {
|
||||||
|
@ -547,13 +600,25 @@ int Client::connection_made() {
|
||||||
|
|
||||||
record_connect_time(&worker->stats);
|
record_connect_time(&worker->stats);
|
||||||
|
|
||||||
|
if (!config.timing_script) {
|
||||||
auto nreq =
|
auto nreq =
|
||||||
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
|
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
|
||||||
|
|
||||||
for (; nreq > 0; --nreq) {
|
for (; nreq > 0; --nreq) {
|
||||||
submit_request();
|
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();
|
signal_write();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1003,6 +1068,26 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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 {
|
namespace {
|
||||||
// Use std::vector<std::string>::iterator explicitly, without that,
|
// Use std::vector<std::string>::iterator explicitly, without that,
|
||||||
// http_parser_url u{} fails with clang-3.4.
|
// http_parser_url u{} fails with clang-3.4.
|
||||||
|
@ -1015,30 +1100,16 @@ std::vector<std::string> parse_uris(std::vector<std::string>::iterator first,
|
||||||
exit(EXIT_FAILURE);
|
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
|
if (!parse_base_uri(*first)) {
|
||||||
// this URI and ignore those in the remaining URIs if present.
|
std::cerr << "invalid URI: " << *first << std::endl;
|
||||||
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);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
++first;
|
config.base_uri = *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) {
|
for (; first != last; ++first) {
|
||||||
http_parser_url u{};
|
http_parser_url u{};
|
||||||
|
|
||||||
|
@ -1068,6 +1139,43 @@ std::vector<std::string> read_uri_from_file(std::istream &infile) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void read_script_from_file(std::istream &infile,
|
||||||
|
std::vector<ev_tstamp> &timings,
|
||||||
|
std::vector<std::string> &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 {
|
namespace {
|
||||||
void print_version(std::ostream &out) {
|
void print_version(std::ostream &out) {
|
||||||
out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
|
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
|
are used, then first URI is used and then 2nd URI, and
|
||||||
so on. The scheme, host and port in the subsequent
|
so on. The scheme, host and port in the subsequent
|
||||||
URIs, if present, are ignored. Those in the first URI
|
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:
|
Options:
|
||||||
-n, --requests=<N>
|
-n, --requests=<N>
|
||||||
Number of requests.
|
Number of requests.
|
||||||
|
@ -1112,7 +1221,8 @@ Options:
|
||||||
are used, then first URI is used and then 2nd URI, and
|
are used, then first URI is used and then 2nd URI, and
|
||||||
so on. The scheme, host and port in the subsequent
|
so on. The scheme, host and port in the subsequent
|
||||||
URIs, if present, are ignored. Those in the first URI
|
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|<N>)
|
-m, --max-concurrent-streams=(auto|<N>)
|
||||||
Max concurrent streams to issue per session. If "auto"
|
Max concurrent streams to issue per session. If "auto"
|
||||||
is given, the number of given URIs is used.
|
is given, the number of given URIs is used.
|
||||||
|
@ -1181,6 +1291,29 @@ Options:
|
||||||
wait. When no timeout value is set (either active or
|
wait. When no timeout value is set (either active or
|
||||||
inactive), h2load will keep a connection open
|
inactive), h2load will keep a connection open
|
||||||
indefinitely, waiting for a response.
|
indefinitely, waiting for a response.
|
||||||
|
=======
|
||||||
|
--timing-script-file=<PATH>
|
||||||
|
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 <PATH>, 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=<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
|
-v, --verbose
|
||||||
Output debug information.
|
Output debug information.
|
||||||
--version Display version information and exit.
|
--version Display version information and exit.
|
||||||
|
@ -1220,9 +1353,11 @@ int main(int argc, char **argv) {
|
||||||
{"num-conns", required_argument, nullptr, 'C'},
|
{"num-conns", required_argument, nullptr, 'C'},
|
||||||
{"connection-active-timeout", required_argument, nullptr, 'T'},
|
{"connection-active-timeout", required_argument, nullptr, 'T'},
|
||||||
{"connection-inactivity-timeout", required_argument, nullptr, 'N'},
|
{"connection-inactivity-timeout", required_argument, nullptr, 'N'},
|
||||||
|
{"timing-script-file", required_argument, &flag, 3},
|
||||||
|
{"base-uri", required_argument, nullptr, 'B'},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
int option_index = 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);
|
long_options, &option_index);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
|
@ -1298,10 +1433,9 @@ int main(int argc, char **argv) {
|
||||||
util::inp_strlower(config.custom_headers.back().name);
|
util::inp_strlower(config.custom_headers.back().name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'i': {
|
case 'i':
|
||||||
config.ifile = std::string(optarg);
|
config.ifile = optarg;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case 'p':
|
case 'p':
|
||||||
if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
|
if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
|
||||||
config.no_tls_proto = Config::PROTO_HTTP2;
|
config.no_tls_proto = Config::PROTO_HTTP2;
|
||||||
|
@ -1350,6 +1484,13 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
break;
|
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':
|
case 'v':
|
||||||
config.verbose = true;
|
config.verbose = true;
|
||||||
break;
|
break;
|
||||||
|
@ -1369,6 +1510,10 @@ int main(int argc, char **argv) {
|
||||||
// ciphers option
|
// ciphers option
|
||||||
config.ciphers = optarg;
|
config.ciphers = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
config.ifile = optarg;
|
||||||
|
config.timing_script = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1515,7 +1660,11 @@ int main(int argc, char **argv) {
|
||||||
} else {
|
} else {
|
||||||
std::vector<std::string> uris;
|
std::vector<std::string> uris;
|
||||||
if (config.ifile == "-") {
|
if (config.ifile == "-") {
|
||||||
|
if (!config.timing_script) {
|
||||||
uris = read_uri_from_file(std::cin);
|
uris = read_uri_from_file(std::cin);
|
||||||
|
} else {
|
||||||
|
read_script_from_file(std::cin, config.timings, uris);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
std::ifstream infile(config.ifile);
|
std::ifstream infile(config.ifile);
|
||||||
if (!infile) {
|
if (!infile) {
|
||||||
|
@ -1523,7 +1672,23 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.timing_script) {
|
||||||
uris = read_uri_from_file(infile);
|
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));
|
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(":authority", config.host);
|
||||||
}
|
}
|
||||||
shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
|
shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
|
||||||
|
shared_nva.emplace_back("user-agent", "h2load nghttp2/" NGHTTP2_VERSION);
|
||||||
|
|
||||||
// list overridalbe headers
|
// list overridalbe headers
|
||||||
auto override_hdrs =
|
auto override_hdrs =
|
||||||
|
|
|
@ -62,6 +62,7 @@ struct Worker;
|
||||||
struct Config {
|
struct Config {
|
||||||
std::vector<std::vector<nghttp2_nv>> nva;
|
std::vector<std::vector<nghttp2_nv>> nva;
|
||||||
std::vector<std::vector<const char *>> nv;
|
std::vector<std::vector<const char *>> nv;
|
||||||
|
std::vector<ev_tstamp> timings;
|
||||||
nghttp2::Headers custom_headers;
|
nghttp2::Headers custom_headers;
|
||||||
std::string scheme;
|
std::string scheme;
|
||||||
std::string host;
|
std::string host;
|
||||||
|
@ -91,11 +92,14 @@ struct Config {
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint16_t default_port;
|
uint16_t default_port;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
|
bool timing_script;
|
||||||
|
std::string base_uri;
|
||||||
|
|
||||||
Config();
|
Config();
|
||||||
~Config();
|
~Config();
|
||||||
|
|
||||||
bool is_rate_mode() const;
|
bool is_rate_mode() const;
|
||||||
|
bool has_base_uri() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RequestStat {
|
struct RequestStat {
|
||||||
|
@ -208,6 +212,7 @@ struct Client {
|
||||||
std::function<int(Client &)> readfn, writefn;
|
std::function<int(Client &)> readfn, writefn;
|
||||||
Worker *worker;
|
Worker *worker;
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
|
ev_timer request_timeout_watcher;
|
||||||
addrinfo *next_addr;
|
addrinfo *next_addr;
|
||||||
size_t reqidx;
|
size_t reqidx;
|
||||||
ClientState state;
|
ClientState state;
|
||||||
|
|
Loading…
Reference in New Issue