@nxtedition/histogram
Advanced tools
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAgBnC,CAAA;AAIF,eAAO,MAAM,UAAU,EAAE,MAOrB,CAAA;AAUJ,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAQxE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAK5E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAUpF;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,YAAY,EACvB,WAAW,EAAE,MAAM,EAAE,GACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAmC/B;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IACpC,uFAAuF;IACvF,MAAM,IAAI,YAAY,GAAG,IAAI,CAAA;CAC9B,CAAA;AAiDD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,gBAAqB,EACrB,UAAgC,GACjC,GAAE,iBAAsB,GAAG,UAAU,CA4ErC"} |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} |
+1
-0
@@ -33,1 +33,2 @@ export declare const BUCKETS: readonly number[]; | ||
| export declare function createLagSampler({ sampleIntervalMs, halfLifeMs, }?: LagSamplerOptions): LagSampler; | ||
| //# sourceMappingURL=index.d.ts.map |
+189
-240
@@ -1,155 +0,113 @@ | ||
| import { monitorEventLoopDelay, performance } from 'node:perf_hooks' | ||
| import { monitorEventLoopDelay, performance } from 'node:perf_hooks'; | ||
| // Bucket upper bounds (milliseconds). Values above the highest finite bound | ||
| // land in a final `Infinity` bucket and are reported as `BUCKET_MAX` for | ||
| // percentile/max queries. | ||
| export const BUCKETS = Object.freeze([ | ||
| 1, | ||
| 2, | ||
| 5, | ||
| 10, | ||
| 20, | ||
| 50, | ||
| 100, | ||
| 200, | ||
| 500, | ||
| 1000, | ||
| 2000, | ||
| 5000, | ||
| 10000, | ||
| 30000, | ||
| Infinity, | ||
| ]) | ||
| export const BUCKETS = Object.freeze([ | ||
| 1, | ||
| 2, | ||
| 5, | ||
| 10, | ||
| 20, | ||
| 50, | ||
| 100, | ||
| 200, | ||
| 500, | ||
| 1000, | ||
| 2000, | ||
| 5000, | ||
| 10000, | ||
| 30000, | ||
| Infinity, | ||
| ]); | ||
| // Cap used for percentile/max reporting when the top (Infinity) bucket fires. | ||
| // Derived from the highest finite bound so it stays in sync if BUCKETS changes. | ||
| export const BUCKET_MAX = (() => { | ||
| for (let i = BUCKETS.length - 1; i >= 0; i--) { | ||
| if (Number.isFinite(BUCKETS[i])) { | ||
| return BUCKETS[i] | ||
| export const BUCKET_MAX = (() => { | ||
| for (let i = BUCKETS.length - 1; i >= 0; i--) { | ||
| if (Number.isFinite(BUCKETS[i])) { | ||
| return BUCKETS[i]; | ||
| } | ||
| } | ||
| } | ||
| return 0 | ||
| })() | ||
| function assertLength(fn , histogram ) { | ||
| if (histogram.length !== BUCKETS.length) { | ||
| throw new RangeError( | ||
| `${fn}: length mismatch (histogram=${histogram.length}, buckets=${BUCKETS.length})`, | ||
| ) | ||
| } | ||
| return 0; | ||
| })(); | ||
| function assertLength(fn, histogram) { | ||
| if (histogram.length !== BUCKETS.length) { | ||
| throw new RangeError(`${fn}: length mismatch (histogram=${histogram.length}, buckets=${BUCKETS.length})`); | ||
| } | ||
| } | ||
| export function createHistogram() { | ||
| return new Float64Array(BUCKETS.length) | ||
| export function createHistogram() { | ||
| return new Float64Array(BUCKETS.length); | ||
| } | ||
| export function recordValue(histogram , value ) { | ||
| assertLength('recordValue', histogram) | ||
| for (let i = 0; i < BUCKETS.length; i++) { | ||
| if (value <= BUCKETS[i]) { | ||
| histogram[i]++ | ||
| return | ||
| export function recordValue(histogram, value) { | ||
| assertLength('recordValue', histogram); | ||
| for (let i = 0; i < BUCKETS.length; i++) { | ||
| if (value <= BUCKETS[i]) { | ||
| histogram[i]++; | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| export function decayHistogram(histogram , factor ) { | ||
| assertLength('decayHistogram', histogram) | ||
| for (let i = 0; i < histogram.length; i++) { | ||
| histogram[i] *= factor | ||
| } | ||
| export function decayHistogram(histogram, factor) { | ||
| assertLength('decayHistogram', histogram); | ||
| for (let i = 0; i < histogram.length; i++) { | ||
| histogram[i] *= factor; | ||
| } | ||
| } | ||
| export function mergeHistogram(target , source ) { | ||
| assertLength('mergeHistogram', target) | ||
| if (source.length !== target.length) { | ||
| throw new RangeError( | ||
| `mergeHistogram: length mismatch (target=${target.length}, source=${source.length})`, | ||
| ) | ||
| } | ||
| for (let i = 0; i < target.length; i++) { | ||
| target[i] += source[i] | ||
| } | ||
| export function mergeHistogram(target, source) { | ||
| assertLength('mergeHistogram', target); | ||
| if (source.length !== target.length) { | ||
| throw new RangeError(`mergeHistogram: length mismatch (target=${target.length}, source=${source.length})`); | ||
| } | ||
| for (let i = 0; i < target.length; i++) { | ||
| target[i] += source[i]; | ||
| } | ||
| } | ||
| export function computePercentiles( | ||
| histogram , | ||
| percentiles , | ||
| ) { | ||
| assertLength('computePercentiles', histogram) | ||
| let total = 0 | ||
| for (let i = 0; i < histogram.length; i++) { | ||
| total += histogram[i] | ||
| } | ||
| if (total === 0) { | ||
| return null | ||
| } | ||
| const result = {} | ||
| for (const p of percentiles) { | ||
| // Percentile keys like "p50" / "p95" / "p99". Fractional percentiles such | ||
| // as 0.995 become "p99.5". Two percentiles that round to the same key | ||
| // (e.g. 0.995 and 0.999 truncated to p99) would silently overwrite each | ||
| // other, so we reject that up front. | ||
| const key = `p${Math.round(p * 1000) / 10}` | ||
| if (key in result) { | ||
| throw new RangeError( | ||
| `computePercentiles: duplicate percentile key '${key}' — cannot distinguish percentiles that round to the same name`, | ||
| ) | ||
| } | ||
| const threshold = total * p | ||
| let cumulative = 0 | ||
| export function computePercentiles(histogram, percentiles) { | ||
| assertLength('computePercentiles', histogram); | ||
| let total = 0; | ||
| for (let i = 0; i < histogram.length; i++) { | ||
| cumulative += histogram[i] | ||
| if (cumulative >= threshold) { | ||
| result[key] = BUCKETS[i] === Infinity ? BUCKET_MAX : BUCKETS[i] | ||
| break | ||
| } | ||
| total += histogram[i]; | ||
| } | ||
| } | ||
| return result | ||
| if (total === 0) { | ||
| return null; | ||
| } | ||
| const result = {}; | ||
| for (const p of percentiles) { | ||
| // Percentile keys like "p50" / "p95" / "p99". Fractional percentiles such | ||
| // as 0.995 become "p99.5". Two percentiles that round to the same key | ||
| // (e.g. 0.995 and 0.999 truncated to p99) would silently overwrite each | ||
| // other, so we reject that up front. | ||
| const key = `p${Math.round(p * 1000) / 10}`; | ||
| if (key in result) { | ||
| throw new RangeError(`computePercentiles: duplicate percentile key '${key}' — cannot distinguish percentiles that round to the same name`); | ||
| } | ||
| const threshold = total * p; | ||
| let cumulative = 0; | ||
| for (let i = 0; i < histogram.length; i++) { | ||
| cumulative += histogram[i]; | ||
| if (cumulative >= threshold) { | ||
| result[key] = BUCKETS[i] === Infinity ? BUCKET_MAX : BUCKETS[i]; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| export function computeMax(histogram ) { | ||
| assertLength('computeMax', histogram) | ||
| for (let i = BUCKETS.length - 1; i >= 0; i--) { | ||
| if (histogram[i] > 0) { | ||
| return BUCKETS[i] === Infinity ? BUCKET_MAX : BUCKETS[i] | ||
| export function computeMax(histogram) { | ||
| assertLength('computeMax', histogram); | ||
| for (let i = BUCKETS.length - 1; i >= 0; i--) { | ||
| if (histogram[i] > 0) { | ||
| return BUCKETS[i] === Infinity ? BUCKET_MAX : BUCKETS[i]; | ||
| } | ||
| } | ||
| } | ||
| return null | ||
| return null; | ||
| } | ||
| // Credit a single value into the first bucket whose upper bound contains it. | ||
| function creditBucket(hist , valueMs , weight ) { | ||
| for (let i = 0; i < BUCKETS.length; i++) { | ||
| if (valueMs <= BUCKETS[i]) { | ||
| hist[i] += weight | ||
| return | ||
| function creditBucket(hist, valueMs, weight) { | ||
| for (let i = 0; i < BUCKETS.length; i++) { | ||
| if (valueMs <= BUCKETS[i]) { | ||
| hist[i] += weight; | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // Distribute `weight` uniformly across the buckets that overlap the segment | ||
@@ -160,34 +118,33 @@ // (lowerMs, upperMs]. Assuming a uniform distribution within a percentile | ||
| // (the previous approach) systematically inflates p95/p99. | ||
| function creditSegment(hist , lowerMs , upperMs , weight ) { | ||
| if (weight <= 0) { | ||
| return | ||
| } | ||
| const range = upperMs - lowerMs | ||
| if (!(range > 0) || !Number.isFinite(range)) { | ||
| // Degenerate segment (ELD reported the same percentile value on both | ||
| // ends, e.g. all samples equal) — put the whole weight in one bucket. | ||
| creditBucket(hist, upperMs, weight) | ||
| return | ||
| } | ||
| let startMs = lowerMs | ||
| for (let i = 0; i < BUCKETS.length; i++) { | ||
| const bucketTop = BUCKETS[i] | ||
| if (bucketTop < startMs) { | ||
| continue | ||
| function creditSegment(hist, lowerMs, upperMs, weight) { | ||
| if (weight <= 0) { | ||
| return; | ||
| } | ||
| if (startMs >= upperMs) { | ||
| return | ||
| const range = upperMs - lowerMs; | ||
| if (!(range > 0) || !Number.isFinite(range)) { | ||
| // Degenerate segment (ELD reported the same percentile value on both | ||
| // ends, e.g. all samples equal) — put the whole weight in one bucket. | ||
| creditBucket(hist, upperMs, weight); | ||
| return; | ||
| } | ||
| const segTop = bucketTop === Infinity ? upperMs : Math.min(bucketTop, upperMs) | ||
| const fraction = (segTop - startMs) / range | ||
| if (fraction > 0) { | ||
| hist[i] += fraction * weight | ||
| let startMs = lowerMs; | ||
| for (let i = 0; i < BUCKETS.length; i++) { | ||
| const bucketTop = BUCKETS[i]; | ||
| if (bucketTop < startMs) { | ||
| continue; | ||
| } | ||
| if (startMs >= upperMs) { | ||
| return; | ||
| } | ||
| const segTop = bucketTop === Infinity ? upperMs : Math.min(bucketTop, upperMs); | ||
| const fraction = (segTop - startMs) / range; | ||
| if (fraction > 0) { | ||
| hist[i] += fraction * weight; | ||
| } | ||
| startMs = segTop; | ||
| if (bucketTop === Infinity) { | ||
| return; | ||
| } | ||
| } | ||
| startMs = segTop | ||
| if (bucketTop === Infinity) { | ||
| return | ||
| } | ||
| } | ||
| } | ||
| /** | ||
@@ -200,81 +157,73 @@ * Event-loop lag sampler backed by `perf_hooks.monitorEventLoopDelay`. Each | ||
| */ | ||
| export function createLagSampler({ | ||
| sampleIntervalMs = 10, | ||
| halfLifeMs = 12 * 60 * 60 * 1000, | ||
| } = {}) { | ||
| if (!(sampleIntervalMs > 0)) { | ||
| throw new RangeError(`createLagSampler: sampleIntervalMs must be > 0, got ${sampleIntervalMs}`) | ||
| } | ||
| if (!(halfLifeMs > 0)) { | ||
| throw new RangeError(`createLagSampler: halfLifeMs must be > 0, got ${halfLifeMs}`) | ||
| } | ||
| const eld = monitorEventLoopDelay({ resolution: sampleIntervalMs }) | ||
| eld.enable() | ||
| const decayedHist = createHistogram() | ||
| let lastTickAt = performance.now() | ||
| return { | ||
| sample() { | ||
| const now = performance.now() | ||
| const dtMs = now - lastTickAt | ||
| lastTickAt = now | ||
| const decayFactor = 0.5 ** (dtMs / halfLifeMs) | ||
| decayHistogram(decayedHist, decayFactor) | ||
| // Reconstruct approximate bucket counts from ELD's percentile CDF and | ||
| // fold them into the decayed accumulator. For each adjacent percentile | ||
| // pair (p_{i-1}, v_{i-1}), (p_i, v_i), `(p_i - p_{i-1}) / 100 * count` | ||
| // samples fall in (v_{i-1}, v_i]; we distribute that weight uniformly | ||
| // across the buckets overlapping the segment. ELD timings are in | ||
| // nanoseconds; we bucket in milliseconds. | ||
| // | ||
| // NOTE: `eld.percentiles` and `eld.reset()` are not an atomic pair — | ||
| // a sample recorded by the native timer between the two calls is | ||
| // wiped without being counted. ELD exposes no snapshot-and-clear | ||
| // primitive, so this loss is unavoidable; at `sampleIntervalMs=10` | ||
| // and typical sample cadences it's negligible. | ||
| const count = eld.count | ||
| if (count > 0) { | ||
| const entries = [...eld.percentiles.entries()].sort((a, b) => a[0] - b[0]) | ||
| if (entries.length === 0) { | ||
| // Defensive: ELD reports count > 0 but published no percentile | ||
| // points (would require a racey internal state). Credit the mean | ||
| // so we don't lose the samples entirely. | ||
| const meanMs = eld.mean / 1e6 | ||
| if (Number.isFinite(meanMs) && meanMs >= 0) { | ||
| creditBucket(decayedHist, meanMs, count) | ||
| } | ||
| } else { | ||
| const minMs = eld.min / 1e6 | ||
| let prevValueMs = Number.isFinite(minMs) && minMs >= 0 ? minMs : 0 | ||
| let prevPct = 0 | ||
| for (const [pct, valueNs] of entries) { | ||
| const weight = ((pct - prevPct) / 100) * count | ||
| prevPct = pct | ||
| if (weight <= 0) { | ||
| continue | ||
| export function createLagSampler({ sampleIntervalMs = 10, halfLifeMs = 12 * 60 * 60 * 1000, } = {}) { | ||
| if (!(sampleIntervalMs > 0)) { | ||
| throw new RangeError(`createLagSampler: sampleIntervalMs must be > 0, got ${sampleIntervalMs}`); | ||
| } | ||
| if (!(halfLifeMs > 0)) { | ||
| throw new RangeError(`createLagSampler: halfLifeMs must be > 0, got ${halfLifeMs}`); | ||
| } | ||
| const eld = monitorEventLoopDelay({ resolution: sampleIntervalMs }); | ||
| eld.enable(); | ||
| const decayedHist = createHistogram(); | ||
| let lastTickAt = performance.now(); | ||
| return { | ||
| sample() { | ||
| const now = performance.now(); | ||
| const dtMs = now - lastTickAt; | ||
| lastTickAt = now; | ||
| const decayFactor = 0.5 ** (dtMs / halfLifeMs); | ||
| decayHistogram(decayedHist, decayFactor); | ||
| // Reconstruct approximate bucket counts from ELD's percentile CDF and | ||
| // fold them into the decayed accumulator. For each adjacent percentile | ||
| // pair (p_{i-1}, v_{i-1}), (p_i, v_i), `(p_i - p_{i-1}) / 100 * count` | ||
| // samples fall in (v_{i-1}, v_i]; we distribute that weight uniformly | ||
| // across the buckets overlapping the segment. ELD timings are in | ||
| // nanoseconds; we bucket in milliseconds. | ||
| // | ||
| // NOTE: `eld.percentiles` and `eld.reset()` are not an atomic pair — | ||
| // a sample recorded by the native timer between the two calls is | ||
| // wiped without being counted. ELD exposes no snapshot-and-clear | ||
| // primitive, so this loss is unavoidable; at `sampleIntervalMs=10` | ||
| // and typical sample cadences it's negligible. | ||
| const count = eld.count; | ||
| if (count > 0) { | ||
| const entries = [...eld.percentiles.entries()].sort((a, b) => a[0] - b[0]); | ||
| if (entries.length === 0) { | ||
| // Defensive: ELD reports count > 0 but published no percentile | ||
| // points (would require a racey internal state). Credit the mean | ||
| // so we don't lose the samples entirely. | ||
| const meanMs = eld.mean / 1e6; | ||
| if (Number.isFinite(meanMs) && meanMs >= 0) { | ||
| creditBucket(decayedHist, meanMs, count); | ||
| } | ||
| } | ||
| else { | ||
| const minMs = eld.min / 1e6; | ||
| let prevValueMs = Number.isFinite(minMs) && minMs >= 0 ? minMs : 0; | ||
| let prevPct = 0; | ||
| for (const [pct, valueNs] of entries) { | ||
| const weight = ((pct - prevPct) / 100) * count; | ||
| prevPct = pct; | ||
| if (weight <= 0) { | ||
| continue; | ||
| } | ||
| const valueMs = valueNs / 1e6; | ||
| creditSegment(decayedHist, prevValueMs, valueMs, weight); | ||
| prevValueMs = valueMs; | ||
| } | ||
| } | ||
| } | ||
| const valueMs = valueNs / 1e6 | ||
| creditSegment(decayedHist, prevValueMs, valueMs, weight) | ||
| prevValueMs = valueMs | ||
| } | ||
| } | ||
| } | ||
| eld.reset() | ||
| const pcts = computePercentiles(decayedHist, [0.5, 0.95, 0.99]) | ||
| const max = computeMax(decayedHist) | ||
| if (!pcts || max == null) { | ||
| return null | ||
| } | ||
| return { p50: pcts.p50, p95: pcts.p95, p99: pcts.p99, max } | ||
| }, | ||
| [Symbol.dispose]() { | ||
| eld.disable() | ||
| }, | ||
| } | ||
| eld.reset(); | ||
| const pcts = computePercentiles(decayedHist, [0.5, 0.95, 0.99]); | ||
| const max = computeMax(decayedHist); | ||
| if (!pcts || max == null) { | ||
| return null; | ||
| } | ||
| return { p50: pcts.p50, p95: pcts.p95, p99: pcts.p99, max }; | ||
| }, | ||
| [Symbol.dispose]() { | ||
| eld.disable(); | ||
| }, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=index.js.map |
+2
-3
| { | ||
| "name": "@nxtedition/histogram", | ||
| "version": "1.0.3", | ||
| "version": "1.0.4", | ||
| "type": "module", | ||
@@ -23,3 +23,3 @@ "main": "lib/index.js", | ||
| "scripts": { | ||
| "build": "rimraf lib && tsc -p tsconfig.build.json && amaroc ./src/index.ts && mv src/index.js lib/", | ||
| "build": "rimraf lib && tsc -p tsconfig.build.json", | ||
| "prepublishOnly": "yarn build", | ||
@@ -33,3 +33,2 @@ "typecheck": "tsc --noEmit", | ||
| "@types/node": "^25.5.0", | ||
| "amaroc": "^1.0.1", | ||
| "oxlint-tsgolint": "^0.17.0", | ||
@@ -36,0 +35,0 @@ "rimraf": "^6.1.3", |
20646
67.76%4
-20%6
50%258
-4.09%