From 2cbb7a85db349ea9d527f74e7413231a8986a17b Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 7 Jan 2019 11:36:35 -0800 Subject: [PATCH] perf_hooks: implement histogram based api Add a sampling-based event loop delay monitor. ```js const { monitorEventLoopDelay } = require('perf_hooks'); const h = monitorEventLoopDelay(); h.enable(); h.disable(); console.log(h.percentiles); console.log(h.min); console.log(h.max); console.log(h.mean); console.log(h.stddev); console.log(h.percentile(50)); ``` PR-URL: https://github.com/nodejs/node/pull/25378 Reviewed-By: Matteo Collina Reviewed-By: Gireesh Punathil Reviewed-By: Stephen Belanger Reviewed-By: Richard Lau Reviewed-By: Anna Henningsen --- LICENSE | 45 + deps/histogram/LICENSE.txt | 41 + deps/histogram/README.md | 3 + deps/histogram/histogram.gyp | 15 + deps/histogram/src/hdr_histogram.c | 1032 +++++++++++++++++ deps/histogram/src/hdr_histogram.h | 434 +++++++ deps/histogram/src/hdr_tests.h | 22 + doc/api/perf_hooks.md | 113 ++ lib/perf_hooks.js | 69 +- node.gyp | 7 +- node.gypi | 1 + src/histogram-inl.h | 63 + src/histogram.h | 38 + src/node_perf.cc | 186 +++ src/node_perf.h | 36 + .../test-performance-eventloopdelay.js | 99 ++ tools/doc/type-parser.js | 4 +- tools/license-builder.sh | 2 + 18 files changed, 2207 insertions(+), 3 deletions(-) create mode 100644 deps/histogram/LICENSE.txt create mode 100644 deps/histogram/README.md create mode 100644 deps/histogram/histogram.gyp create mode 100644 deps/histogram/src/hdr_histogram.c create mode 100644 deps/histogram/src/hdr_histogram.h create mode 100644 deps/histogram/src/hdr_tests.h create mode 100644 src/histogram-inl.h create mode 100644 src/histogram.h create mode 100644 test/sequential/test-performance-eventloopdelay.js diff --git a/LICENSE b/LICENSE index f75f7581d35c8f..53176c816407ec 100644 --- a/LICENSE +++ b/LICENSE @@ -1371,3 +1371,48 @@ The externally maintained libraries used by Node.js are: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + +- HdrHistogram, located at deps/histogram, is licensed as follows: + """ + The code in this repository code was Written by Gil Tene, Michael Barker, + and Matt Warren, and released to the public domain, as explained at + http://creativecommons.org/publicdomain/zero/1.0/ + + For users of this code who wish to consume it under the "BSD" license + rather than under the public domain or CC0 contribution text mentioned + above, the code found under this directory is *also* provided under the + following license (commonly referred to as the BSD 2-Clause License). This + license does not detract from the above stated release of the code into + the public domain, and simply represents an additional license granted by + the Author. + + ----------------------------------------------------------------------------- + ** Beginning of "BSD 2-Clause License" text. ** + + Copyright (c) 2012, 2013, 2014 Gil Tene + Copyright (c) 2014 Michael Barker + Copyright (c) 2014 Matt Warren + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + """ diff --git a/deps/histogram/LICENSE.txt b/deps/histogram/LICENSE.txt new file mode 100644 index 00000000000000..9b4e66ed7defab --- /dev/null +++ b/deps/histogram/LICENSE.txt @@ -0,0 +1,41 @@ +The code in this repository code was Written by Gil Tene, Michael Barker, +and Matt Warren, and released to the public domain, as explained at +http://creativecommons.org/publicdomain/zero/1.0/ + +For users of this code who wish to consume it under the "BSD" license +rather than under the public domain or CC0 contribution text mentioned +above, the code found under this directory is *also* provided under the +following license (commonly referred to as the BSD 2-Clause License). This +license does not detract from the above stated release of the code into +the public domain, and simply represents an additional license granted by +the Author. + +----------------------------------------------------------------------------- +** Beginning of "BSD 2-Clause License" text. ** + + Copyright (c) 2012, 2013, 2014 Gil Tene + Copyright (c) 2014 Michael Barker + Copyright (c) 2014 Matt Warren + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/histogram/README.md b/deps/histogram/README.md new file mode 100644 index 00000000000000..b6775fd6dcb603 --- /dev/null +++ b/deps/histogram/README.md @@ -0,0 +1,3 @@ +# HdrHistogram_c + +From: https://github.com/HdrHistogram/HdrHistogram_c diff --git a/deps/histogram/histogram.gyp b/deps/histogram/histogram.gyp new file mode 100644 index 00000000000000..bcfa198f9d37c1 --- /dev/null +++ b/deps/histogram/histogram.gyp @@ -0,0 +1,15 @@ +{ + 'targets': [ + { + 'target_name': 'histogram', + 'type': 'static_library', + 'include_dirs': ['src'], + 'direct_dependent_settings': { + 'include_dirs': [ 'src' ] + }, + 'sources': [ + 'src/hdr_histogram.c', + ] + } + ] +} diff --git a/deps/histogram/src/hdr_histogram.c b/deps/histogram/src/hdr_histogram.c new file mode 100644 index 00000000000000..1d7343442d3b67 --- /dev/null +++ b/deps/histogram/src/hdr_histogram.c @@ -0,0 +1,1032 @@ +/** + * hdr_histogram.c + * Written by Michael Barker and released to the public domain, + * as explained at http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hdr_histogram.h" +#include "hdr_tests.h" + +/* ###### ####### ## ## ## ## ######## ###### */ +/* ## ## ## ## ## ## ### ## ## ## ## */ +/* ## ## ## ## ## #### ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ###### */ +/* ## ## ## ## ## ## #### ## ## */ +/* ## ## ## ## ## ## ## ### ## ## ## */ +/* ###### ####### ####### ## ## ## ###### */ + +static int32_t normalize_index(const struct hdr_histogram* h, int32_t index) +{ + int32_t normalized_index; + int32_t adjustment = 0; + if (h->normalizing_index_offset == 0) + { + return index; + } + + normalized_index = index - h->normalizing_index_offset; + + if (normalized_index < 0) + { + adjustment = h->counts_len; + } + else if (normalized_index >= h->counts_len) + { + adjustment = -h->counts_len; + } + + return normalized_index + adjustment; +} + +static int64_t counts_get_direct(const struct hdr_histogram* h, int32_t index) +{ + return h->counts[index]; +} + +static int64_t counts_get_normalised(const struct hdr_histogram* h, int32_t index) +{ + return counts_get_direct(h, normalize_index(h, index)); +} + +static void counts_inc_normalised( + struct hdr_histogram* h, int32_t index, int64_t value) +{ + int32_t normalised_index = normalize_index(h, index); + h->counts[normalised_index] += value; + h->total_count += value; +} + +static void update_min_max(struct hdr_histogram* h, int64_t value) +{ + h->min_value = (value < h->min_value && value != 0) ? value : h->min_value; + h->max_value = (value > h->max_value) ? value : h->max_value; +} + +/* ## ## ######## #### ## #### ######## ## ## */ +/* ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## #### */ +/* ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## */ +/* ####### ## #### ######## #### ## ## */ + +static int64_t power(int64_t base, int64_t exp) +{ + int64_t result = 1; + while(exp) + { + result *= base; exp--; + } + return result; +} + +#if defined(_MSC_VER) +#pragma intrinsic(_BitScanReverse64) +#endif + +static int32_t get_bucket_index(const struct hdr_histogram* h, int64_t value) +{ +#if defined(_MSC_VER) + uint32_t leading_zero = 0; + _BitScanReverse64(&leading_zero, value | h->sub_bucket_mask); + int32_t pow2ceiling = 64 - (63 - leading_zero); /* smallest power of 2 containing value */ +#else + int32_t pow2ceiling = 64 - __builtin_clzll(value | h->sub_bucket_mask); /* smallest power of 2 containing value */ +#endif + return pow2ceiling - h->unit_magnitude - (h->sub_bucket_half_count_magnitude + 1); +} + +static int32_t get_sub_bucket_index(int64_t value, int32_t bucket_index, int32_t unit_magnitude) +{ + return (int32_t)(value >> (bucket_index + unit_magnitude)); +} + +static int32_t counts_index(const struct hdr_histogram* h, int32_t bucket_index, int32_t sub_bucket_index) +{ + /* Calculate the index for the first entry in the bucket: */ + /* (The following is the equivalent of ((bucket_index + 1) * subBucketHalfCount) ): */ + int32_t bucket_base_index = (bucket_index + 1) << h->sub_bucket_half_count_magnitude; + /* Calculate the offset in the bucket: */ + int32_t offset_in_bucket = sub_bucket_index - h->sub_bucket_half_count; + /* The following is the equivalent of ((sub_bucket_index - subBucketHalfCount) + bucketBaseIndex; */ + return bucket_base_index + offset_in_bucket; +} + +static int64_t value_from_index(int32_t bucket_index, int32_t sub_bucket_index, int32_t unit_magnitude) +{ + return ((int64_t) sub_bucket_index) << (bucket_index + unit_magnitude); +} + +int32_t counts_index_for(const struct hdr_histogram* h, int64_t value) +{ + int32_t bucket_index = get_bucket_index(h, value); + int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, h->unit_magnitude); + + return counts_index(h, bucket_index, sub_bucket_index); +} + +int64_t hdr_value_at_index(const struct hdr_histogram *h, int32_t index) +{ + int32_t bucket_index = (index >> h->sub_bucket_half_count_magnitude) - 1; + int32_t sub_bucket_index = (index & (h->sub_bucket_half_count - 1)) + h->sub_bucket_half_count; + + if (bucket_index < 0) + { + sub_bucket_index -= h->sub_bucket_half_count; + bucket_index = 0; + } + + return value_from_index(bucket_index, sub_bucket_index, h->unit_magnitude); +} + +int64_t hdr_size_of_equivalent_value_range(const struct hdr_histogram* h, int64_t value) +{ + int32_t bucket_index = get_bucket_index(h, value); + int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, h->unit_magnitude); + int32_t adjusted_bucket = (sub_bucket_index >= h->sub_bucket_count) ? (bucket_index + 1) : bucket_index; + return INT64_C(1) << (h->unit_magnitude + adjusted_bucket); +} + +static int64_t lowest_equivalent_value(const struct hdr_histogram* h, int64_t value) +{ + int32_t bucket_index = get_bucket_index(h, value); + int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, h->unit_magnitude); + return value_from_index(bucket_index, sub_bucket_index, h->unit_magnitude); +} + +int64_t hdr_next_non_equivalent_value(const struct hdr_histogram *h, int64_t value) +{ + return lowest_equivalent_value(h, value) + hdr_size_of_equivalent_value_range(h, value); +} + +static int64_t highest_equivalent_value(const struct hdr_histogram* h, int64_t value) +{ + return hdr_next_non_equivalent_value(h, value) - 1; +} + +int64_t hdr_median_equivalent_value(const struct hdr_histogram *h, int64_t value) +{ + return lowest_equivalent_value(h, value) + (hdr_size_of_equivalent_value_range(h, value) >> 1); +} + +static int64_t non_zero_min(const struct hdr_histogram* h) +{ + if (INT64_MAX == h->min_value) + { + return INT64_MAX; + } + + return lowest_equivalent_value(h, h->min_value); +} + +void hdr_reset_internal_counters(struct hdr_histogram* h) +{ + int min_non_zero_index = -1; + int max_index = -1; + int64_t observed_total_count = 0; + int i; + + for (i = 0; i < h->counts_len; i++) + { + int64_t count_at_index; + + if ((count_at_index = counts_get_direct(h, i)) > 0) + { + observed_total_count += count_at_index; + max_index = i; + if (min_non_zero_index == -1 && i != 0) + { + min_non_zero_index = i; + } + } + } + + if (max_index == -1) + { + h->max_value = 0; + } + else + { + int64_t max_value = hdr_value_at_index(h, max_index); + h->max_value = highest_equivalent_value(h, max_value); + } + + if (min_non_zero_index == -1) + { + h->min_value = INT64_MAX; + } + else + { + h->min_value = hdr_value_at_index(h, min_non_zero_index); + } + + h->total_count = observed_total_count; +} + +static int32_t buckets_needed_to_cover_value(int64_t value, int32_t sub_bucket_count, int32_t unit_magnitude) +{ + int64_t smallest_untrackable_value = ((int64_t) sub_bucket_count) << unit_magnitude; + int32_t buckets_needed = 1; + while (smallest_untrackable_value <= value) + { + if (smallest_untrackable_value > INT64_MAX / 2) + { + return buckets_needed + 1; + } + smallest_untrackable_value <<= 1; + buckets_needed++; + } + + return buckets_needed; +} + +/* ## ## ######## ## ## ####### ######## ## ## */ +/* ### ### ## ### ### ## ## ## ## ## ## */ +/* #### #### ## #### #### ## ## ## ## #### */ +/* ## ### ## ###### ## ### ## ## ## ######## ## */ +/* ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ######## ## ## ####### ## ## ## */ + +int hdr_calculate_bucket_config( + int64_t lowest_trackable_value, + int64_t highest_trackable_value, + int significant_figures, + struct hdr_histogram_bucket_config* cfg) +{ + int32_t sub_bucket_count_magnitude; + int64_t largest_value_with_single_unit_resolution; + + if (lowest_trackable_value < 1 || + significant_figures < 1 || 5 < significant_figures) + { + return EINVAL; + } + else if (lowest_trackable_value * 2 > highest_trackable_value) + { + return EINVAL; + } + + cfg->lowest_trackable_value = lowest_trackable_value; + cfg->significant_figures = significant_figures; + cfg->highest_trackable_value = highest_trackable_value; + + largest_value_with_single_unit_resolution = 2 * power(10, significant_figures); + sub_bucket_count_magnitude = (int32_t) ceil(log((double)largest_value_with_single_unit_resolution) / log(2)); + cfg->sub_bucket_half_count_magnitude = ((sub_bucket_count_magnitude > 1) ? sub_bucket_count_magnitude : 1) - 1; + + cfg->unit_magnitude = (int32_t) floor(log((double)lowest_trackable_value) / log(2)); + + cfg->sub_bucket_count = (int32_t) pow(2, (cfg->sub_bucket_half_count_magnitude + 1)); + cfg->sub_bucket_half_count = cfg->sub_bucket_count / 2; + cfg->sub_bucket_mask = ((int64_t) cfg->sub_bucket_count - 1) << cfg->unit_magnitude; + + if (cfg->unit_magnitude + cfg->sub_bucket_half_count_magnitude > 61) + { + return EINVAL; + } + + cfg->bucket_count = buckets_needed_to_cover_value(highest_trackable_value, cfg->sub_bucket_count, (int32_t)cfg->unit_magnitude); + cfg->counts_len = (cfg->bucket_count + 1) * (cfg->sub_bucket_count / 2); + + return 0; +} + +void hdr_init_preallocated(struct hdr_histogram* h, struct hdr_histogram_bucket_config* cfg) +{ + h->lowest_trackable_value = cfg->lowest_trackable_value; + h->highest_trackable_value = cfg->highest_trackable_value; + h->unit_magnitude = (int32_t)cfg->unit_magnitude; + h->significant_figures = (int32_t)cfg->significant_figures; + h->sub_bucket_half_count_magnitude = cfg->sub_bucket_half_count_magnitude; + h->sub_bucket_half_count = cfg->sub_bucket_half_count; + h->sub_bucket_mask = cfg->sub_bucket_mask; + h->sub_bucket_count = cfg->sub_bucket_count; + h->min_value = INT64_MAX; + h->max_value = 0; + h->normalizing_index_offset = 0; + h->conversion_ratio = 1.0; + h->bucket_count = cfg->bucket_count; + h->counts_len = cfg->counts_len; + h->total_count = 0; +} + +int hdr_init( + int64_t lowest_trackable_value, + int64_t highest_trackable_value, + int significant_figures, + struct hdr_histogram** result) +{ + int64_t* counts; + struct hdr_histogram_bucket_config cfg; + struct hdr_histogram* histogram; + + int r = hdr_calculate_bucket_config(lowest_trackable_value, highest_trackable_value, significant_figures, &cfg); + if (r) + { + return r; + } + + counts = calloc((size_t) cfg.counts_len, sizeof(int64_t)); + histogram = calloc(1, sizeof(struct hdr_histogram)); + + if (!counts || !histogram) + { + return ENOMEM; + } + + histogram->counts = counts; + + hdr_init_preallocated(histogram, &cfg); + *result = histogram; + + return 0; +} + +void hdr_close(struct hdr_histogram* h) +{ + free(h->counts); + free(h); +} + +int hdr_alloc(int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result) +{ + return hdr_init(1, highest_trackable_value, significant_figures, result); +} + +/* reset a histogram to zero. */ +void hdr_reset(struct hdr_histogram *h) +{ + h->total_count=0; + h->min_value = INT64_MAX; + h->max_value = 0; + memset(h->counts, 0, (sizeof(int64_t) * h->counts_len)); +} + +size_t hdr_get_memory_size(struct hdr_histogram *h) +{ + return sizeof(struct hdr_histogram) + h->counts_len * sizeof(int64_t); +} + +/* ## ## ######## ######## ### ######## ######## ###### */ +/* ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ######## ## ## ## ## ## ###### ###### */ +/* ## ## ## ## ## ######### ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## */ +/* ####### ## ######## ## ## ## ######## ###### */ + + +bool hdr_record_value(struct hdr_histogram* h, int64_t value) +{ + return hdr_record_values(h, value, 1); +} + +bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count) +{ + int32_t counts_index; + + if (value < 0) + { + return false; + } + + counts_index = counts_index_for(h, value); + + if (counts_index < 0 || h->counts_len <= counts_index) + { + return false; + } + + counts_inc_normalised(h, counts_index, count); + update_min_max(h, value); + + return true; +} + +bool hdr_record_corrected_value(struct hdr_histogram* h, int64_t value, int64_t expected_interval) +{ + return hdr_record_corrected_values(h, value, 1, expected_interval); +} + + +bool hdr_record_corrected_values(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval) +{ + int64_t missing_value; + + if (!hdr_record_values(h, value, count)) + { + return false; + } + + if (expected_interval <= 0 || value <= expected_interval) + { + return true; + } + + missing_value = value - expected_interval; + for (; missing_value >= expected_interval; missing_value -= expected_interval) + { + if (!hdr_record_values(h, missing_value, count)) + { + return false; + } + } + + return true; +} + +int64_t hdr_add(struct hdr_histogram* h, const struct hdr_histogram* from) +{ + struct hdr_iter iter; + int64_t dropped = 0; + hdr_iter_recorded_init(&iter, from); + + while (hdr_iter_next(&iter)) + { + int64_t value = iter.value; + int64_t count = iter.count; + + if (!hdr_record_values(h, value, count)) + { + dropped += count; + } + } + + return dropped; +} + +int64_t hdr_add_while_correcting_for_coordinated_omission( + struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval) +{ + struct hdr_iter iter; + int64_t dropped = 0; + hdr_iter_recorded_init(&iter, from); + + while (hdr_iter_next(&iter)) + { + int64_t value = iter.value; + int64_t count = iter.count; + + if (!hdr_record_corrected_values(h, value, count, expected_interval)) + { + dropped += count; + } + } + + return dropped; +} + + + +/* ## ## ### ## ## ## ######## ###### */ +/* ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ###### ###### */ +/* ## ## ######### ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## */ +/* ### ## ## ######## ####### ######## ###### */ + + +int64_t hdr_max(const struct hdr_histogram* h) +{ + if (0 == h->max_value) + { + return 0; + } + + return highest_equivalent_value(h, h->max_value); +} + +int64_t hdr_min(const struct hdr_histogram* h) +{ + if (0 < hdr_count_at_index(h, 0)) + { + return 0; + } + + return non_zero_min(h); +} + +int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile) +{ + struct hdr_iter iter; + int64_t total = 0; + double requested_percentile = percentile < 100.0 ? percentile : 100.0; + int64_t count_at_percentile = + (int64_t) (((requested_percentile / 100) * h->total_count) + 0.5); + count_at_percentile = count_at_percentile > 1 ? count_at_percentile : 1; + + hdr_iter_init(&iter, h); + + while (hdr_iter_next(&iter)) + { + total += iter.count; + + if (total >= count_at_percentile) + { + int64_t value_from_index = iter.value; + return highest_equivalent_value(h, value_from_index); + } + } + + return 0; +} + +double hdr_mean(const struct hdr_histogram* h) +{ + struct hdr_iter iter; + int64_t total = 0; + + hdr_iter_init(&iter, h); + + while (hdr_iter_next(&iter)) + { + if (0 != iter.count) + { + total += iter.count * hdr_median_equivalent_value(h, iter.value); + } + } + + return (total * 1.0) / h->total_count; +} + +double hdr_stddev(const struct hdr_histogram* h) +{ + double mean = hdr_mean(h); + double geometric_dev_total = 0.0; + + struct hdr_iter iter; + hdr_iter_init(&iter, h); + + while (hdr_iter_next(&iter)) + { + if (0 != iter.count) + { + double dev = (hdr_median_equivalent_value(h, iter.value) * 1.0) - mean; + geometric_dev_total += (dev * dev) * iter.count; + } + } + + return sqrt(geometric_dev_total / h->total_count); +} + +bool hdr_values_are_equivalent(const struct hdr_histogram* h, int64_t a, int64_t b) +{ + return lowest_equivalent_value(h, a) == lowest_equivalent_value(h, b); +} + +int64_t hdr_lowest_equivalent_value(const struct hdr_histogram* h, int64_t value) +{ + return lowest_equivalent_value(h, value); +} + +int64_t hdr_count_at_value(const struct hdr_histogram* h, int64_t value) +{ + return counts_get_normalised(h, counts_index_for(h, value)); +} + +int64_t hdr_count_at_index(const struct hdr_histogram* h, int32_t index) +{ + return counts_get_normalised(h, index); +} + + +/* #### ######## ######## ######## ### ######## ####### ######## ###### */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ###### ######## ## ## ## ## ## ######## ###### */ +/* ## ## ## ## ## ######### ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* #### ## ######## ## ## ## ## ## ####### ## ## ###### */ + + +static bool has_buckets(struct hdr_iter* iter) +{ + return iter->counts_index < iter->h->counts_len; +} + +static bool has_next(struct hdr_iter* iter) +{ + return iter->cumulative_count < iter->total_count; +} + +static bool move_next(struct hdr_iter* iter) +{ + iter->counts_index++; + + if (!has_buckets(iter)) + { + return false; + } + + iter->count = counts_get_normalised(iter->h, iter->counts_index); + iter->cumulative_count += iter->count; + + iter->value = hdr_value_at_index(iter->h, iter->counts_index); + iter->highest_equivalent_value = highest_equivalent_value(iter->h, iter->value); + iter->lowest_equivalent_value = lowest_equivalent_value(iter->h, iter->value); + iter->median_equivalent_value = hdr_median_equivalent_value(iter->h, iter->value); + + return true; +} + +static int64_t peek_next_value_from_index(struct hdr_iter* iter) +{ + return hdr_value_at_index(iter->h, iter->counts_index + 1); +} + +static bool next_value_greater_than_reporting_level_upper_bound( + struct hdr_iter *iter, int64_t reporting_level_upper_bound) +{ + if (iter->counts_index >= iter->h->counts_len) + { + return false; + } + + return peek_next_value_from_index(iter) > reporting_level_upper_bound; +} + +static bool _basic_iter_next(struct hdr_iter *iter) +{ + if (!has_next(iter) || iter->counts_index >= iter->h->counts_len) + { + return false; + } + + move_next(iter); + + return true; +} + +static void _update_iterated_values(struct hdr_iter* iter, int64_t new_value_iterated_to) +{ + iter->value_iterated_from = iter->value_iterated_to; + iter->value_iterated_to = new_value_iterated_to; +} + +static bool _all_values_iter_next(struct hdr_iter* iter) +{ + bool result = move_next(iter); + + if (result) + { + _update_iterated_values(iter, iter->value); + } + + return result; +} + +void hdr_iter_init(struct hdr_iter* iter, const struct hdr_histogram* h) +{ + iter->h = h; + + iter->counts_index = -1; + iter->total_count = h->total_count; + iter->count = 0; + iter->cumulative_count = 0; + iter->value = 0; + iter->highest_equivalent_value = 0; + iter->value_iterated_from = 0; + iter->value_iterated_to = 0; + + iter->_next_fp = _all_values_iter_next; +} + +bool hdr_iter_next(struct hdr_iter* iter) +{ + return iter->_next_fp(iter); +} + +/* ######## ######## ######## ###### ######## ## ## ######## #### ## ######## ###### */ +/* ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## #### ## ## ## ## ## ## */ +/* ######## ###### ######## ## ###### ## ## ## ## ## ## ###### ###### */ +/* ## ## ## ## ## ## ## #### ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## */ +/* ## ######## ## ## ###### ######## ## ## ## #### ######## ######## ###### */ + +static bool _percentile_iter_next(struct hdr_iter* iter) +{ + int64_t temp, half_distance, percentile_reporting_ticks; + + struct hdr_iter_percentiles* percentiles = &iter->specifics.percentiles; + + if (!has_next(iter)) + { + if (percentiles->seen_last_value) + { + return false; + } + + percentiles->seen_last_value = true; + percentiles->percentile = 100.0; + + return true; + } + + if (iter->counts_index == -1 && !_basic_iter_next(iter)) + { + return false; + } + + do + { + double current_percentile = (100.0 * (double) iter->cumulative_count) / iter->h->total_count; + if (iter->count != 0 && + percentiles->percentile_to_iterate_to <= current_percentile) + { + _update_iterated_values(iter, highest_equivalent_value(iter->h, iter->value)); + + percentiles->percentile = percentiles->percentile_to_iterate_to; + temp = (int64_t)(log(100 / (100.0 - (percentiles->percentile_to_iterate_to))) / log(2)) + 1; + half_distance = (int64_t) pow(2, (double) temp); + percentile_reporting_ticks = percentiles->ticks_per_half_distance * half_distance; + percentiles->percentile_to_iterate_to += 100.0 / percentile_reporting_ticks; + + return true; + } + } + while (_basic_iter_next(iter)); + + return true; +} + +void hdr_iter_percentile_init(struct hdr_iter* iter, const struct hdr_histogram* h, int32_t ticks_per_half_distance) +{ + iter->h = h; + + hdr_iter_init(iter, h); + + iter->specifics.percentiles.seen_last_value = false; + iter->specifics.percentiles.ticks_per_half_distance = ticks_per_half_distance; + iter->specifics.percentiles.percentile_to_iterate_to = 0.0; + iter->specifics.percentiles.percentile = 0.0; + + iter->_next_fp = _percentile_iter_next; +} + +static void format_line_string(char* str, size_t len, int significant_figures, format_type format) +{ +#if defined(_MSC_VER) +#define snprintf _snprintf +#pragma warning(push) +#pragma warning(disable: 4996) +#endif + const char* format_str = "%s%d%s"; + + switch (format) + { + case CSV: + snprintf(str, len, format_str, "%.", significant_figures, "f,%f,%d,%.2f\n"); + break; + case CLASSIC: + snprintf(str, len, format_str, "%12.", significant_figures, "f %12f %12d %12.2f\n"); + break; + default: + snprintf(str, len, format_str, "%12.", significant_figures, "f %12f %12d %12.2f\n"); + } +#if defined(_MSC_VER) +#undef snprintf +#pragma warning(pop) +#endif +} + + +/* ######## ######## ###### ####### ######## ######## ######## ######## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ######## ###### ## ## ## ######## ## ## ###### ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ######## ###### ####### ## ## ######## ######## ######## */ + + +static bool _recorded_iter_next(struct hdr_iter* iter) +{ + while (_basic_iter_next(iter)) + { + if (iter->count != 0) + { + _update_iterated_values(iter, iter->value); + + iter->specifics.recorded.count_added_in_this_iteration_step = iter->count; + return true; + } + } + + return false; +} + +void hdr_iter_recorded_init(struct hdr_iter* iter, const struct hdr_histogram* h) +{ + hdr_iter_init(iter, h); + + iter->specifics.recorded.count_added_in_this_iteration_step = 0; + + iter->_next_fp = _recorded_iter_next; +} + +/* ## #### ## ## ######## ### ######## */ +/* ## ## ### ## ## ## ## ## ## */ +/* ## ## #### ## ## ## ## ## ## */ +/* ## ## ## ## ## ###### ## ## ######## */ +/* ## ## ## #### ## ######### ## ## */ +/* ## ## ## ### ## ## ## ## ## */ +/* ######## #### ## ## ######## ## ## ## ## */ + + +static bool _iter_linear_next(struct hdr_iter* iter) +{ + struct hdr_iter_linear* linear = &iter->specifics.linear; + + linear->count_added_in_this_iteration_step = 0; + + if (has_next(iter) || + next_value_greater_than_reporting_level_upper_bound( + iter, linear->next_value_reporting_level_lowest_equivalent)) + { + do + { + if (iter->value >= linear->next_value_reporting_level_lowest_equivalent) + { + _update_iterated_values(iter, linear->next_value_reporting_level); + + linear->next_value_reporting_level += linear->value_units_per_bucket; + linear->next_value_reporting_level_lowest_equivalent = + lowest_equivalent_value(iter->h, linear->next_value_reporting_level); + + return true; + } + + if (!move_next(iter)) + { + return true; + } + + linear->count_added_in_this_iteration_step += iter->count; + } + while (true); + } + + return false; +} + + +void hdr_iter_linear_init(struct hdr_iter* iter, const struct hdr_histogram* h, int64_t value_units_per_bucket) +{ + hdr_iter_init(iter, h); + + iter->specifics.linear.count_added_in_this_iteration_step = 0; + iter->specifics.linear.value_units_per_bucket = value_units_per_bucket; + iter->specifics.linear.next_value_reporting_level = value_units_per_bucket; + iter->specifics.linear.next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(h, value_units_per_bucket); + + iter->_next_fp = _iter_linear_next; +} + +/* ## ####### ###### ### ######## #### ######## ## ## ## ## #### ###### */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## ### ### ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## #### #### ## ## */ +/* ## ## ## ## #### ## ## ######## ## ## ######### ## ### ## ## ## */ +/* ## ## ## ## ## ######### ## ## ## ## ## ## ## ## ## ## */ +/* ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ +/* ######## ####### ###### ## ## ## ## #### ## ## ## ## ## #### ###### */ + +static bool _log_iter_next(struct hdr_iter *iter) +{ + struct hdr_iter_log* logarithmic = &iter->specifics.log; + + logarithmic->count_added_in_this_iteration_step = 0; + + if (has_next(iter) || + next_value_greater_than_reporting_level_upper_bound( + iter, logarithmic->next_value_reporting_level_lowest_equivalent)) + { + do + { + if (iter->value >= logarithmic->next_value_reporting_level_lowest_equivalent) + { + _update_iterated_values(iter, logarithmic->next_value_reporting_level); + + logarithmic->next_value_reporting_level *= (int64_t)logarithmic->log_base; + logarithmic->next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(iter->h, logarithmic->next_value_reporting_level); + + return true; + } + + if (!move_next(iter)) + { + return true; + } + + logarithmic->count_added_in_this_iteration_step += iter->count; + } + while (true); + } + + return false; +} + +void hdr_iter_log_init( + struct hdr_iter* iter, + const struct hdr_histogram* h, + int64_t value_units_first_bucket, + double log_base) +{ + hdr_iter_init(iter, h); + iter->specifics.log.count_added_in_this_iteration_step = 0; + iter->specifics.log.log_base = log_base; + iter->specifics.log.next_value_reporting_level = value_units_first_bucket; + iter->specifics.log.next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(h, value_units_first_bucket); + + iter->_next_fp = _log_iter_next; +} + +/* Printing. */ + +static const char* format_head_string(format_type format) +{ + switch (format) + { + case CSV: + return "%s,%s,%s,%s\n"; + case CLASSIC: + return "%12s %12s %12s %12s\n\n"; + default: + return "%12s %12s %12s %12s\n\n"; + } +} + +static const char CLASSIC_FOOTER[] = + "#[Mean = %12.3f, StdDeviation = %12.3f]\n" + "#[Max = %12.3f, Total count = %12" PRIu64 "]\n" + "#[Buckets = %12d, SubBuckets = %12d]\n"; + +int hdr_percentiles_print( + struct hdr_histogram* h, FILE* stream, int32_t ticks_per_half_distance, + double value_scale, format_type format) +{ + char line_format[25]; + const char* head_format; + int rc = 0; + struct hdr_iter iter; + struct hdr_iter_percentiles * percentiles; + + format_line_string(line_format, 25, h->significant_figures, format); + head_format = format_head_string(format); + + hdr_iter_percentile_init(&iter, h, ticks_per_half_distance); + + if (fprintf( + stream, head_format, + "Value", "Percentile", "TotalCount", "1/(1-Percentile)") < 0) + { + rc = EIO; + goto cleanup; + } + + percentiles = &iter.specifics.percentiles; + while (hdr_iter_next(&iter)) + { + double value = iter.highest_equivalent_value / value_scale; + double percentile = percentiles->percentile / 100.0; + int64_t total_count = iter.cumulative_count; + double inverted_percentile = (1.0 / (1.0 - percentile)); + + if (fprintf( + stream, line_format, value, percentile, total_count, inverted_percentile) < 0) + { + rc = EIO; + goto cleanup; + } + } + + if (CLASSIC == format) + { + double mean = hdr_mean(h) / value_scale; + double stddev = hdr_stddev(h) / value_scale; + double max = hdr_max(h) / value_scale; + + if (fprintf( + stream, CLASSIC_FOOTER, mean, stddev, max, + h->total_count, h->bucket_count, h->sub_bucket_count) < 0) + { + rc = EIO; + goto cleanup; + } + } + + cleanup: + return rc; +} diff --git a/deps/histogram/src/hdr_histogram.h b/deps/histogram/src/hdr_histogram.h new file mode 100644 index 00000000000000..4a0f4606b57172 --- /dev/null +++ b/deps/histogram/src/hdr_histogram.h @@ -0,0 +1,434 @@ +/** + * hdr_histogram.h + * Written by Michael Barker and released to the public domain, + * as explained at http://creativecommons.org/publicdomain/zero/1.0/ + * + * The source for the hdr_histogram utilises a few C99 constructs, specifically + * the use of stdint/stdbool and inline variable declaration. + */ + +#ifndef HDR_HISTOGRAM_H +#define HDR_HISTOGRAM_H 1 + +#include +#include +#include + +struct hdr_histogram +{ + int64_t lowest_trackable_value; + int64_t highest_trackable_value; + int32_t unit_magnitude; + int32_t significant_figures; + int32_t sub_bucket_half_count_magnitude; + int32_t sub_bucket_half_count; + int64_t sub_bucket_mask; + int32_t sub_bucket_count; + int32_t bucket_count; + int64_t min_value; + int64_t max_value; + int32_t normalizing_index_offset; + double conversion_ratio; + int32_t counts_len; + int64_t total_count; + int64_t* counts; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocate the memory and initialise the hdr_histogram. + * + * Due to the size of the histogram being the result of some reasonably + * involved math on the input parameters this function it is tricky to stack allocate. + * The histogram should be released with hdr_close + * + * @param lowest_trackable_value The smallest possible value to be put into the + * histogram. + * @param highest_trackable_value The largest possible value to be put into the + * histogram. + * @param significant_figures The level of precision for this histogram, i.e. the number + * of figures in a decimal number that will be maintained. E.g. a value of 3 will mean + * the results from the histogram will be accurate up to the first three digits. Must + * be a value between 1 and 5 (inclusive). + * @param result Output parameter to capture allocated histogram. + * @return 0 on success, EINVAL if lowest_trackable_value is < 1 or the + * significant_figure value is outside of the allowed range, ENOMEM if malloc + * failed. + */ +int hdr_init( + int64_t lowest_trackable_value, + int64_t highest_trackable_value, + int significant_figures, + struct hdr_histogram** result); + +/** + * Free the memory and close the hdr_histogram. + * + * @param h The histogram you want to close. + */ +void hdr_close(struct hdr_histogram* h); + +/** + * Allocate the memory and initialise the hdr_histogram. This is the equivalent of calling + * hdr_init(1, highest_trackable_value, significant_figures, result); + * + * @deprecated use hdr_init. + */ +int hdr_alloc(int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result); + + +/** + * Reset a histogram to zero - empty out a histogram and re-initialise it + * + * If you want to re-use an existing histogram, but reset everything back to zero, this + * is the routine to use. + * + * @param h The histogram you want to reset to empty. + * + */ +void hdr_reset(struct hdr_histogram* h); + +/** + * Get the memory size of the hdr_histogram. + * + * @param h "This" pointer + * @return The amount of memory used by the hdr_histogram in bytes + */ +size_t hdr_get_memory_size(struct hdr_histogram* h); + +/** + * Records a value in the histogram, will round this value of to a precision at or better + * than the significant_figure specified at construction time. + * + * @param h "This" pointer + * @param value Value to add to the histogram + * @return false if the value is larger than the highest_trackable_value and can't be recorded, + * true otherwise. + */ +bool hdr_record_value(struct hdr_histogram* h, int64_t value); + +/** + * Records count values in the histogram, will round this value of to a + * precision at or better than the significant_figure specified at construction + * time. + * + * @param h "This" pointer + * @param value Value to add to the histogram + * @param count Number of 'value's to add to the histogram + * @return false if any value is larger than the highest_trackable_value and can't be recorded, + * true otherwise. + */ +bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count); + + +/** + * Record a value in the histogram and backfill based on an expected interval. + * + * Records a value in the histogram, will round this value of to a precision at or better + * than the significant_figure specified at contruction time. This is specifically used + * for recording latency. If the value is larger than the expected_interval then the + * latency recording system has experienced co-ordinated omission. This method fills in the + * values that would have occured had the client providing the load not been blocked. + + * @param h "This" pointer + * @param value Value to add to the histogram + * @param expected_interval The delay between recording values. + * @return false if the value is larger than the highest_trackable_value and can't be recorded, + * true otherwise. + */ +bool hdr_record_corrected_value(struct hdr_histogram* h, int64_t value, int64_t expexcted_interval); +/** + * Record a value in the histogram 'count' times. Applies the same correcting logic + * as 'hdr_record_corrected_value'. + * + * @param h "This" pointer + * @param value Value to add to the histogram + * @param count Number of 'value's to add to the histogram + * @param expected_interval The delay between recording values. + * @return false if the value is larger than the highest_trackable_value and can't be recorded, + * true otherwise. + */ +bool hdr_record_corrected_values(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval); + +/** + * Adds all of the values from 'from' to 'this' histogram. Will return the + * number of values that are dropped when copying. Values will be dropped + * if they around outside of h.lowest_trackable_value and + * h.highest_trackable_value. + * + * @param h "This" pointer + * @param from Histogram to copy values from. + * @return The number of values dropped when copying. + */ +int64_t hdr_add(struct hdr_histogram* h, const struct hdr_histogram* from); + +/** + * Adds all of the values from 'from' to 'this' histogram. Will return the + * number of values that are dropped when copying. Values will be dropped + * if they around outside of h.lowest_trackable_value and + * h.highest_trackable_value. + * + * @param h "This" pointer + * @param from Histogram to copy values from. + * @return The number of values dropped when copying. + */ +int64_t hdr_add_while_correcting_for_coordinated_omission( + struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval); + +/** + * Get minimum value from the histogram. Will return 2^63-1 if the histogram + * is empty. + * + * @param h "This" pointer + */ +int64_t hdr_min(const struct hdr_histogram* h); + +/** + * Get maximum value from the histogram. Will return 0 if the histogram + * is empty. + * + * @param h "This" pointer + */ +int64_t hdr_max(const struct hdr_histogram* h); + +/** + * Get the value at a specific percentile. + * + * @param h "This" pointer. + * @param percentile The percentile to get the value for + */ +int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile); + +/** + * Gets the standard deviation for the values in the histogram. + * + * @param h "This" pointer + * @return The standard deviation + */ +double hdr_stddev(const struct hdr_histogram* h); + +/** + * Gets the mean for the values in the histogram. + * + * @param h "This" pointer + * @return The mean + */ +double hdr_mean(const struct hdr_histogram* h); + +/** + * Determine if two values are equivalent with the histogram's resolution. + * Where "equivalent" means that value samples recorded for any two + * equivalent values are counted in a common total count. + * + * @param h "This" pointer + * @param a first value to compare + * @param b second value to compare + * @return 'true' if values are equivalent with the histogram's resolution. + */ +bool hdr_values_are_equivalent(const struct hdr_histogram* h, int64_t a, int64_t b); + +/** + * Get the lowest value that is equivalent to the given value within the histogram's resolution. + * Where "equivalent" means that value samples recorded for any two + * equivalent values are counted in a common total count. + * + * @param h "This" pointer + * @param value The given value + * @return The lowest value that is equivalent to the given value within the histogram's resolution. + */ +int64_t hdr_lowest_equivalent_value(const struct hdr_histogram* h, int64_t value); + +/** + * Get the count of recorded values at a specific value + * (to within the histogram resolution at the value level). + * + * @param h "This" pointer + * @param value The value for which to provide the recorded count + * @return The total count of values recorded in the histogram within the value range that is + * {@literal >=} lowestEquivalentValue(value) and {@literal <=} highestEquivalentValue(value) + */ +int64_t hdr_count_at_value(const struct hdr_histogram* h, int64_t value); + +int64_t hdr_count_at_index(const struct hdr_histogram* h, int32_t index); + +int64_t hdr_value_at_index(const struct hdr_histogram* h, int32_t index); + +struct hdr_iter_percentiles +{ + bool seen_last_value; + int32_t ticks_per_half_distance; + double percentile_to_iterate_to; + double percentile; +}; + +struct hdr_iter_recorded +{ + int64_t count_added_in_this_iteration_step; +}; + +struct hdr_iter_linear +{ + int64_t value_units_per_bucket; + int64_t count_added_in_this_iteration_step; + int64_t next_value_reporting_level; + int64_t next_value_reporting_level_lowest_equivalent; +}; + +struct hdr_iter_log +{ + double log_base; + int64_t count_added_in_this_iteration_step; + int64_t next_value_reporting_level; + int64_t next_value_reporting_level_lowest_equivalent; +}; + +/** + * The basic iterator. This is a generic structure + * that supports all of the types of iteration. Use + * the appropriate initialiser to get the desired + * iteration. + * + * @ + */ +struct hdr_iter +{ + const struct hdr_histogram* h; + /** raw index into the counts array */ + int32_t counts_index; + /** snapshot of the length at the time the iterator is created */ + int32_t total_count; + /** value directly from array for the current counts_index */ + int64_t count; + /** sum of all of the counts up to and including the count at this index */ + int64_t cumulative_count; + /** The current value based on counts_index */ + int64_t value; + int64_t highest_equivalent_value; + int64_t lowest_equivalent_value; + int64_t median_equivalent_value; + int64_t value_iterated_from; + int64_t value_iterated_to; + + union + { + struct hdr_iter_percentiles percentiles; + struct hdr_iter_recorded recorded; + struct hdr_iter_linear linear; + struct hdr_iter_log log; + } specifics; + + bool (* _next_fp)(struct hdr_iter* iter); + +}; + +/** + * Initalises the basic iterator. + * + * @param itr 'This' pointer + * @param h The histogram to iterate over + */ +void hdr_iter_init(struct hdr_iter* iter, const struct hdr_histogram* h); + +/** + * Initialise the iterator for use with percentiles. + */ +void hdr_iter_percentile_init(struct hdr_iter* iter, const struct hdr_histogram* h, int32_t ticks_per_half_distance); + +/** + * Initialise the iterator for use with recorded values. + */ +void hdr_iter_recorded_init(struct hdr_iter* iter, const struct hdr_histogram* h); + +/** + * Initialise the iterator for use with linear values. + */ +void hdr_iter_linear_init( + struct hdr_iter* iter, + const struct hdr_histogram* h, + int64_t value_units_per_bucket); + +/** + * Initialise the iterator for use with logarithmic values + */ +void hdr_iter_log_init( + struct hdr_iter* iter, + const struct hdr_histogram* h, + int64_t value_units_first_bucket, + double log_base); + +/** + * Iterate to the next value for the iterator. If there are no more values + * available return faluse. + * + * @param itr 'This' pointer + * @return 'false' if there are no values remaining for this iterator. + */ +bool hdr_iter_next(struct hdr_iter* iter); + +typedef enum +{ + CLASSIC, + CSV +} format_type; + +/** + * Print out a percentile based histogram to the supplied stream. Note that + * this call will not flush the FILE, this is left up to the user. + * + * @param h 'This' pointer + * @param stream The FILE to write the output to + * @param ticks_per_half_distance The number of iteration steps per half-distance to 100% + * @param value_scale Scale the output values by this amount + * @param format_type Format to use, e.g. CSV. + * @return 0 on success, error code on failure. EIO if an error occurs writing + * the output. + */ +int hdr_percentiles_print( + struct hdr_histogram* h, FILE* stream, int32_t ticks_per_half_distance, + double value_scale, format_type format); + +/** +* Internal allocation methods, used by hdr_dbl_histogram. +*/ +struct hdr_histogram_bucket_config +{ + int64_t lowest_trackable_value; + int64_t highest_trackable_value; + int64_t unit_magnitude; + int64_t significant_figures; + int32_t sub_bucket_half_count_magnitude; + int32_t sub_bucket_half_count; + int64_t sub_bucket_mask; + int32_t sub_bucket_count; + int32_t bucket_count; + int32_t counts_len; +}; + +int hdr_calculate_bucket_config( + int64_t lowest_trackable_value, + int64_t highest_trackable_value, + int significant_figures, + struct hdr_histogram_bucket_config* cfg); + +void hdr_init_preallocated(struct hdr_histogram* h, struct hdr_histogram_bucket_config* cfg); + +int64_t hdr_size_of_equivalent_value_range(const struct hdr_histogram* h, int64_t value); + +int64_t hdr_next_non_equivalent_value(const struct hdr_histogram* h, int64_t value); + +int64_t hdr_median_equivalent_value(const struct hdr_histogram* h, int64_t value); + +/** + * Used to reset counters after importing data manuallying into the histogram, used by the logging code + * and other custom serialisation tools. + */ +void hdr_reset_internal_counters(struct hdr_histogram* h); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/deps/histogram/src/hdr_tests.h b/deps/histogram/src/hdr_tests.h new file mode 100644 index 00000000000000..c016d3a6de09aa --- /dev/null +++ b/deps/histogram/src/hdr_tests.h @@ -0,0 +1,22 @@ +#ifndef HDR_TESTS_H +#define HDR_TESTS_H + +/* These are functions used in tests and are not intended for normal usage. */ + +#include "hdr_histogram.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t counts_index_for(const struct hdr_histogram* h, int64_t value); +int hdr_encode_compressed(struct hdr_histogram* h, uint8_t** compressed_histogram, size_t* compressed_len); +int hdr_decode_compressed(uint8_t* buffer, size_t length, struct hdr_histogram** histogram); +void hdr_base64_decode_block(const char* input, uint8_t* output); +void hdr_base64_encode_block(const uint8_t* input, char* output); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index a891e875b5ab80..d805a0cf9de4d9 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -398,6 +398,119 @@ Returns a list of `PerformanceEntry` objects in chronological order with respect to `performanceEntry.startTime` whose `performanceEntry.entryType` is equal to `type`. +## monitorEventLoopDelay([options]) + + +* `options` {Object} + * `resolution` {number} The sampling rate in milliseconds. Must be greater + than zero. Defaults to `10`. +* Returns: {Histogram} + +Creates a `Histogram` object that samples and reports the event loop delay +over time. + +Using a timer to detect approximate event loop delay works because the +execution of timers is tied specifically to the lifecycle of the libuv +event loop. That is, a delay in the loop will cause a delay in the execution +of the timer, and those delays are specifically what this API is intended to +detect. + +```js +const { monitorEventLoopDelay } = require('perf_hooks'); +const h = monitorEventLoopDelay({ resolution: 20 }); +h.enable(); +// Do something +h.disable(); +console.log(h.min); +console.log(h.max); +console.log(h.mean); +console.log(h.stddev); +console.log(h.percentiles); +console.log(h.percentile(50)); +console.log(h.percentile(99)); +``` + +### Class: Histogram + +Tracks the event loop delay at a given sampling rate. + +#### histogram.disable() + + +* Returns: {boolean} + +Disables the event loop delay sample timer. Returns `true` if the timer was +stopped, `false` if it was already stopped. + +#### histogram.enable() + + +* Returns: {boolean} + +Enables the event loop delay sample timer. Returns `true` if the timer was +started, `false` if it was already started. + +#### histogram.exceeds + +* Value: {number} + +The number of times the event loop delay exceeded the maximum 1 hour event +loop delay threshold. + +#### histogram.max + +* Value: {number} + +The maximum recorded event loop delay. + +#### histogram.mean + +* Value: {number} + +The mean of the recorded event loop delays. + +#### histogram.min + + +* Value: {number} + +The minimum recorded event loop delay. + +#### histogram.percentile(percentile) + +* `percentile` {number} A percentile value between 1 and 100. + +Returns the value at the given percentile. + +#### histogram.percentiles + +* Value: {Map} + +Returns a `Map` object detailing the accumulated percentile distribution. + +#### histogram.reset() + + +Resets the collected histogram data. + +#### histogram.stddev + +* Value: {number} + +The standard deviation of the recorded event loop delays. + ## Examples ### Measuring the duration of async operations diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js index 041bbf68f2f6c4..02c617dfdf9e0f 100644 --- a/lib/perf_hooks.js +++ b/lib/perf_hooks.js @@ -1,6 +1,7 @@ 'use strict'; const { + ELDHistogram: _ELDHistogram, PerformanceEntry, mark: _mark, clearMark: _clearMark, @@ -36,6 +37,8 @@ const L = require('internal/linkedlist'); const kInspect = require('internal/util').customInspectSymbol; const { inherits } = require('util'); +const kHandle = Symbol('handle'); +const kMap = Symbol('map'); const kCallback = Symbol('callback'); const kTypes = Symbol('types'); const kEntries = Symbol('entries'); @@ -547,9 +550,73 @@ function sortedInsert(list, entry) { list.splice(location, 0, entry); } +class ELDHistogram { + constructor(handle) { + this[kHandle] = handle; + this[kMap] = new Map(); + } + + reset() { this[kHandle].reset(); } + enable() { return this[kHandle].enable(); } + disable() { return this[kHandle].disable(); } + + get exceeds() { return this[kHandle].exceeds(); } + get min() { return this[kHandle].min(); } + get max() { return this[kHandle].max(); } + get mean() { return this[kHandle].mean(); } + get stddev() { return this[kHandle].stddev(); } + percentile(percentile) { + if (typeof percentile !== 'number') { + const errors = lazyErrors(); + throw new errors.ERR_INVALID_ARG_TYPE('percentile', 'number', percentile); + } + if (percentile <= 0 || percentile > 100) { + const errors = lazyErrors(); + throw new errors.ERR_INVALID_ARG_VALUE.RangeError('percentile', + percentile); + } + return this[kHandle].percentile(percentile); + } + get percentiles() { + this[kMap].clear(); + this[kHandle].percentiles(this[kMap]); + return this[kMap]; + } + + [kInspect]() { + return { + min: this.min, + max: this.max, + mean: this.mean, + stddev: this.stddev, + percentiles: this.percentiles, + exceeds: this.exceeds + }; + } +} + +function monitorEventLoopDelay(options = {}) { + if (typeof options !== 'object' || options === null) { + const errors = lazyErrors(); + throw new errors.ERR_INVALID_ARG_TYPE('options', 'Object', options); + } + const { resolution = 10 } = options; + if (typeof resolution !== 'number') { + const errors = lazyErrors(); + throw new errors.ERR_INVALID_ARG_TYPE('options.resolution', + 'number', resolution); + } + if (resolution <= 0 || !Number.isSafeInteger(resolution)) { + const errors = lazyErrors(); + throw new errors.ERR_INVALID_OPT_VALUE.RangeError('resolution', resolution); + } + return new ELDHistogram(new _ELDHistogram(resolution)); +} + module.exports = { performance, - PerformanceObserver + PerformanceObserver, + monitorEventLoopDelay }; Object.defineProperty(module.exports, 'constants', { diff --git a/node.gyp b/node.gyp index 9a417d60fd7fbf..8f974b020b8e75 100644 --- a/node.gyp +++ b/node.gyp @@ -252,8 +252,9 @@ ], 'include_dirs': [ 'src', - 'deps/v8/include', + 'deps/v8/include' ], + 'dependencies': [ 'deps/histogram/histogram.gyp:histogram' ], # - "C4244: conversion from 'type1' to 'type2', possible loss of data" # Ususaly safe. Disable for `dep`, enable for `src` @@ -351,6 +352,7 @@ 'src', '<(SHARED_INTERMEDIATE_DIR)' # for node_natives.h ], + 'dependencies': [ 'deps/histogram/histogram.gyp:histogram' ], 'sources': [ 'src/async_wrap.cc', @@ -445,6 +447,8 @@ 'src/env.h', 'src/env-inl.h', 'src/handle_wrap.h', + 'src/histogram.h', + 'src/histogram-inl.h', 'src/http_parser_adaptor.h', 'src/js_stream.h', 'src/memory_tracker.h', @@ -952,6 +956,7 @@ '<(node_lib_target_name)', 'rename_node_bin_win', 'deps/gtest/gtest.gyp:gtest', + 'deps/histogram/histogram.gyp:histogram', 'node_dtrace_header', 'node_dtrace_ustack', 'node_dtrace_provider', diff --git a/node.gypi b/node.gypi index 689138c15b5705..c07b5ea70431ff 100644 --- a/node.gypi +++ b/node.gypi @@ -237,6 +237,7 @@ [ 'OS=="aix"', { 'defines': [ '_LINUX_SOURCE_COMPAT', + '__STDC_FORMAT_MACROS' ], 'conditions': [ [ 'force_load=="true"', { diff --git a/src/histogram-inl.h b/src/histogram-inl.h new file mode 100644 index 00000000000000..3135041f7387a9 --- /dev/null +++ b/src/histogram-inl.h @@ -0,0 +1,63 @@ +#ifndef SRC_HISTOGRAM_INL_H_ +#define SRC_HISTOGRAM_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "histogram.h" +#include "node_internals.h" + +namespace node { + +inline Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { + CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram_)); +} + +inline Histogram::~Histogram() { + hdr_close(histogram_); +} + +inline void Histogram::Reset() { + hdr_reset(histogram_); +} + +inline bool Histogram::Record(int64_t value) { + return hdr_record_value(histogram_, value); +} + +inline int64_t Histogram::Min() { + return hdr_min(histogram_); +} + +inline int64_t Histogram::Max() { + return hdr_max(histogram_); +} + +inline double Histogram::Mean() { + return hdr_mean(histogram_); +} + +inline double Histogram::Stddev() { + return hdr_stddev(histogram_); +} + +inline double Histogram::Percentile(double percentile) { + CHECK_GT(percentile, 0); + CHECK_LE(percentile, 100); + return hdr_value_at_percentile(histogram_, percentile); +} + +inline void Histogram::Percentiles(std::function fn) { + hdr_iter iter; + hdr_iter_percentile_init(&iter, histogram_, 1); + while (hdr_iter_next(&iter)) { + double key = iter.specifics.percentiles.percentile; + double value = iter.value; + fn(key, value); + } +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_HISTOGRAM_INL_H_ diff --git a/src/histogram.h b/src/histogram.h new file mode 100644 index 00000000000000..eb94af5da2a997 --- /dev/null +++ b/src/histogram.h @@ -0,0 +1,38 @@ +#ifndef SRC_HISTOGRAM_H_ +#define SRC_HISTOGRAM_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "hdr_histogram.h" +#include +#include + +namespace node { + +class Histogram { + public: + inline Histogram(int64_t lowest, int64_t highest, int figures = 3); + inline virtual ~Histogram(); + + inline bool Record(int64_t value); + inline void Reset(); + inline int64_t Min(); + inline int64_t Max(); + inline double Mean(); + inline double Stddev(); + inline double Percentile(double percentile); + inline void Percentiles(std::function fn); + + size_t GetMemorySize() const { + return hdr_get_memory_size(histogram_); + } + + private: + hdr_histogram* histogram_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_HISTOGRAM_H_ diff --git a/src/node_perf.cc b/src/node_perf.cc index 33dd1d2051872c..b9c0183a83d930 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -1,5 +1,10 @@ +#include "aliased_buffer.h" #include "node_internals.h" #include "node_perf.h" +#include "node_buffer.h" +#include "node_process.h" + +#include #ifdef __POSIX__ #include // gettimeofday @@ -20,6 +25,7 @@ using v8::HandleScope; using v8::Integer; using v8::Isolate; using v8::Local; +using v8::Map; using v8::MaybeLocal; using v8::Name; using v8::NewStringType; @@ -387,6 +393,168 @@ void Timerify(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(wrap); } +// Event Loop Timing Histogram +namespace { +static void ELDHistogramMin(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Min()); + args.GetReturnValue().Set(value); +} + +static void ELDHistogramMax(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Max()); + args.GetReturnValue().Set(value); +} + +static void ELDHistogramMean(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Mean()); +} + +static void ELDHistogramExceeds(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Exceeds()); + args.GetReturnValue().Set(value); +} + +static void ELDHistogramStddev(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Stddev()); +} + +static void ELDHistogramPercentile(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As()->Value(); + args.GetReturnValue().Set(histogram->Percentile(percentile)); +} + +static void ELDHistogramPercentiles(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local map = args[0].As(); + histogram->Percentiles([&](double key, double value) { + map->Set(env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), value)).IsEmpty(); + }); +} + +static void ELDHistogramEnable(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Enable()); +} + +static void ELDHistogramDisable(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Disable()); +} + +static void ELDHistogramReset(const FunctionCallbackInfo& args) { + ELDHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + histogram->ResetState(); +} + +static void ELDHistogramNew(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args.IsConstructCall()); + int32_t resolution = args[0]->IntegerValue(env->context()).FromJust(); + CHECK_GT(resolution, 0); + new ELDHistogram(env, args.This(), resolution); +} +} // namespace + +ELDHistogram::ELDHistogram( + Environment* env, + Local wrap, + int32_t resolution) : BaseObject(env, wrap), + Histogram(1, 3.6e12), + resolution_(resolution) { + MakeWeak(); + timer_ = new uv_timer_t(); + uv_timer_init(env->event_loop(), timer_); + timer_->data = this; +} + +void ELDHistogram::CloseTimer() { + if (timer_ == nullptr) + return; + + env()->CloseHandle(timer_, [](uv_timer_t* handle) { delete handle; }); + timer_ = nullptr; +} + +ELDHistogram::~ELDHistogram() { + Disable(); + CloseTimer(); +} + +void ELDHistogramDelayInterval(uv_timer_t* req) { + ELDHistogram* histogram = + reinterpret_cast(req->data); + histogram->RecordDelta(); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "min", histogram->Min()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "max", histogram->Max()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "mean", histogram->Mean()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "stddev", histogram->Stddev()); +} + +bool ELDHistogram::RecordDelta() { + uint64_t time = uv_hrtime(); + bool ret = true; + if (prev_ > 0) { + int64_t delta = time - prev_; + if (delta > 0) { + ret = Record(delta); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "delay", delta); + if (!ret) { + if (exceeds_ < 0xFFFFFFFF) + exceeds_++; + ProcessEmitWarning( + env(), + "Event loop delay exceeded 1 hour: %" PRId64 " nanoseconds", + delta); + } + } + } + prev_ = time; + return ret; +} + +bool ELDHistogram::Enable() { + if (enabled_) return false; + enabled_ = true; + uv_timer_start(timer_, + ELDHistogramDelayInterval, + resolution_, + resolution_); + uv_unref(reinterpret_cast(timer_)); + return true; +} + +bool ELDHistogram::Disable() { + if (!enabled_) return false; + enabled_ = false; + uv_timer_stop(timer_); + return true; +} void Initialize(Local target, Local unused, @@ -456,6 +624,24 @@ void Initialize(Local target, env->constants_string(), constants, attr).ToChecked(); + + Local eldh_classname = FIXED_ONE_BYTE_STRING(isolate, "ELDHistogram"); + Local eldh = + env->NewFunctionTemplate(ELDHistogramNew); + eldh->SetClassName(eldh_classname); + eldh->InstanceTemplate()->SetInternalFieldCount(1); + env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds); + env->SetProtoMethod(eldh, "min", ELDHistogramMin); + env->SetProtoMethod(eldh, "max", ELDHistogramMax); + env->SetProtoMethod(eldh, "mean", ELDHistogramMean); + env->SetProtoMethod(eldh, "stddev", ELDHistogramStddev); + env->SetProtoMethod(eldh, "percentile", ELDHistogramPercentile); + env->SetProtoMethod(eldh, "percentiles", ELDHistogramPercentiles); + env->SetProtoMethod(eldh, "enable", ELDHistogramEnable); + env->SetProtoMethod(eldh, "disable", ELDHistogramDisable); + env->SetProtoMethod(eldh, "reset", ELDHistogramReset); + target->Set(context, eldh_classname, + eldh->GetFunction(env->context()).ToLocalChecked()).FromJust(); } } // namespace performance diff --git a/src/node_perf.h b/src/node_perf.h index e3ef69c0fb48e2..a8e43dc3476cc2 100644 --- a/src/node_perf.h +++ b/src/node_perf.h @@ -7,6 +7,7 @@ #include "node_perf_common.h" #include "env.h" #include "base_object-inl.h" +#include "histogram-inl.h" #include "v8.h" #include "uv.h" @@ -124,6 +125,41 @@ class GCPerformanceEntry : public PerformanceEntry { PerformanceGCKind gckind_; }; +class ELDHistogram : public BaseObject, public Histogram { + public: + ELDHistogram(Environment* env, + Local wrap, + int32_t resolution); + + ~ELDHistogram() override; + + bool RecordDelta(); + bool Enable(); + bool Disable(); + void ResetState() { + Reset(); + exceeds_ = 0; + prev_ = 0; + } + int64_t Exceeds() { return exceeds_; } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackFieldWithSize("histogram", GetMemorySize()); + } + + SET_MEMORY_INFO_NAME(ELDHistogram) + SET_SELF_SIZE(ELDHistogram) + + private: + void CloseTimer(); + + bool enabled_ = false; + int32_t resolution_ = 0; + int64_t exceeds_ = 0; + uint64_t prev_ = 0; + uv_timer_t* timer_; +}; + } // namespace performance } // namespace node diff --git a/test/sequential/test-performance-eventloopdelay.js b/test/sequential/test-performance-eventloopdelay.js new file mode 100644 index 00000000000000..82f47b6fb29c47 --- /dev/null +++ b/test/sequential/test-performance-eventloopdelay.js @@ -0,0 +1,99 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + monitorEventLoopDelay +} = require('perf_hooks'); + +{ + const histogram = monitorEventLoopDelay(); + assert(histogram); + assert(histogram.enable()); + assert(!histogram.enable()); + histogram.reset(); + assert(histogram.disable()); + assert(!histogram.disable()); +} + +{ + [null, 'a', 1, false, Infinity].forEach((i) => { + common.expectsError( + () => monitorEventLoopDelay(i), + { + type: TypeError, + code: 'ERR_INVALID_ARG_TYPE' + } + ); + }); + + [null, 'a', false, {}, []].forEach((i) => { + common.expectsError( + () => monitorEventLoopDelay({ resolution: i }), + { + type: TypeError, + code: 'ERR_INVALID_ARG_TYPE' + } + ); + }); + + [-1, 0, Infinity].forEach((i) => { + common.expectsError( + () => monitorEventLoopDelay({ resolution: i }), + { + type: RangeError, + code: 'ERR_INVALID_OPT_VALUE' + } + ); + }); +} + +{ + const histogram = monitorEventLoopDelay({ resolution: 1 }); + histogram.enable(); + let m = 5; + function spinAWhile() { + common.busyLoop(1000); + if (--m > 0) { + setTimeout(spinAWhile, common.platformTimeout(500)); + } else { + histogram.disable(); + // The values are non-deterministic, so we just check that a value is + // present, as opposed to a specific value. + assert(histogram.min > 0); + assert(histogram.max > 0); + assert(histogram.stddev > 0); + assert(histogram.mean > 0); + assert(histogram.percentiles.size > 0); + for (let n = 1; n < 100; n = n + 0.1) { + assert(histogram.percentile(n) >= 0); + } + histogram.reset(); + assert.strictEqual(histogram.min, 9223372036854776000); + assert.strictEqual(histogram.max, 0); + assert(Number.isNaN(histogram.stddev)); + assert(Number.isNaN(histogram.mean)); + assert.strictEqual(histogram.percentiles.size, 1); + + ['a', false, {}, []].forEach((i) => { + common.expectsError( + () => histogram.percentile(i), + { + type: TypeError, + code: 'ERR_INVALID_ARG_TYPE' + } + ); + }); + [-1, 0, 101].forEach((i) => { + common.expectsError( + () => histogram.percentile(i), + { + type: RangeError, + code: 'ERR_INVALID_ARG_VALUE' + } + ); + }); + } + } + spinAWhile(); +} diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js index 96dd98737f9564..ffbec9115c272e 100644 --- a/tools/doc/type-parser.js +++ b/tools/doc/type-parser.js @@ -16,7 +16,7 @@ const jsPrimitives = { const jsGlobalObjectsUrl = `${jsDocPrefix}Reference/Global_Objects/`; const jsGlobalTypes = [ 'Array', 'ArrayBuffer', 'DataView', 'Date', 'Error', 'EvalError', 'Function', - 'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp', 'Set', + 'Map', 'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp', 'Set', 'SharedArrayBuffer', 'SyntaxError', 'TypeError', 'TypedArray', 'URIError', 'Uint8Array', ]; @@ -75,6 +75,8 @@ const customTypesMap = { 'fs.Stats': 'fs.html#fs_class_fs_stats', 'fs.WriteStream': 'fs.html#fs_class_fs_writestream', + 'Histogram': 'perf_hooks.html#perf_hooks_class_histogram', + 'http.Agent': 'http.html#http_class_http_agent', 'http.ClientRequest': 'http.html#http_class_http_clientrequest', 'http.IncomingMessage': 'http.html#http_class_http_incomingmessage', diff --git a/tools/license-builder.sh b/tools/license-builder.sh index 1c884508e3f5e9..be808f00d1be49 100755 --- a/tools/license-builder.sh +++ b/tools/license-builder.sh @@ -95,4 +95,6 @@ addlicense "large_pages" "src/large_pages" "$(sed -e '/SPDX-License-Identifier/, # brotli addlicense "brotli" "deps/brotli" "$(cat ${rootdir}/deps/brotli/LICENSE)" +addlicense "HdrHistogram" "deps/histogram" "$(cat ${rootdir}/deps/histogram/LICENSE.txt)" + mv $tmplicense $licensefile