diff --git a/README.rst b/README.rst index 84cfae92..480bb25e 100644 --- a/README.rst +++ b/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:: diff --git a/src/h2load.cc b/src/h2load.cc index d8611a86..e571265d 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -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> &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( + req_stat.stream_close_time - req_stat.request_time); + if (lower <= t.count() && t.count() <= upper) { + ++m; + } + } + } + return (m / static_cast(n)) * 100; +} +} // namespace + +namespace { +TimeStats +process_time_stats(const std::vector> &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( + 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(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> workers; + workers.reserve(config.nthreads - 1); + #ifndef NOTHREADS - std::vector> futures; + std::vector> 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(i, ssl_ctx, nreqs, nclients, &config); - futures.push_back(std::async(std::launch::async, - [](std::unique_ptr worker) { - worker->run(); - - return worker->stats; - }, - std::move(worker))); + workers.push_back( + util::make_unique(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( + 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( - end - start).count(); + auto duration = + std::chrono::duration_cast(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(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(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); diff --git a/src/h2load.h b/src/h2load.h index 41268b4d..7a624940 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include @@ -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 status; + // The statistics per request + std::vector 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(); }; diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc index 998419de..c9228bc1 100644 --- a/src/h2load_http2_session.cc +++ b/src/h2load_http2_session.cc @@ -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(user_data); - client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR); + auto req_stat = static_cast( + 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(user_data); + client->on_request(frame->hd.stream_id); + auto req_stat = static_cast( + 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) { diff --git a/src/h2load_http2_session.h b/src/h2load_http2_session.h index d32b8c40..a06e4118 100644 --- a/src/h2load_http2_session.h +++ b/src/h2load_http2_session.h @@ -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(); diff --git a/src/h2load_session.h b/src/h2load_session.h index 8740535c..4231b914 100644 --- a/src/h2load_session.h +++ b/src/h2load_session.h @@ -30,6 +30,8 @@ #include #include +#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; diff --git a/src/h2load_spdy_session.cc b/src/h2load_spdy_session.cc index fb4a55b7..afa5b9bc 100644 --- a/src/h2load_spdy_session.cc +++ b/src/h2load_spdy_session.cc @@ -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(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(user_data); - client->on_stream_close(stream_id, status_code == SPDYLAY_OK); + auto req_stat = static_cast( + 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) { diff --git a/src/h2load_spdy_session.h b/src/h2load_spdy_session.h index 07e7b91d..f76f25ac 100644 --- a/src/h2load_spdy_session.h +++ b/src/h2load_spdy_session.h @@ -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(); diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index b3c624ef..b4b4af9e 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -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) || diff --git a/src/util.cc b/src/util.cc index c5fce569..63c9be55 100644 --- a/src/util.cc +++ b/src/util.cc @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -1031,6 +1032,27 @@ std::string duration_str(double t) { return utos(static_cast(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(t) / d) + unit; +} + +std::string dtos(double n) { + auto f = utos(static_cast(round(100. * n)) % 100); + return utos(static_cast(n)) + "." + (f.size() == 1 ? "0" : "") + f; +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index f653efcf..9f48411f 100644 --- a/src/util.h +++ b/src/util.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -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 std::string utos(T n) { std::string res; if (n == 0) { @@ -359,6 +363,26 @@ template std::string utos_with_unit(T n) { return utos(n) + u; } +// Like utos_with_unit(), but 2 digits fraction part is followed. +template 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(n) / (1 << b)) + u; +} + extern const char UPPER_XDIGITS[]; template 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 diff --git a/src/util_test.cc b/src/util_test.cc index 4b406dfc..05e5dba9 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -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 diff --git a/src/util_test.h b/src/util_test.h index 1c256ec1..511ef139 100644 --- a/src/util_test.h +++ b/src/util_test.h @@ -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