Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions benchmark/perf_hooks/histogram-snapshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const assert = require('assert');
const common = require('../common.js');
const { createHistogram } = require('perf_hooks');

const bench = common.createBenchmark(main, {
n: [1e3],
highest: [1e6, Number.MAX_SAFE_INTEGER],
figures: [2, 3],
});

let snapshot;

function main({ n, highest, figures }) {
const histogram = createHistogram({ highest, figures });
for (let i = 1; i <= 1e4; i++) histogram.record(i);

bench.start();
for (let i = 0; i < n; i++) snapshot = histogram.snapshot();
bench.end(n);

assert.strictEqual(snapshot.count, 1e4);
}
97 changes: 95 additions & 2 deletions doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2152,6 +2152,52 @@ added:
Returns the number of recorded values that fall within the equivalent
value range of the given value.

### `histogram.diff(other)`

<!-- YAML
added: REPLACEME
-->

* `other` {Histogram} An earlier snapshot of this histogram.
* Returns: {Histogram}

Returns a new {Histogram} containing the values recorded in this histogram after
`other` was taken. Neither histogram is changed. To get the values recorded
during each interval without calling `reset()`, compute each difference from a
snapshot and keep that snapshot as the baseline for the next interval:

```js
const { monitorEventLoopDelay } = require('node:perf_hooks');

const histogram = monitorEventLoopDelay();
histogram.enable();
let previous = histogram.snapshot();

setInterval(() => {
const current = histogram.snapshot();
// After a reset, use everything recorded since the reset.
const delta = current.resetCount === previous.resetCount ?
current.diff(previous) : current;
console.log(delta.percentile(99));
previous = current;
}, 10_000);
```

The `count`, `exceeds`, and bucket counts of the returned histogram are the
differences between the two histograms. Its `min` and `max` are computed from
the buckets of the difference, it has no EWMA state, and its `resetCount` is
`0`.

This method throws:

* `ERR_INVALID_ARG_VALUE` if `other` has a different `lowest`, `highest`, or
`figures` configuration.
* `ERR_INVALID_STATE` if values have been removed from this histogram since
`other` was taken, which is the case when the `resetCount` of the two
histograms differs.
* `ERR_INVALID_ARG_VALUE` if `other` contains values that are not in this
histogram, for example because the histograms were passed in the wrong order.

### `histogram.exceeds`

<!-- YAML
Expand Down Expand Up @@ -2604,7 +2650,21 @@ boundaries are equal has an infinite density.
added: v11.10.0
-->

Resets the collected histogram data.
Resets the collected histogram data and increments `histogram.resetCount`.

### `histogram.resetCount`

<!-- YAML
added: REPLACEME
-->

* Type: {number}

The number of times values have been removed from this histogram by `reset()`
or, for a {RecordableHistogram}, `subtract()`. A snapshot has the `resetCount`
of its source at the time it was taken, so comparing the `resetCount` of two
snapshots shows whether the source was reset between them. See
[`histogram.diff()`][].

### `histogram.skewness`

Expand All @@ -2621,6 +2681,38 @@ distribution. A positive value indicates a right-skewed distribution
(longer right tail, common for latency data); a negative value
indicates a left-skewed distribution.

### `histogram.snapshot()`

<!-- YAML
added: REPLACEME
-->

* Returns: {Histogram}

Returns a new, independent {Histogram} containing a copy of this histogram's
current state: its configuration, recorded values, `exceeds` count, and EWMA
state. Values recorded into this histogram after this method returns, and later
calls to `reset()`, do not change the returned histogram. This provides a stable
view of a histogram that is still recording, such as an enabled {ELDHistogram}.

Values cannot be recorded into the returned histogram. Taking a snapshot copies
every bucket, so both its time and memory cost depend on the histogram's
`lowest`, `highest`, and `figures` configuration rather than on the number of
recorded values.

```js
const { monitorEventLoopDelay } = require('node:perf_hooks');

const histogram = monitorEventLoopDelay();
histogram.enable();

setTimeout(() => {
const snapshot = histogram.snapshot();
console.log(snapshot.percentile(99));
histogram.disable();
}, 1000);
```

### `histogram.stddev`

<!-- YAML
Expand Down Expand Up @@ -2778,7 +2870,7 @@ added:

Subtracts the values of `other` from this histogram. Both histograms should
have compatible configurations. Bucket counts that would become negative
are clamped to zero.
are clamped to zero. Increments `histogram.resetCount`.

## Class: `SlidingWindowHistogram`

Expand Down Expand Up @@ -3257,6 +3349,7 @@ dns.promises.resolve('localhost');
[Worker threads]: worker_threads.md#worker-threads
[`'exit'`]: process.md#event-exit
[`child_process.spawnSync()`]: child_process.md#child_processspawnsynccommand-args-options
[`histogram.diff()`]: #histogramdiffother
[`histogram.export()`]: #histogramexport
[`perf_hooks.createSlidingWindowHistogram()`]: #perf_hookscreateslidingwindowhistogramoptions
[`perf_hooks.eventLoopUtilization()`]: #perf_hookseventlooputilizationutilization1-utilization2
Expand Down
38 changes: 38 additions & 0 deletions lib/internal/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,44 @@ class Histogram {
this[kHandle]?.reset();
}

/**
* The number of times values have been removed from the histogram by
* `reset()` or `subtract()`.
* @readonly
* @type {number}
*/
get resetCount() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return this[kHandle]?.resetCount();
}

/**
* Returns a new, independent histogram containing a copy of this
* histogram's current state. Values cannot be recorded into the returned
* histogram.
* @returns {Histogram}
*/
snapshot() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return new ClonedHistogram(this[kHandle].snapshot());
}

/**
* Returns a new histogram containing the values recorded in this histogram
* after `other`, an earlier snapshot of it, was taken.
* @param {Histogram} other
* @returns {Histogram}
*/
diff(other) {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
if (!isHistogram(other))
throw new ERR_INVALID_ARG_TYPE('other', 'Histogram', other);
return new ClonedHistogram(this[kHandle].diff(other[kHandle]));
}

[kClone]() {
const handle = this[kHandle];
return {
Expand Down
6 changes: 6 additions & 0 deletions src/histogram-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ void Histogram::Reset() {
RwLock::ScopedWriteLock lock(mutex_);
hdr_reset(histogram_.get());
InvalidateRecordedSnapshot();
reset_count_++;
exceeds_ = 0;
prev_ = 0;
ewma_mean_ = 0;
Expand Down Expand Up @@ -90,6 +91,11 @@ size_t Histogram::Exceeds() const {
return exceeds_;
}

uint64_t Histogram::ResetCount() const {
RwLock::ScopedReadLock lock(mutex_);
return reset_count_;
}

int64_t Histogram::Min() const {
RwLock::ScopedReadLock lock(mutex_);
return hdr_min(histogram_.get());
Expand Down
Loading
Loading