FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

lib,src: improve histogram implementation · nodejs/node@753033c · GitHub

/ node Public

Commit 753033c

Browse files
authored andcommitted
lib,src: improve histogram implementation
Several improvements: 1. In histogram-inl, Add previous locked only this->mutex while reading the other's fields unsafely. 2. In histogram.cc, PrepareCB now uses ContainerOf 3. In histogram.cc, BigInt value range is checked 4. In histogram.js, simplified impl and reduced duplication 5. In event_loop_delay.js, use a more consistent constructor Adds new analytical APIs to Histogram * histogram.ccdf(value) * histogram.cdf(value) * histogram.countAt(value) * histogram.ksTest(other) * histogram.kurtosis * histogram.linearBuckets(stepSize) * histogram.logBuckets(first, base) * histogram.percentilesAt(percentiles) * histogram.shewness On RecordableHistogram * histogram.recordCorrected(val, expectedInterval) * histogram.subtract(other) Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus PR-URL: #65024 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 4018f3a commit 753033c

7 files changed

Lines changed: 1530 additions & 156 deletions

File tree

‎doc/api/perf_hooks.md‎

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,45 @@ added:
18661866

18671867
The number of samples recorded by the histogram.
18681868

1869+
### `histogram.ccdf(value)`
1870+
1871+
<!-- YAML
1872+
added: REPLACEME
1873+
-->
1874+
1875+
* `value` {number} The value to query.
1876+
* Returns: {number} A probability between 0.0 and 1.0.
1877+
1878+
Returns the complementary cumulative distribution function (CCDF) value
1879+
for the given value, representing the probability that a recorded value
1880+
will exceed `value`. Equivalent to `1 - histogram.cdf(value)`.
1881+
1882+
### `histogram.cdf(value)`
1883+
1884+
<!-- YAML
1885+
added: REPLACEME
1886+
-->
1887+
1888+
* `value` {number} The value to query.
1889+
* Returns: {number} A probability between 0.0 and 1.0.
1890+
1891+
Returns the cumulative distribution function (CDF) value for the given
1892+
value, representing the probability that a recorded value will be less
1893+
than or equal to `value`. This is the inverse operation of
1894+
`histogram.percentile()`.
1895+
1896+
### `histogram.countAt(value)`
1897+
1898+
<!-- YAML
1899+
added: REPLACEME
1900+
-->
1901+
1902+
* `value` {number} The value to query.
1903+
* Returns: {number}
1904+
1905+
Returns the number of recorded values that fall within the equivalent
1906+
value range of the given value.
1907+
18691908
### `histogram.exceeds`
18701909

18711910
<!-- YAML
@@ -1890,6 +1929,59 @@ added:
18901929
The number of times the event loop delay exceeded the maximum 1 hour event
18911930
loop delay threshold.
18921931

1932+
### `histogram.ksTest(other)`
1933+
1934+
<!-- YAML
1935+
added: REPLACEME
1936+
-->
1937+
1938+
* `other` {Histogram} The histogram to compare against.
1939+
* Returns: {number} The KS D-statistic, between 0.0 and 1.0.
1940+
1941+
Computes the Kolmogorov-Smirnov test statistic comparing this histogram's
1942+
distribution to `other`. A value of 0 indicates identical distributions;
1943+
values close to 1 indicate completely disjoint distributions. Useful for
1944+
detecting performance regressions by comparing before/after histograms.
1945+
1946+
### `histogram.kurtosis`
1947+
1948+
<!-- YAML
1949+
added: REPLACEME
1950+
-->
1951+
1952+
* Type: {number}
1953+
1954+
The excess kurtosis of the recorded values. Measures the heaviness of the
1955+
distribution's tails relative to a normal distribution. Positive values
1956+
indicate heavier tails (more extreme outliers); negative values indicate
1957+
lighter tails.
1958+
1959+
### `histogram.linearBuckets(stepSize)`
1960+
1961+
<!-- YAML
1962+
added: REPLACEME
1963+
-->
1964+
1965+
* `stepSize` {number} The width of each linear bucket.
1966+
* Returns: {Map} A map of bucket boundary values to counts.
1967+
1968+
Returns the histogram data rebucketed into linearly-spaced intervals
1969+
of `stepSize`. Useful for visualization and export.
1970+
1971+
### `histogram.logBuckets(firstBucket, base)`
1972+
1973+
<!-- YAML
1974+
added: REPLACEME
1975+
-->
1976+
1977+
* `firstBucket` {number} The value of the first bucket boundary.
1978+
* `base` {number} The logarithmic base for bucket width growth. Must be > 1.
1979+
* Returns: {Map} A map of bucket boundary values to counts.
1980+
1981+
Returns the histogram data rebucketed into logarithmically-spaced
1982+
intervals, where each bucket's width is multiplied by `base`.
1983+
Useful for visualization and export.
1984+
18931985
### `histogram.max`
18941986

18951987
<!-- YAML
@@ -1990,6 +2082,20 @@ added:
19902082

19912083
Returns a `Map` object detailing the accumulated percentile distribution.
19922084

2085+
### `histogram.percentilesAt(percentiles)`
2086+
2087+
<!-- YAML
2088+
added: REPLACEME
2089+
-->
2090+
2091+
* `percentiles` {number\[]} An array of percentile values in the range (0, 100].
2092+
* Returns: {Map} A map of percentile values to their corresponding histogram
2093+
values.
2094+
2095+
Returns the values at the specified percentiles, computed in a single
2096+
efficient pass over the histogram data. More efficient than calling
2097+
`histogram.percentile()` multiple times.
2098+
19932099
### `histogram.reset()`
19942100

