diff --git a/deps/histogram/include/hdr/hdr_histogram.h b/deps/histogram/include/hdr/hdr_histogram.h index 3dd76037ee67..2c35186a152e 100644 --- a/deps/histogram/include/hdr/hdr_histogram.h +++ b/deps/histogram/include/hdr/hdr_histogram.h @@ -265,8 +265,9 @@ 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. + * Get minimum value from the histogram. Will return 0 if the histogram is + * empty, like hdr_max; a recorded 0 also gives 0, so use hdr_total_count to + * tell the two apart. * * @param h "This" pointer */ @@ -292,7 +293,10 @@ int64_t hdr_total_count(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 + * @param percentile The percentile to get the value for. Above 100 gives the value at + * 100; below 0 and -inf give the first recorded value. Exactly 0 returns the lowest + * equivalent value of that bucket. + * @return the value at the percentile, or 0 if no values have been recorded. */ int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile); @@ -300,11 +304,14 @@ int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile * Get the values at the given percentiles. * * @param h "This" pointer. - * @param percentiles The ordered percentiles array to get the values for. + * @param percentiles The ordered percentiles array to get the values for, with the + * same out-of-range handling as hdr_value_at_percentile. * @param length Number of elements in the arrays. * @param values Destination array containing the values at the given percentiles. * The values array should be allocated by the caller. - * @return 0 on success, ENOMEM if the provided destination array is null. + * Percentiles that cannot be resolved get the same value hdr_value_at_percentile + * returns for them, so all are 0 on an empty histogram. + * @return 0 on success, EINVAL if percentiles or values is null. */ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percentiles, int64_t *values, size_t length); @@ -312,7 +319,7 @@ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percen * Gets the standard deviation for the values in the histogram. * * @param h "This" pointer - * @return The standard deviation + * @return The standard deviation, or 0.0 if the histogram is empty. */ double hdr_stddev(const struct hdr_histogram* h); @@ -320,7 +327,7 @@ double hdr_stddev(const struct hdr_histogram* h); * Gets the mean for the values in the histogram. * * @param h "This" pointer - * @return The mean + * @return The mean, or 0.0 if the histogram is empty. */ double hdr_mean(const struct hdr_histogram* h); diff --git a/deps/histogram/include/hdr/hdr_histogram_version.h b/deps/histogram/include/hdr/hdr_histogram_version.h index c009dce9f205..a03cb6215a74 100644 --- a/deps/histogram/include/hdr/hdr_histogram_version.h +++ b/deps/histogram/include/hdr/hdr_histogram_version.h @@ -7,6 +7,6 @@ #ifndef HDR_HISTOGRAM_VERSION_H #define HDR_HISTOGRAM_VERSION_H -#define HDR_HISTOGRAM_VERSION "0.12.0" +#define HDR_HISTOGRAM_VERSION "0.12.1" #endif // HDR_HISTOGRAM_VERSION_H diff --git a/deps/histogram/src/hdr_histogram.c b/deps/histogram/src/hdr_histogram.c index 3d10cf589692..71c26c9e8d82 100644 --- a/deps/histogram/src/hdr_histogram.c +++ b/deps/histogram/src/hdr_histogram.c @@ -780,7 +780,7 @@ int64_t hdr_total_count(const struct hdr_histogram* h) int64_t hdr_min(const struct hdr_histogram* h) { - if (0 < hdr_count_at_index(h, 0)) + if (0 == hdr_total_count(h) || 0 < hdr_count_at_index(h, 0)) { return 0; } @@ -914,12 +914,25 @@ static int64_t get_value_from_idx_up_to_count(const struct hdr_histogram* h, int } +/* Target cumulative count in [1, total_count]; NaN/+inf -> total_count, -inf/negative -> 1 */ +static int64_t count_at_percentile(int64_t total_count, double percentile) +{ + const double requested = percentile < 100.0 ? percentile : 100.0; + const double cc = ((requested / 100) * (double) total_count) + 0.5; + if (!(cc >= 1.0)) return 1; + if (cc >= (double) total_count) return total_count; + return (int64_t) cc; +} + int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile) { - double requested_percentile = percentile < 100.0 ? percentile : 100.0; - int64_t count_at_percentile = - (int64_t) (((requested_percentile / 100) * h->total_count) + 0.5); - int64_t value_from_idx = get_value_from_idx_up_to_count(h, count_at_percentile); + /* empty: the scan's not-found 0 must not be rounded up to highest_equivalent_value(0) */ + if (HDR_UNLIKELY(h->total_count == 0)) + { + return 0; + } + int64_t value_from_idx = + get_value_from_idx_up_to_count(h, count_at_percentile(h->total_count, percentile)); if (percentile == 0.0) { return lowest_equivalent_value(h, value_from_idx); @@ -927,12 +940,26 @@ int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile return highest_equivalent_value(h, value_from_idx); } +/* p0 is the bucket's lowest value, matching hdr_value_at_percentile */ +static int64_t percentile_bound(const struct hdr_histogram* h, double percentile, int64_t value) +{ + return percentile == 0.0 ? lowest_equivalent_value(h, value) : highest_equivalent_value(h, value); +} + int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percentiles, int64_t *values, size_t length) { if (NULL == percentiles || NULL == values) { return EINVAL; } + if (HDR_UNLIKELY(h->total_count == 0)) + { + for (size_t i = 0; i < length; i++) + { + values[i] = 0; + } + return 0; + } struct hdr_iter iter; const int64_t total_count = h->total_count; @@ -940,10 +967,7 @@ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percen // i.e. to store the expected cumulative count at each percentile for (size_t i = 0; i < length; i++) { - const double requested_percentile = percentiles[i] < 100.0 ? percentiles[i] : 100.0; - const int64_t count_at_percentile = - (int64_t) (((requested_percentile / 100) * total_count) + 0.5); - values[i] = count_at_percentile > 1 ? count_at_percentile : 1; + values[i] = count_at_percentile(total_count, percentiles[i]); } uint64_t total = 0; /* unsigned: no signed-overflow UB when a hostile block sum is added at once */ @@ -974,7 +998,7 @@ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percen total += (uint64_t)counts[j]; while (at_pos < length && (int64_t)total >= values[at_pos]) { - values[at_pos] = highest_equivalent_value(h, hdr_value_at_index(h, j)); + values[at_pos] = percentile_bound(h, percentiles[at_pos], hdr_value_at_index(h, j)); at_pos++; } } @@ -990,7 +1014,7 @@ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percen total += (uint64_t)counts[idx]; while (at_pos < length && (int64_t)total >= values[at_pos]) { - values[at_pos] = highest_equivalent_value(h, hdr_value_at_index(h, idx)); + values[at_pos] = percentile_bound(h, percentiles[at_pos], hdr_value_at_index(h, idx)); at_pos++; } } @@ -1005,11 +1029,17 @@ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percen total += (uint64_t)iter.count; while (at_pos < length && (int64_t)total >= values[at_pos]) { - values[at_pos] = highest_equivalent_value(h, iter.value); + values[at_pos] = percentile_bound(h, percentiles[at_pos], iter.value); at_pos++; } } } + /* unreached targets (counts below total_count) get the singular's not-found value */ + for (; at_pos < length; at_pos++) + { + values[at_pos] = percentiles[at_pos] == 0.0 ? + lowest_equivalent_value(h, 0) : highest_equivalent_value(h, 0); + } return 0; } @@ -1020,6 +1050,11 @@ double hdr_mean(const struct hdr_histogram* h) int64_t count = 0; int64_t total_count = h->total_count; + if (total_count == 0) + { + return 0.0; + } + hdr_iter_init(&iter, h); while (hdr_iter_next(&iter) && count < total_count) @@ -1037,6 +1072,11 @@ double hdr_mean(const struct hdr_histogram* h) double hdr_stddev(const struct hdr_histogram* h) { + if (h->total_count == 0) + { + return 0.0; + } + double mean = hdr_mean(h); double geometric_dev_total = 0.0; diff --git a/test/parallel/test-perf-hooks-histogram-snapshot.js b/test/parallel/test-perf-hooks-histogram-snapshot.js index ab554373bbc5..e1a1f2042f85 100644 --- a/test/parallel/test-perf-hooks-histogram-snapshot.js +++ b/test/parallel/test-perf-hooks-histogram-snapshot.js @@ -97,7 +97,7 @@ function assertReadOnly(snapshot) { const snapshot = histogram.snapshot(); assertReadOnly(snapshot); assert.strictEqual(snapshot.count, 0); - assert.strictEqual(snapshot.minBigInt, 9223372036854775807n); + assert.strictEqual(snapshot.minBigInt, 0n); assert.strictEqual(snapshot.maxBigInt, 0n); assertSameState(snapshot, histogram); } diff --git a/test/parallel/test-perf-hooks-histogram-stats.js b/test/parallel/test-perf-hooks-histogram-stats.js index 151b43e08c2b..892267c933ef 100644 --- a/test/parallel/test-perf-hooks-histogram-stats.js +++ b/test/parallel/test-perf-hooks-histogram-stats.js @@ -12,7 +12,7 @@ const { createHistogram, importHistogram } = require('perf_hooks'); const empty = createHistogram(); const emptyCI = empty.meanCI(); assert.strictEqual(Object.getPrototypeOf(emptyCI), null); - assert.ok(Number.isNaN(emptyCI.mean)); + assert.strictEqual(emptyCI.mean, 0); assert.ok(Number.isNaN(emptyCI.lower)); assert.ok(Number.isNaN(emptyCI.upper)); @@ -560,7 +560,7 @@ const { createHistogram, importHistogram } = require('perf_hooks'); const emptyBuf = empty.export(); const empty2 = importHistogram(emptyBuf); assert.strictEqual(empty2.count, 0); - assert.strictEqual(empty2.min, 9223372036854776000); // INT64_MAX as double + assert.strictEqual(empty2.min, 0); // Sparse: only a few distinct values const sparse = createHistogram(); diff --git a/test/parallel/test-perf-hooks-histogram.js b/test/parallel/test-perf-hooks-histogram.js index 3f5cf29e53b6..f153775def31 100644 --- a/test/parallel/test-perf-hooks-histogram.js +++ b/test/parallel/test-perf-hooks-histogram.js @@ -14,14 +14,14 @@ const { inspect } = require('util'); { const h = createHistogram(); - assert.strictEqual(h.min, 9223372036854776000); - assert.strictEqual(h.minBigInt, 9223372036854775807n); + assert.strictEqual(h.min, 0); + assert.strictEqual(h.minBigInt, 0n); assert.strictEqual(h.max, 0); assert.strictEqual(h.maxBigInt, 0n); assert.strictEqual(h.exceeds, 0); assert.strictEqual(h.exceedsBigInt, 0n); - assert.ok(Number.isNaN(h.mean)); - assert.ok(Number.isNaN(h.stddev)); + assert.strictEqual(h.mean, 0); + assert.strictEqual(h.stddev, 0); assert.strictEqual(h.count, 0); assert.strictEqual(h.countBigInt, 0n); diff --git a/test/sequential/test-performance-eventloopdelay.js b/test/sequential/test-performance-eventloopdelay.js index ede729514937..d02a9ff8836d 100644 --- a/test/sequential/test-performance-eventloopdelay.js +++ b/test/sequential/test-performance-eventloopdelay.js @@ -106,10 +106,10 @@ function runEventLoopIterations(iterations, callback) { assert(histogram.percentile(n) >= 0); } histogram.reset(); - assert.strictEqual(histogram.min, 9223372036854776000); + assert.strictEqual(histogram.min, 0); assert.strictEqual(histogram.max, 0); - assert(Number.isNaN(histogram.stddev)); - assert(Number.isNaN(histogram.mean)); + assert.strictEqual(histogram.stddev, 0); + assert.strictEqual(histogram.mean, 0); assert.strictEqual(histogram.percentiles.size, 1); ['a', false, {}, []].forEach((i) => { @@ -158,9 +158,9 @@ function runEventLoopIterations(iterations, callback) { histogram.reset(); assert.strictEqual(histogram.count, 0); assert.strictEqual(histogram.max, 0); - assert.strictEqual(histogram.min, 9223372036854776000); - assert(Number.isNaN(histogram.mean)); - assert(Number.isNaN(histogram.stddev)); + assert.strictEqual(histogram.min, 0); + assert.strictEqual(histogram.mean, 0); + assert.strictEqual(histogram.stddev, 0); assert.strictEqual(histogram.percentiles.size, 1); })); }