From 129015887315e15aab88075c6f1e913ea3b89b0f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 20 Aug 2026 07:22:45 -0700 Subject: [PATCH 1/2] perf_hooks: add CBOR export/import for histogram exchange Provide a binary export/import mechanism for histograms. Since there is no standard interchange format for histograms, using CBOR is meant to make the format as platform/runtime agnostic as possible while producing a compact/efficient result. No new dependency is introduced, we just encode the CBOR directly. Signed-off-by: James M Snell Assisted-by: Opencode/Opus --- doc/api/perf_hooks.md | 68 +++ lib/internal/histogram.js | 29 + lib/perf_hooks.js | 2 + src/histogram.cc | 537 +++++++++++++++++- src/histogram.h | 15 +- .../test-perf-hooks-histogram-stats.js | 138 ++++- 6 files changed, 769 insertions(+), 20 deletions(-) diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index 1e5f059a3835..72d2b9c3b7f0 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -1639,6 +1639,34 @@ added: Returns a {RecordableHistogram}. +## `perf_hooks.importHistogram(data)` + + + +* `data` {Uint8Array} A CBOR-encoded histogram previously produced by + [`histogram.export()`][]. +* Returns: {RecordableHistogram} + +Reconstructs a histogram from a CBOR-encoded `Uint8Array`. The returned +histogram is a full {RecordableHistogram} with all bucket data, configuration, +and EWMA state restored. New values can be recorded into it. + +```js +const { createHistogram, importHistogram } = require('node:perf_hooks'); + +const h = createHistogram(); +for (let i = 1; i <= 1000; i++) h.record(i); + +// Serialize and reconstruct +const data = h.export(); +const h2 = importHistogram(data); + +console.log(h2.count); // 1000 +console.log(h2.percentile(99)); // Same as h.percentile(99) +``` + ## `perf_hooks.eventLoopUtilization([utilization1[, utilization2]])` + +* Returns: {Uint8Array} + +Serializes the histogram to a [CBOR][]-encoded (RFC 8949) `Uint8Array` +suitable for transmission or persistent storage. The encoding uses a +delta-encoded sparse representation of the bucket counts, so the output size +scales with the number of distinct recorded values rather than the total +bucket count. + +The output includes all histogram configuration, bucket data, and EWMA +state (when enabled). It can be reconstructed into a new histogram using +[`perf_hooks.importHistogram()`][]. + +The CBOR payload is a map with integer keys: + +| Key | Type | Field | +| --- | ------- | --------------------------------------------- | +| 0 | uint | Format version (currently 1) | +| 1 | uint | Lowest discernible value | +| 2 | uint | Highest trackable value | +| 3 | uint | Significant figures | +| 4 | uint | Total count | +| 5 | uint | Min value | +| 6 | uint | Max value | +| 7 | uint | Normalizing index offset | +| 8 | float64 | Conversion ratio | +| 9 | uint | Counts array length | +| 10 | array | Delta-encoded sparse counts `[delta, c, ...]` | +| 11 | map | EWMA state (omitted when disabled) | + +Any standard CBOR decoder can parse the output. + ### `histogram.ewmaMean` +### `histogram.burnRate(sloTarget)` + + + +* `sloTarget` {number} The SLO target as a fraction between 0 and 1 + (exclusive). For example, `0.999` for a 99.9% SLO. +* Returns: {number} + +Returns the SLO burn rate: `ewmaErrorRate / (1 - sloTarget)`. A burn rate +of 1 means the error budget will be exactly exhausted over the SLO window. +A burn rate greater than 1 means it is being consumed faster than allowed. +Requires the histogram to have been created with both `halfLife` and +`threshold` options. + +```js +const { createHistogram } = require('node:perf_hooks'); + +// Track latency with a 200ms SLO threshold, half-life of 100 samples +const h = createHistogram({ halfLife: 100, threshold: 200_000_000 }); + +// ... record latency values ... + +// Check burn rate against a 99.9% SLO +const rate = h.burnRate(0.999); +if (rate > 1) { + console.log(`SLO burn rate: ${rate.toFixed(2)}x — error budget depleting`); +} +``` + ### `histogram.count` - -* `sloTarget` {number} The SLO target as a fraction between 0 and 1 - (exclusive). For example, `0.999` for a 99.9% SLO. -* Returns: {number} - -Returns the SLO burn rate: `ewmaErrorRate / (1 - sloTarget)`. A burn rate -of 1 means the error budget will be exactly exhausted over the SLO window. -A burn rate greater than 1 means it is being consumed faster than allowed. -Requires the histogram to have been created with both `halfLife` and -`threshold` options. - -```js -const { createHistogram } = require('node:perf_hooks'); - -// Track latency with a 200ms SLO threshold, half-life of 100 samples -const h = createHistogram({ halfLife: 100, threshold: 200_000_000 }); - -// ... record latency values ... - -// Check burn rate against a 99.9% SLO -const rate = h.burnRate(0.999); -if (rate > 1) { - console.log(`SLO burn rate: ${rate.toFixed(2)}x — error budget depleting`); -} -``` - ### `histogram.ksTest(other)`