19952101
<!-- YAML
@@ -1998,6 +2104,19 @@ added: v11.10.0
19982104

19992105
Resets the collected histogram data.
20002106

2107+
### `histogram.skewness`
2108+
2109+
<!-- YAML
2110+
added: REPLACEME
2111+
-->
2112+
2113+
* Type: {number}
2114+
2115+
The skewness of the recorded values. Measures the asymmetry of the
2116+
distribution. A positive value indicates a right-skewed distribution
2117+
(longer right tail, common for latency data); a negative value
2118+
indicates a left-skewed distribution.
2119+
20012120
### `histogram.stddev`
20022121

20032122
<!-- YAML
@@ -2099,6 +2218,127 @@ added:
20992218
Calculates the amount of time (in nanoseconds) that has passed since the
21002219
previous call to `recordDelta()` and records that amount in the histogram.
21012220

2221+
### `histogram.recordCorrected(val, expectedInterval)`
2222+
2223+
<!-- YAML
2224+
added: REPLACEME
2225+
-->
2226+
2227+
* `val` {number|bigint} The value to record.
2228+
* `expectedInterval` {number|bigint} The expected recording interval.
2229+
2230+
Records a value with coordinated omission correction. When a system stall
2231+
prevents timely recording, this method backfills intermediate values at
2232+
`expectedInterval` steps between the previously recorded value and `val`.
2233+
This compensates for measurement gaps that would otherwise underrepresent
2234+
latency.
2235+
2236+
### `histogram.subtract(other)`
2237+
2238+
<!-- YAML
2239+
added: REPLACEME
2240+
-->
2241+
2242+
* `other` {RecordableHistogram}
2243+
2244+
Subtracts the values of `other` from this histogram. Both histograms should
2245+
have compatible configurations. Bucket counts that would become negative
2246+
are clamped to zero.
2247+
2248+
## Histogram analysis examples
2249+
2250+
The `Histogram` class provides statistical analysis methods useful for
2251+
performance monitoring, SLO enforcement, and regression detection.
2252+
2253+
### Distribution shape analysis
2254+
2255+
```js
2256+
const { createHistogram } = require('node:perf_hooks');
2257+
2258+
const h = createHistogram();
2259+
2260+
// Simulate a right-skewed latency distribution
2261+
for (let i = 0; i < 1000; i++) {
2262+
h.record(Math.ceil(Math.random() * 100));
2263+
}
2264+
// Add some outliers
2265+
for (let i = 0; i < 10; i++) {
2266+
h.record(500 + Math.ceil(Math.random() * 500));
2267+
}
2268+
2269+
console.log('Skewness:', h.skewness.toFixed(4)); // Positive = right-skewed
2270+
console.log('Kurtosis:', h.kurtosis.toFixed(4)); // Positive = heavy tails
2271+
```
2272+
2273+
### SLO monitoring with CDF
2274+
2275+
```js
2276+
const { createHistogram } = require('node:perf_hooks');
2277+
2278+
const latency = createHistogram();
2279+
2280+
// Record request latencies (in nanoseconds)...
2281+
2282+
// "What fraction of requests complete within 100ms?"
2283+
const withinSLO = latency.cdf(100_000_000);
2284+
console.log(`${(withinSLO * 100).toFixed(1)}% of requests within SLO`);
2285+
2286+
// "What fraction of requests exceed 500ms?"
2287+
const violating = latency.ccdf(500_000_000);
2288+
console.log(`${(violating * 100).toFixed(1)}% of requests violating SLO`);
2289+
```
2290+
2291+
### Regression detection with KS test
2292+
2293+
```js
2294+
const { createHistogram } = require('node:perf_hooks');
2295+
2296+
const baseline = createHistogram();
2297+
const current = createHistogram();
2298+
2299+
// Record baseline and current latencies...
2300+
2301+
// D-statistic: 0 = identical, 1 = completely different
2302+
const d = baseline.ksTest(current);
2303+
if (d > 0.1) {
2304+
console.log(`Possible regression detected (D=${d.toFixed(4)})`);
2305+
}
2306+
```
2307+
2308+
### Batch percentile queries
2309+
2310+
```js
2311+
const { createHistogram } = require('node:perf_hooks');
2312+
2313+
const h = createHistogram();
2314+
// Record values...
2315+
2316+
// Efficiently query common monitoring percentiles in one pass
2317+
const p = h.percentilesAt([50, 75, 90, 95, 99, 99.9]);
2318+
console.log('p50:', p.get(50));
2319+
console.log('p99:', p.get(99));
2320+
```
2321+
2322+
### Snapshot diffing with subtract
2323+
2324+
```js
2325+
const { createHistogram } = require('node:perf_hooks');
2326+
2327+
const total = createHistogram();
2328+
const snapshot = createHistogram();
2329+
2330+
// Record values into total...
2331+
// Periodically snapshot for "last interval" analysis:
2332+
snapshot.add(total);
2333+
2334+
// Later, take a new snapshot and diff:
2335+
const newSnapshot = createHistogram();
2336+
newSnapshot.add(total);
2337+
newSnapshot.subtract(snapshot);
2338+
// newSnapshot now contains only the values recorded since the last snapshot
2339+
console.log('Recent p99:', newSnapshot.percentile(99));
2340+
```
2341+
21022342
## Examples
21032343

21042344
### Measuring the duration of async operations

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL