h2load: Add request stats (time for request min, max, mean and sd)
This commit is contained in:
parent
bbc34904c1
commit
a91e0de06c
23
README.rst
23
README.rst
|
@ -514,8 +514,11 @@ library. The UI of ``h2load`` is heavily inspired by ``weighttp``
|
|||
(https://github.com/lighttpd/weighttp). The typical usage is as
|
||||
follows::
|
||||
|
||||
$ src/h2load -n1000 -c10 -m10 https://127.0.0.1:8443/
|
||||
$ src/h2load -n100000 -c100 -m100 https://localhost:8443/
|
||||
starting benchmark...
|
||||
spawning thread #0: 100 concurrent clients, 100000 total requests
|
||||
Protocol: TLSv1.2
|
||||
Cipher: ECDHE-RSA-AES128-GCM-SHA256
|
||||
progress: 10% done
|
||||
progress: 20% done
|
||||
progress: 30% done
|
||||
|
@ -527,15 +530,17 @@ follows::
|
|||
progress: 90% done
|
||||
progress: 100% done
|
||||
|
||||
finished in 0 sec, 152 millisec and 152 microsec, 6572 req/s, 749 kbytes/s
|
||||
requests: 1000 total, 1000 started, 1000 done, 0 succeeded, 1000 failed, 0 errored
|
||||
status codes: 0 2xx, 0 3xx, 1000 4xx, 0 5xx
|
||||
traffic: 141100 bytes total, 840 bytes headers, 116000 bytes data
|
||||
finished in 7.10s, 14092 req/s, 55.67MB/s
|
||||
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored
|
||||
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
|
||||
traffic: 414200800 bytes total, 2723100 bytes headers, 409600000 bytes data
|
||||
min max mean sd +/- sd
|
||||
time for request: 283.86ms 1.46s 659.70ms 150.87ms 84.68%
|
||||
|
||||
The above example issued total 1000 requests, using 10 concurrent
|
||||
clients (thus 10 HTTP/2 sessions), and maximum 10 streams per client.
|
||||
With ``-t`` option, ``h2load`` will use multiple native threads to
|
||||
avoid saturating single core on client side.
|
||||
The above example issued total 100000 requests, using 100 concurrent
|
||||
clients (in other words, 100 HTTP/2 sessions), and maximum 100 streams
|
||||
per client. With ``-t`` option, ``h2load`` will use multiple native
|
||||
threads to avoid saturating single core on client side.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
|
242
src/h2load.cc
242
src/h2load.cc
|
@ -33,6 +33,7 @@
|
|||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
@ -93,6 +94,19 @@ void debug_nextproto_error() {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::chrono::steady_clock::time_point get_current_time() {
|
||||
return std::chrono::steady_clock::now();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
RequestStat::RequestStat() : completed(false) {}
|
||||
|
||||
Stats::Stats(size_t req_todo)
|
||||
: req_todo(0), req_started(0), req_done(0), req_success(0),
|
||||
req_status_success(0), req_failed(0), req_error(0), bytes_total(0),
|
||||
bytes_head(0), bytes_body(0), status(), req_stats(req_todo) {}
|
||||
|
||||
Stream::Stream() : status_success(-1) {}
|
||||
|
||||
namespace {
|
||||
|
@ -217,8 +231,11 @@ void Client::disconnect() {
|
|||
}
|
||||
|
||||
void Client::submit_request() {
|
||||
session->submit_request();
|
||||
++worker->stats.req_started;
|
||||
auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
|
||||
// TODO It would be more accurate to record time when
|
||||
// HEADERS/SYN_STREAM was transmitted (e.g., on_frame_sent_callback)
|
||||
req_stat->request_time = get_current_time();
|
||||
session->submit_request(req_stat);
|
||||
++req_started;
|
||||
}
|
||||
|
||||
|
@ -352,11 +369,17 @@ void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
|||
}
|
||||
}
|
||||
|
||||
void Client::on_stream_close(int32_t stream_id, bool success) {
|
||||
void Client::on_stream_close(int32_t stream_id, bool success,
|
||||
RequestStat *req_stat) {
|
||||
req_stat->stream_close_time = get_current_time();
|
||||
if (success) {
|
||||
req_stat->completed = true;
|
||||
++worker->stats.req_success;
|
||||
}
|
||||
++worker->stats.req_done;
|
||||
++req_done;
|
||||
if (success && streams[stream_id].status_success == 1) {
|
||||
++worker->stats.req_success;
|
||||
++worker->stats.req_status_success;
|
||||
} else {
|
||||
++worker->stats.req_failed;
|
||||
}
|
||||
|
@ -668,12 +691,16 @@ int Client::write_tls() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void Client::record_request_time(RequestStat *req_stat) {
|
||||
req_stat->request_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
||||
|
||||
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||
Config *config)
|
||||
: stats{0}, loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config), id(id),
|
||||
tls_info_report_done(false) {
|
||||
: stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
|
||||
id(id), tls_info_report_done(false) {
|
||||
stats.req_todo = req_todo;
|
||||
progress_interval = std::max((size_t)1, req_todo / 10);
|
||||
|
||||
|
@ -707,6 +734,74 @@ void Worker::run() {
|
|||
ev_run(loop, 0);
|
||||
}
|
||||
|
||||
namespace {
|
||||
double within_sd(const std::vector<std::unique_ptr<Worker>> &workers,
|
||||
const std::chrono::microseconds &mean,
|
||||
const std::chrono::microseconds &sd, size_t n) {
|
||||
auto upper = mean.count() + sd.count();
|
||||
auto lower = mean.count() - sd.count();
|
||||
size_t m = 0;
|
||||
for (const auto &w : workers) {
|
||||
for (const auto &req_stat : w->stats.req_stats) {
|
||||
if (!req_stat.completed) {
|
||||
continue;
|
||||
}
|
||||
auto t = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
req_stat.stream_close_time - req_stat.request_time);
|
||||
if (lower <= t.count() && t.count() <= upper) {
|
||||
++m;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (m / static_cast<double>(n)) * 100;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
TimeStats
|
||||
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
||||
auto ts = TimeStats();
|
||||
int64_t sum = 0;
|
||||
size_t n = 0;
|
||||
|
||||
ts.time_min = std::chrono::microseconds::max();
|
||||
ts.time_max = std::chrono::microseconds::min();
|
||||
ts.within_sd = 0.;
|
||||
|
||||
// standard deviation calculated using Rapid calculation method:
|
||||
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||
double a = 0, q = 0;
|
||||
for (const auto &w : workers) {
|
||||
for (const auto &req_stat : w->stats.req_stats) {
|
||||
if (!req_stat.completed) {
|
||||
continue;
|
||||
}
|
||||
++n;
|
||||
auto t = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
req_stat.stream_close_time - req_stat.request_time);
|
||||
ts.time_min = std::min(ts.time_min, t);
|
||||
ts.time_max = std::max(ts.time_max, t);
|
||||
sum += t.count();
|
||||
|
||||
auto na = a + (t.count() - a) / n;
|
||||
q = q + (t.count() - a) * (t.count() - na);
|
||||
a = na;
|
||||
}
|
||||
}
|
||||
if (n == 0) {
|
||||
ts.time_max = ts.time_min = std::chrono::microseconds::zero();
|
||||
return ts;
|
||||
}
|
||||
|
||||
ts.time_mean = std::chrono::microseconds(sum / n);
|
||||
ts.time_sd = std::chrono::microseconds(
|
||||
static_cast<std::chrono::microseconds::rep>(sqrt(q / n)));
|
||||
|
||||
ts.within_sd = within_sd(workers, ts.time_mean, ts.time_sd, n);
|
||||
return ts;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void resolve_host() {
|
||||
int rv;
|
||||
|
@ -1229,23 +1324,22 @@ int main(int argc, char **argv) {
|
|||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
std::vector<std::unique_ptr<Worker>> workers;
|
||||
workers.reserve(config.nthreads - 1);
|
||||
|
||||
#ifndef NOTHREADS
|
||||
std::vector<std::future<Stats>> futures;
|
||||
std::vector<std::future<void>> futures;
|
||||
for (size_t i = 0; i < config.nthreads - 1; ++i) {
|
||||
auto nreqs = nreqs_per_thread + (nreqs_rem-- > 0);
|
||||
auto nclients = nclients_per_thread + (nclients_rem-- > 0);
|
||||
std::cout << "spawning thread #" << i << ": " << nclients
|
||||
<< " concurrent clients, " << nreqs << " total requests"
|
||||
<< std::endl;
|
||||
auto worker =
|
||||
util::make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config);
|
||||
futures.push_back(std::async(std::launch::async,
|
||||
[](std::unique_ptr<Worker> worker) {
|
||||
worker->run();
|
||||
|
||||
return worker->stats;
|
||||
},
|
||||
std::move(worker)));
|
||||
workers.push_back(
|
||||
util::make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config));
|
||||
auto &worker = workers.back();
|
||||
futures.push_back(
|
||||
std::async(std::launch::async, [&worker]() { worker->run(); }));
|
||||
}
|
||||
#endif // NOTHREADS
|
||||
|
||||
|
@ -1254,73 +1348,81 @@ int main(int argc, char **argv) {
|
|||
std::cout << "spawning thread #" << (config.nthreads - 1) << ": "
|
||||
<< nclients_last << " concurrent clients, " << nreqs_last
|
||||
<< " total requests" << std::endl;
|
||||
Worker worker(config.nthreads - 1, ssl_ctx, nreqs_last, nclients_last,
|
||||
&config);
|
||||
worker.run();
|
||||
workers.push_back(util::make_unique<Worker>(
|
||||
config.nthreads - 1, ssl_ctx, nreqs_last, nclients_last, &config));
|
||||
workers.back()->run();
|
||||
|
||||
#ifndef NOTHREADS
|
||||
for (auto &fut : futures) {
|
||||
auto stats = fut.get();
|
||||
|
||||
worker.stats.req_todo += stats.req_todo;
|
||||
worker.stats.req_started += stats.req_started;
|
||||
worker.stats.req_done += stats.req_done;
|
||||
worker.stats.req_success += stats.req_success;
|
||||
worker.stats.req_failed += stats.req_failed;
|
||||
worker.stats.req_error += stats.req_error;
|
||||
worker.stats.bytes_total += stats.bytes_total;
|
||||
worker.stats.bytes_head += stats.bytes_head;
|
||||
worker.stats.bytes_body += stats.bytes_body;
|
||||
|
||||
for (size_t i = 0; i < util::array_size(stats.status); ++i) {
|
||||
worker.stats.status[i] += stats.status[i];
|
||||
}
|
||||
fut.get();
|
||||
}
|
||||
#endif // NOTHREADS
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
end - start).count();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
Stats stats(0);
|
||||
for (const auto &w : workers) {
|
||||
const auto &s = w->stats;
|
||||
|
||||
stats.req_todo += s.req_todo;
|
||||
stats.req_started += s.req_started;
|
||||
stats.req_done += s.req_done;
|
||||
stats.req_success += s.req_success;
|
||||
stats.req_status_success += s.req_status_success;
|
||||
stats.req_failed += s.req_failed;
|
||||
stats.req_error += s.req_error;
|
||||
stats.bytes_total += s.bytes_total;
|
||||
stats.bytes_head += s.bytes_head;
|
||||
stats.bytes_body += s.bytes_body;
|
||||
|
||||
for (size_t i = 0; i < stats.status.size(); ++i) {
|
||||
stats.status[i] += s.status[i];
|
||||
}
|
||||
}
|
||||
|
||||
auto time_stats = process_time_stats(workers);
|
||||
|
||||
// Requests which have not been issued due to connection errors, are
|
||||
// counted towards req_failed and req_error.
|
||||
auto req_not_issued = worker.stats.req_todo - worker.stats.req_success -
|
||||
worker.stats.req_failed;
|
||||
worker.stats.req_failed += req_not_issued;
|
||||
worker.stats.req_error += req_not_issued;
|
||||
auto req_not_issued =
|
||||
stats.req_todo - stats.req_status_success - stats.req_failed;
|
||||
stats.req_failed += req_not_issued;
|
||||
stats.req_error += req_not_issued;
|
||||
|
||||
// UI is heavily inspired by weighttp
|
||||
// https://github.com/lighttpd/weighttp
|
||||
size_t rps;
|
||||
int64_t kbps;
|
||||
if (duration > 0) {
|
||||
auto secd = static_cast<double>(duration) / (1000 * 1000);
|
||||
rps = worker.stats.req_todo / secd;
|
||||
kbps = worker.stats.bytes_total / secd / 1024;
|
||||
} else {
|
||||
rps = 0;
|
||||
kbps = 0;
|
||||
// UI is heavily inspired by weighttp[1] and wrk[2]
|
||||
//
|
||||
// [1] https://github.com/lighttpd/weighttp
|
||||
// [2] https://github.com/wg/wrk
|
||||
size_t rps = 0;
|
||||
int64_t bps = 0;
|
||||
if (duration.count() > 0) {
|
||||
auto secd = static_cast<double>(duration.count()) / (1000 * 1000);
|
||||
rps = stats.req_success / secd;
|
||||
bps = stats.bytes_total / secd;
|
||||
}
|
||||
|
||||
auto sec = duration / (1000 * 1000);
|
||||
auto millisec = (duration / 1000) % 1000;
|
||||
auto microsec = duration % 1000;
|
||||
|
||||
std::cout << "\n"
|
||||
<< "finished in " << sec << " sec, " << millisec << " millisec and "
|
||||
<< microsec << " microsec, " << rps << " req/s, " << kbps
|
||||
<< " kbytes/s\n"
|
||||
<< "requests: " << worker.stats.req_todo << " total, "
|
||||
<< worker.stats.req_started << " started, " << worker.stats.req_done
|
||||
<< " done, " << worker.stats.req_success << " succeeded, "
|
||||
<< worker.stats.req_failed << " failed, " << worker.stats.req_error
|
||||
<< " errored\n"
|
||||
<< "status codes: " << worker.stats.status[2] << " 2xx, "
|
||||
<< worker.stats.status[3] << " 3xx, " << worker.stats.status[4]
|
||||
<< " 4xx, " << worker.stats.status[5] << " 5xx\n"
|
||||
<< "traffic: " << worker.stats.bytes_total << " bytes total, "
|
||||
<< worker.stats.bytes_head << " bytes headers, "
|
||||
<< worker.stats.bytes_body << " bytes data" << std::endl;
|
||||
std::cout << R"(
|
||||
finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
|
||||
<< util::utos_with_funit(bps) << R"(B/s
|
||||
requests: )" << stats.req_todo << " total, " << stats.req_started
|
||||
<< " started, " << stats.req_done << " done, "
|
||||
<< stats.req_status_success << " succeeded, " << stats.req_failed
|
||||
<< " failed, " << stats.req_error << R"( errored
|
||||
status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
|
||||
<< stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
|
||||
traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
|
||||
<< " bytes headers, " << stats.bytes_body << R"( bytes data
|
||||
min max mean sd +/- sd
|
||||
time for request: )" << std::setw(10)
|
||||
<< util::format_duration(time_stats.time_min) << " "
|
||||
<< std::setw(10) << util::format_duration(time_stats.time_max)
|
||||
<< " " << std::setw(10)
|
||||
<< util::format_duration(time_stats.time_mean) << " "
|
||||
<< std::setw(10) << util::format_duration(time_stats.time_sd)
|
||||
<< std::setw(9) << util::dtos(time_stats.within_sd) << "%"
|
||||
<< std::endl;
|
||||
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
|
||||
|
|
37
src/h2load.h
37
src/h2load.h
|
@ -35,6 +35,8 @@
|
|||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <array>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
|
@ -75,16 +77,38 @@ struct Config {
|
|||
~Config();
|
||||
};
|
||||
|
||||
struct RequestStat {
|
||||
RequestStat();
|
||||
// time point when request was sent
|
||||
std::chrono::steady_clock::time_point request_time;
|
||||
// time point when stream was closed
|
||||
std::chrono::steady_clock::time_point stream_close_time;
|
||||
// true if stream was successfully closed. This means stream was
|
||||
// not reset, but it does not mean HTTP level error (e.g., 404).
|
||||
bool completed;
|
||||
};
|
||||
|
||||
struct TimeStats {
|
||||
// time for request: max, min, mean and sd (standard deviation)
|
||||
std::chrono::microseconds time_max, time_min, time_mean, time_sd;
|
||||
// percentage of number of requests inside mean -/+ sd
|
||||
double within_sd;
|
||||
};
|
||||
|
||||
struct Stats {
|
||||
Stats(size_t req_todo);
|
||||
// The total number of requests
|
||||
size_t req_todo;
|
||||
// The number of requests issued so far
|
||||
size_t req_started;
|
||||
// The number of requests finished
|
||||
size_t req_done;
|
||||
// The number of requests marked as success. This is subset of
|
||||
// req_done.
|
||||
// The number of requests completed successfull, but not necessarily
|
||||
// means successful HTTP status code.
|
||||
size_t req_success;
|
||||
// The number of requests marked as success. HTTP status code is
|
||||
// also considered as success. This is subset of req_done.
|
||||
size_t req_status_success;
|
||||
// The number of requests failed. This is subset of req_done.
|
||||
size_t req_failed;
|
||||
// The number of requests failed due to network errors. This is
|
||||
|
@ -99,7 +123,9 @@ struct Stats {
|
|||
int64_t bytes_body;
|
||||
// The number of each HTTP status category, status[i] is status code
|
||||
// in the range [i*100, (i+1)*100).
|
||||
size_t status[6];
|
||||
std::array<size_t, 6> status;
|
||||
// The statistics per request
|
||||
std::vector<RequestStat> req_stats;
|
||||
};
|
||||
|
||||
enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
|
||||
|
@ -119,6 +145,7 @@ struct Worker {
|
|||
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
|
||||
Config *config);
|
||||
~Worker();
|
||||
Worker(Worker &&o) = default;
|
||||
void run();
|
||||
};
|
||||
|
||||
|
@ -180,7 +207,9 @@ struct Client {
|
|||
void on_request(int32_t stream_id);
|
||||
void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen);
|
||||
void on_stream_close(int32_t stream_id, bool success);
|
||||
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat);
|
||||
|
||||
void record_request_time(RequestStat *req_stat);
|
||||
|
||||
void signal_write();
|
||||
};
|
||||
|
|
|
@ -80,7 +80,27 @@ namespace {
|
|||
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||
uint32_t error_code, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
||||
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR, req_stat);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int before_frame_send_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data) {
|
||||
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
client->on_request(frame->hd.stream_id);
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
client->record_request_time(req_stat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -121,6 +141,9 @@ void Http2Session::on_connect() {
|
|||
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
||||
on_header_callback);
|
||||
|
||||
nghttp2_session_callbacks_set_before_frame_send_callback(
|
||||
callbacks, before_frame_send_callback);
|
||||
|
||||
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
||||
|
||||
nghttp2_session_client_new(&session_, callbacks, client_);
|
||||
|
@ -153,7 +176,7 @@ void Http2Session::on_connect() {
|
|||
client_->signal_write();
|
||||
}
|
||||
|
||||
void Http2Session::submit_request() {
|
||||
void Http2Session::submit_request(RequestStat *req_stat) {
|
||||
auto config = client_->worker->config;
|
||||
auto &nva = config->nva[client_->reqidx++];
|
||||
|
||||
|
@ -162,10 +185,8 @@ void Http2Session::submit_request() {
|
|||
}
|
||||
|
||||
auto stream_id = nghttp2_submit_request(session_, nullptr, nva.data(),
|
||||
nva.size(), nullptr, nullptr);
|
||||
nva.size(), nullptr, req_stat);
|
||||
assert(stream_id > 0);
|
||||
|
||||
client_->on_request(stream_id);
|
||||
}
|
||||
|
||||
int Http2Session::on_read(const uint8_t *data, size_t len) {
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
Http2Session(Client *client);
|
||||
virtual ~Http2Session();
|
||||
virtual void on_connect();
|
||||
virtual void submit_request();
|
||||
virtual void submit_request(RequestStat *req_stat);
|
||||
virtual int on_read(const uint8_t *data, size_t len);
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "h2load.h"
|
||||
|
||||
namespace h2load {
|
||||
|
||||
class Session {
|
||||
|
@ -38,7 +40,7 @@ public:
|
|||
// Called when the connection was made.
|
||||
virtual void on_connect() = 0;
|
||||
// Called when one request must be issued.
|
||||
virtual void submit_request() = 0;
|
||||
virtual void submit_request(RequestStat *req_stat) = 0;
|
||||
// Called when incoming bytes are available. The subclass has to
|
||||
// return the number of bytes read.
|
||||
virtual int on_read(const uint8_t *data, size_t len) = 0;
|
||||
|
|
|
@ -47,6 +47,10 @@ void before_ctrl_send_callback(spdylay_session *session,
|
|||
return;
|
||||
}
|
||||
client->on_request(frame->syn_stream.stream_id);
|
||||
auto req_stat =
|
||||
static_cast<RequestStat *>(spdylay_session_get_stream_user_data(
|
||||
session, frame->syn_stream.stream_id));
|
||||
client->record_request_time(req_stat);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -86,7 +90,9 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
|
|||
spdylay_status_code status_code,
|
||||
void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
client->on_stream_close(stream_id, status_code == SPDYLAY_OK);
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
spdylay_session_get_stream_user_data(session, stream_id));
|
||||
client->on_stream_close(stream_id, status_code == SPDYLAY_OK, req_stat);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -137,7 +143,7 @@ void SpdySession::on_connect() {
|
|||
client_->signal_write();
|
||||
}
|
||||
|
||||
void SpdySession::submit_request() {
|
||||
void SpdySession::submit_request(RequestStat *req_stat) {
|
||||
auto config = client_->worker->config;
|
||||
auto &nv = config->nv[client_->reqidx++];
|
||||
|
||||
|
@ -145,7 +151,7 @@ void SpdySession::submit_request() {
|
|||
client_->reqidx = 0;
|
||||
}
|
||||
|
||||
spdylay_submit_request(session_, 0, nv.data(), nullptr, nullptr);
|
||||
spdylay_submit_request(session_, 0, nv.data(), nullptr, req_stat);
|
||||
}
|
||||
|
||||
int SpdySession::on_read(const uint8_t *data, size_t len) {
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
SpdySession(Client *client, uint16_t spdy_version);
|
||||
virtual ~SpdySession();
|
||||
virtual void on_connect();
|
||||
virtual void submit_request();
|
||||
virtual void submit_request(RequestStat *req_stat);
|
||||
virtual int on_read(const uint8_t *data, size_t len);
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
|
|
|
@ -132,6 +132,8 @@ int main(int argc, char *argv[]) {
|
|||
shrpx::test_util_ipv6_numeric_addr) ||
|
||||
!CU_add_test(pSuite, "util_utos_with_unit",
|
||||
shrpx::test_util_utos_with_unit) ||
|
||||
!CU_add_test(pSuite, "util_utos_with_funit",
|
||||
shrpx::test_util_utos_with_funit) ||
|
||||
!CU_add_test(pSuite, "util_parse_uint_with_unit",
|
||||
shrpx::test_util_parse_uint_with_unit) ||
|
||||
!CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) ||
|
||||
|
@ -139,6 +141,8 @@ int main(int argc, char *argv[]) {
|
|||
shrpx::test_util_parse_duration_with_unit) ||
|
||||
!CU_add_test(pSuite, "util_duration_str",
|
||||
shrpx::test_util_duration_str) ||
|
||||
!CU_add_test(pSuite, "util_format_duration",
|
||||
shrpx::test_util_format_duration) ||
|
||||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
||||
!CU_add_test(pSuite, "ringbuf_write", nghttp2::test_ringbuf_write) ||
|
||||
!CU_add_test(pSuite, "ringbuf_iovec", nghttp2::test_ringbuf_iovec) ||
|
||||
|
|
22
src/util.cc
22
src/util.cc
|
@ -34,6 +34,7 @@
|
|||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cerrno>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
@ -1031,6 +1032,27 @@ std::string duration_str(double t) {
|
|||
return utos(static_cast<int64_t>(t)) + "s";
|
||||
}
|
||||
|
||||
std::string format_duration(const std::chrono::microseconds &u) {
|
||||
const char *unit = "us";
|
||||
int d = 0;
|
||||
auto t = u.count();
|
||||
if (t >= 1000000) {
|
||||
d = 1000000;
|
||||
unit = "s";
|
||||
} else if (t >= 1000) {
|
||||
d = 1000;
|
||||
unit = "ms";
|
||||
} else {
|
||||
return utos(t) + unit;
|
||||
}
|
||||
return dtos(static_cast<double>(t) / d) + unit;
|
||||
}
|
||||
|
||||
std::string dtos(double n) {
|
||||
auto f = utos(static_cast<int64_t>(round(100. * n)) % 100);
|
||||
return utos(static_cast<int64_t>(n)) + "." + (f.size() == 1 ? "0" : "") + f;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
} // namespace nghttp2
|
||||
|
|
30
src/util.h
30
src/util.h
|
@ -30,6 +30,7 @@
|
|||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
@ -323,6 +324,9 @@ inline char lowcase(char c) {
|
|||
// Lowercase |s| in place.
|
||||
void inp_strlower(std::string &s);
|
||||
|
||||
// Returns string representation of |n| with 2 fractional digits.
|
||||
std::string dtos(double n);
|
||||
|
||||
template <typename T> std::string utos(T n) {
|
||||
std::string res;
|
||||
if (n == 0) {
|
||||
|
@ -359,6 +363,26 @@ template <typename T> std::string utos_with_unit(T n) {
|
|||
return utos(n) + u;
|
||||
}
|
||||
|
||||
// Like utos_with_unit(), but 2 digits fraction part is followed.
|
||||
template <typename T> std::string utos_with_funit(T n) {
|
||||
char u = 0;
|
||||
int b = 0;
|
||||
if (n >= (1 << 30)) {
|
||||
u = 'G';
|
||||
b = 30;
|
||||
} else if (n >= (1 << 20)) {
|
||||
u = 'M';
|
||||
b = 20;
|
||||
} else if (n >= (1 << 10)) {
|
||||
u = 'K';
|
||||
b = 10;
|
||||
}
|
||||
if (b == 0) {
|
||||
return utos(n);
|
||||
}
|
||||
return dtos(static_cast<double>(n) / (1 << b)) + u;
|
||||
}
|
||||
|
||||
extern const char UPPER_XDIGITS[];
|
||||
|
||||
template <typename T> std::string utox(T n) {
|
||||
|
@ -522,6 +546,12 @@ double parse_duration_with_unit(const char *s);
|
|||
// is left as is and "s" is appended.
|
||||
std::string duration_str(double t);
|
||||
|
||||
// Returns string representation of time duration |t|. It appends
|
||||
// unit after the formatting. The available units are s, ms and us.
|
||||
// The unit which is equal to or less than |t| is used and 2
|
||||
// fractional digits follow.
|
||||
std::string format_duration(const std::chrono::microseconds &u);
|
||||
|
||||
} // namespace util
|
||||
|
||||
} // namespace nghttp2
|
||||
|
|
|
@ -184,6 +184,20 @@ void test_util_utos_with_unit(void) {
|
|||
CU_ASSERT("1024G" == util::utos_with_unit(1LL << 40));
|
||||
}
|
||||
|
||||
void test_util_utos_with_funit(void) {
|
||||
CU_ASSERT("0" == util::utos_with_funit(0));
|
||||
CU_ASSERT("1023" == util::utos_with_funit(1023));
|
||||
CU_ASSERT("1.00K" == util::utos_with_funit(1024));
|
||||
CU_ASSERT("1.00K" == util::utos_with_funit(1025));
|
||||
CU_ASSERT("1.09K" == util::utos_with_funit(1119));
|
||||
CU_ASSERT("1.27K" == util::utos_with_funit(1300));
|
||||
CU_ASSERT("1.00M" == util::utos_with_funit(1 << 20));
|
||||
CU_ASSERT("1.18M" == util::utos_with_funit(1234567));
|
||||
CU_ASSERT("1.00G" == util::utos_with_funit(1 << 30));
|
||||
CU_ASSERT("4492450797.23G" == util::utos_with_funit(4823732313248234343LL));
|
||||
CU_ASSERT("1024.00G" == util::utos_with_funit(1LL << 40));
|
||||
}
|
||||
|
||||
void test_util_parse_uint_with_unit(void) {
|
||||
CU_ASSERT(0 == util::parse_uint_with_unit("0"));
|
||||
CU_ASSERT(1023 == util::parse_uint_with_unit("1023"));
|
||||
|
@ -250,4 +264,18 @@ void test_util_duration_str(void) {
|
|||
CU_ASSERT("1500ms" == util::duration_str(1.5));
|
||||
}
|
||||
|
||||
void test_util_format_duration(void) {
|
||||
CU_ASSERT("0us" == util::format_duration(std::chrono::microseconds(0)));
|
||||
CU_ASSERT("999us" == util::format_duration(std::chrono::microseconds(999)));
|
||||
CU_ASSERT("1.00ms" == util::format_duration(std::chrono::microseconds(1000)));
|
||||
CU_ASSERT("1.09ms" == util::format_duration(std::chrono::microseconds(1090)));
|
||||
CU_ASSERT("1.01ms" == util::format_duration(std::chrono::microseconds(1009)));
|
||||
CU_ASSERT("999.99ms" ==
|
||||
util::format_duration(std::chrono::microseconds(999990)));
|
||||
CU_ASSERT("1.00s" ==
|
||||
util::format_duration(std::chrono::microseconds(1000000)));
|
||||
CU_ASSERT("1.05s" ==
|
||||
util::format_duration(std::chrono::microseconds(1050000)));
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -38,10 +38,12 @@ void test_util_http_date(void);
|
|||
void test_util_select_h2(void);
|
||||
void test_util_ipv6_numeric_addr(void);
|
||||
void test_util_utos_with_unit(void);
|
||||
void test_util_utos_with_funit(void);
|
||||
void test_util_parse_uint_with_unit(void);
|
||||
void test_util_parse_uint(void);
|
||||
void test_util_parse_duration_with_unit(void);
|
||||
void test_util_duration_str(void);
|
||||
void test_util_format_duration(void);
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
|
|
Loading…
Reference in New Issue