h2load: Add request stats (time for request min, max, mean and sd)

This commit is contained in:
Tatsuhiro Tsujikawa 2015-01-31 18:13:07 +09:00
parent bbc34904c1
commit a91e0de06c
13 changed files with 345 additions and 94 deletions

View File

@ -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::

View File

@ -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);

View File

@ -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();
};

View File

@ -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) {

View File

@ -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();

View File

@ -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;

View File

@ -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) {

View File

@ -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();

View File

@ -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) ||

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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