From c1e2478bcd41933bc88d46e77e73beb48c00fb10 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 5 Sep 2026 20:27:04 +0000 Subject: [PATCH] test: expand histogram test coverage Signed-off-by: James M Snell Assisted-by: Opencode PR-URL: https://github.com/nodejs/node/pull/65825 Reviewed-By: Matteo Collina --- .../test-perf-hooks-histogram-qrde-worker.js | 21 ++++++++ .../test-perf-hooks-histogram-qrde.js | 27 ++++++++++ ...est-perf-hooks-sliding-window-histogram.js | 18 +++++++ .../test-perf-hooks-histogram-heapdump.js | 54 +++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 test/parallel/test-perf-hooks-histogram-qrde-worker.js create mode 100644 test/sequential/test-perf-hooks-histogram-heapdump.js diff --git a/test/parallel/test-perf-hooks-histogram-qrde-worker.js b/test/parallel/test-perf-hooks-histogram-qrde-worker.js new file mode 100644 index 00000000000..ba836651665 --- /dev/null +++ b/test/parallel/test-perf-hooks-histogram-qrde-worker.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { once } = require('events'); +const { Worker } = require('worker_threads'); + +const worker = new Worker(` + const { parentPort } = require('worker_threads'); + const { createHistogram } = require('perf_hooks'); + + const histogram = createHistogram({ highest: 200000, figures: 5 }); + for (let i = 1; i <= 100000; i++) histogram.record(i); + histogram.qrde({ bins: 1000, dequantize: 'all' }); + parentPort.postMessage('scheduled'); +`, { eval: true }); + +(async () => { + assert.deepStrictEqual(await once(worker, 'message'), ['scheduled']); + assert.strictEqual(await worker.terminate(), 1); +})().then(common.mustCall()); diff --git a/test/parallel/test-perf-hooks-histogram-qrde.js b/test/parallel/test-perf-hooks-histogram-qrde.js index a417942d916..40eb1e71f1e 100644 --- a/test/parallel/test-perf-hooks-histogram-qrde.js +++ b/test/parallel/test-perf-hooks-histogram-qrde.js @@ -9,6 +9,16 @@ function assertClose(actual, expected, tolerance = 1e-12) { `${actual} != ${expected}`); } +function recordRepeated(histogram, options, value, count) { + const block = createHistogram(options); + block.record(value); + while (count > 0) { + if (count % 2 === 1) histogram.add(block); + count = Math.floor(count / 2); + if (count > 0) block.add(block); + } +} + (async () => { const empty = createHistogram(); const emptyResult = await empty.qrde(); @@ -23,6 +33,9 @@ function assertClose(actual, expected, tolerance = 1e-12) { assert.strictEqual(emptyResult.corrections, 0); assert.strictEqual(emptyResult.dequantize, 'hdr'); + assert.throws(() => empty.qrde.call({}), { + code: 'ERR_INVALID_THIS', + }); assert.throws(() => empty.qrde(null), { code: 'ERR_INVALID_ARG_TYPE', }); @@ -219,4 +232,18 @@ function assertClose(actual, expected, tolerance = 1e-12) { await largeCount.qrde({ bins: 2, dequantize: 'none' }); assert.strictEqual(largeCountResult.count, (1n << 53n) + 1n); assertClose(largeCountResult.quantiles[1], 2); + + // Exercise correction across the exact-to-asymptotic beta CDF threshold. + const correctionOptions = { highest: 131071, figures: 5 }; + const correction = createHistogram(correctionOptions); + recordRepeated(correction, correctionOptions, 1, 26239); + recordRepeated(correction, correctionOptions, 131071, 973761); + const count = 1_000_000; + const threshold = (1 - Math.sqrt(1 - 100_000 / (count + 1))) / 2; + const corrected = await correction.qrde({ + probabilities: [0, threshold - 1e-10, threshold + 1e-10, 1], + dequantize: 'none', + }); + assert.strictEqual(corrected.corrections, 1); + assert.strictEqual(corrected.quantiles[1], corrected.quantiles[2]); })().then(common.mustCall()); diff --git a/test/parallel/test-perf-hooks-sliding-window-histogram.js b/test/parallel/test-perf-hooks-sliding-window-histogram.js index 9e28677e5d6..3ee1ca4ea43 100644 --- a/test/parallel/test-perf-hooks-sliding-window-histogram.js +++ b/test/parallel/test-perf-hooks-sliding-window-histogram.js @@ -49,6 +49,11 @@ const { assert.strictEqual(histogram.snapshot().count, 0); histogram.record(10n); assert.strictEqual(histogram.snapshot().maxBigInt, 10n); + for (const value of [0n, 2n ** 63n]) { + assert.throws(() => histogram.record(value), { + code: 'ERR_OUT_OF_RANGE', + }); + } assert.throws(() => new histogram.constructor(), { code: 'ERR_ILLEGAL_CONSTRUCTOR', @@ -142,6 +147,19 @@ const { highest: 10, }), { code: 'ERR_OUT_OF_RANGE' }); + for (const [name, value] of [ + ['lowest', 0n], + ['lowest', 2n ** 63n], + ['highest', 0n], + ['highest', 2n ** 63n], + ]) { + assert.throws(() => createSlidingWindowHistogram({ + chunks: 2, + recordsPerChunk: 1, + [name]: value, + }), { code: 'ERR_OUT_OF_RANGE' }); + } + for (const bounds of [ { lowest: 1n }, { lowest: 1n, highest: 100 }, diff --git a/test/sequential/test-perf-hooks-histogram-heapdump.js b/test/sequential/test-perf-hooks-histogram-heapdump.js new file mode 100644 index 00000000000..cf310eb973b --- /dev/null +++ b/test/sequential/test-perf-hooks-histogram-heapdump.js @@ -0,0 +1,54 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + createJSHeapSnapshot, + validateByRetainingPathFromNodes, +} = require('../common/heap'); +const { + createHistogram, + createSlidingWindowHistogram, +} = require('perf_hooks'); + +(async () => { + const uncached = createHistogram(); + const cached = createHistogram(); + cached.record(1); + cached.record(1000); + await cached.qrde({ cache: true }); + + const sliding = createSlidingWindowHistogram({ + chunks: 2, + recordsPerChunk: 1, + }); + + const nodes = createJSHeapSnapshot(); + const snapshots = validateByRetainingPathFromNodes( + nodes, + 'Node / Histogram', + [{ node_name: 'Node / qrde_snapshot', edge_name: 'qrde_snapshot' }], + ); + assert.strictEqual(snapshots.length, 1); + assert.ok(snapshots[0].self_size > 0); + + const windows = validateByRetainingPathFromNodes( + nodes, + 'Node / SlidingWindowHistogram', + [], + ); + for (const [edgeName, nodeName] of [ + ['chunks', 'Node / chunks'], + ['generations', 'Node / generations'], + ['spare', 'Node / Histogram'], + ]) { + validateByRetainingPathFromNodes(windows, 'Node / SlidingWindowHistogram', [ + { node_name: nodeName, edge_name: edgeName }, + ]); + } + + // Keep all three wrappers live through snapshot generation. + assert.strictEqual(uncached.count, 0); + assert.strictEqual(cached.count, 2); + assert.strictEqual(sliding.snapshot().count, 0); +})().then(common.mustCall());