mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
perf_hooks: add histogram.diff()
Getting the values recorded during an interval requires calling `reset()`, which removes them for every other user of the histogram, or copying and subtracting histograms, which silently produces a wrong result when the source was reset in between. Add `histogram.diff(other)`, which returns a new read-only `Histogram` containing the values recorded after `other`, an earlier snapshot of the histogram, was taken. Neither histogram is changed. Unlike `subtract()`, it verifies that both histograms have the same layout, and it throws instead of clamping when `other` contains values that the histogram does not. Add `histogram.resetCount`, the number of calls to `reset()` and `subtract()`, which `snapshot()` copies. `diff()` throws when the counts differ, so a reset between two snapshots is detected even when every bucket has since grown past its previous count. Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: OpenCode PR-URL: https://github.com/nodejs/node/pull/66099 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
1 parent
7d2b7e178d
commit
a3bb551ea7
7 files changed
+392
-8
No files matched your search
+63
-2
@@ -2199,6 +2199,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
|
||||
@@ -2672,7 +2718,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`
|
||||
|
||||
@@ -2878,7 +2938,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`
|
||||
|
||||
@@ -3357,6 +3417,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
|
||||
|
||||
@@ -679,6 +679,18 @@ 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
|
||||
@@ -691,6 +703,20 @@ class 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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
|
||||
+103
-6
@@ -93,17 +93,22 @@ void CopyRecordedData(hdr_histogram* target, const hdr_histogram* source) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<Histogram> Histogram::Clone() const {
|
||||
// The layout is fixed when the histogram is created, so the copy can be
|
||||
// allocated without holding the lock.
|
||||
hdr_histogram* copy;
|
||||
std::shared_ptr<Histogram> Histogram::CreateWithSameLayout() const {
|
||||
// The layout is fixed when the histogram is created, so it can be read
|
||||
// without holding the lock.
|
||||
hdr_histogram* histogram;
|
||||
if (hdr_init(histogram_->lowest_discernible_value,
|
||||
histogram_->highest_trackable_value,
|
||||
histogram_->significant_figures,
|
||||
©) != 0) {
|
||||
&histogram) != 0) {
|
||||
return {};
|
||||
}
|
||||
auto clone = std::make_shared<Histogram>(HistogramPointer(copy), Options{});
|
||||
return std::make_shared<Histogram>(HistogramPointer(histogram), Options{});
|
||||
}
|
||||
|
||||
std::shared_ptr<Histogram> Histogram::Clone() const {
|
||||
std::shared_ptr<Histogram> clone = CreateWithSameLayout();
|
||||
if (!clone) return {};
|
||||
|
||||
// Every member that holds recorded or statistical state must be copied
|
||||
// here. The recorded snapshot cache is not copied; the clone builds its own
|
||||
@@ -112,6 +117,7 @@ std::shared_ptr<Histogram> Histogram::Clone() const {
|
||||
CopyRecordedData(clone->histogram_.get(), histogram_.get());
|
||||
clone->prev_ = prev_;
|
||||
clone->exceeds_ = exceeds_;
|
||||
clone->reset_count_ = reset_count_;
|
||||
clone->ewma_alpha_ = ewma_alpha_;
|
||||
clone->ewma_mean_ = ewma_mean_;
|
||||
clone->ewma_variance_ = ewma_variance_;
|
||||
@@ -121,6 +127,59 @@ std::shared_ptr<Histogram> Histogram::Clone() const {
|
||||
return clone;
|
||||
}
|
||||
|
||||
std::shared_ptr<Histogram> Histogram::Diff(const Histogram& other,
|
||||
DiffError* error) const {
|
||||
// Counts are subtracted index by index, so both histograms must map values
|
||||
// to the same indexes. None of these fields change after creation.
|
||||
if (!IsCompatible(other) || histogram_->normalizing_index_offset !=
|
||||
other.histogram_->normalizing_index_offset) {
|
||||
*error = DiffError::kIncompatible;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Histogram> diff = CreateWithSameLayout();
|
||||
if (!diff) {
|
||||
*error = DiffError::kOutOfMemory;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Only the recorded values and the exceeds count carry over. EWMA and timing
|
||||
// state cannot be subtracted.
|
||||
uint64_t reset_count;
|
||||
{
|
||||
RwLock::ScopedReadLock lock(mutex_);
|
||||
CopyRecordedData(diff->histogram_.get(), histogram_.get());
|
||||
diff->exceeds_ = exceeds_;
|
||||
reset_count = reset_count_;
|
||||
}
|
||||
|
||||
// `diff` is not shared yet, so only the lock of `other` is needed from here
|
||||
// on. Never holding both locks at once avoids lock ordering issues.
|
||||
RwLock::ScopedReadLock lock(other.mutex_);
|
||||
if (reset_count != other.reset_count_) {
|
||||
*error = DiffError::kReset;
|
||||
return {};
|
||||
}
|
||||
if (diff->exceeds_ < other.exceeds_) {
|
||||
*error = DiffError::kNotEarlier;
|
||||
return {};
|
||||
}
|
||||
|
||||
hdr_histogram* target = diff->histogram_.get();
|
||||
const hdr_histogram* source = other.histogram_.get();
|
||||
for (int32_t i = 0; i < target->counts_len; i++) {
|
||||
if (target->counts[i] < source->counts[i]) {
|
||||
*error = DiffError::kNotEarlier;
|
||||
return {};
|
||||
}
|
||||
target->counts[i] -= source->counts[i];
|
||||
}
|
||||
diff->exceeds_ -= other.exceeds_;
|
||||
hdr_reset_internal_counters(target);
|
||||
*error = DiffError::kNone;
|
||||
return diff;
|
||||
}
|
||||
|
||||
void Histogram::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackFieldWithSize("histogram", GetMemorySize());
|
||||
tracker->TrackFieldWithSize("qrde_snapshot",
|
||||
@@ -313,6 +372,7 @@ double Histogram::Subtract(const Histogram& other) {
|
||||
}
|
||||
hdr_reset_internal_counters(histogram_.get());
|
||||
InvalidateRecordedSnapshot();
|
||||
reset_count_++;
|
||||
exceeds_ = (exceeds_ > other.exceeds_) ? exceeds_ - other.exceeds_ : 0;
|
||||
return static_cast<double>(dropped);
|
||||
};
|
||||
@@ -1963,6 +2023,8 @@ void HistogramImpl::AddMethods(Isolate* isolate, Local<FunctionTemplate> tmpl) {
|
||||
&fast_get_ewma_error_rate_);
|
||||
SetProtoMethodNoSideEffect(isolate, tmpl, "export", DoExport);
|
||||
SetProtoMethodNoSideEffect(isolate, tmpl, "snapshot", DoSnapshot);
|
||||
SetProtoMethodNoSideEffect(isolate, tmpl, "diff", DoDiff);
|
||||
SetProtoMethodNoSideEffect(isolate, tmpl, "resetCount", GetResetCount);
|
||||
SetFastMethod(isolate, instance, "reset", DoReset, &fast_reset_);
|
||||
}
|
||||
|
||||
@@ -2013,6 +2075,8 @@ void HistogramImpl::RegisterExternalReferences(
|
||||
registry->Register(GetEwmaErrorRate);
|
||||
registry->Register(DoExport);
|
||||
registry->Register(DoSnapshot);
|
||||
registry->Register(DoDiff);
|
||||
registry->Register(GetResetCount);
|
||||
registry->Register(fast_get_ewma_mean_);
|
||||
registry->Register(fast_get_ewma_stddev_);
|
||||
registry->Register(fast_get_ewma_error_rate_);
|
||||
@@ -3173,6 +3237,39 @@ void HistogramImpl::DoSnapshot(const FunctionCallbackInfo<Value>& args) {
|
||||
if (result) args.GetReturnValue().Set(result->object());
|
||||
}
|
||||
|
||||
void HistogramImpl::DoDiff(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
HistogramImpl* histogram = HistogramImpl::FromJSObject(args.This());
|
||||
HistogramImpl* other = HistogramImpl::FromJSObject(args[0]);
|
||||
Histogram::DiffError error;
|
||||
std::shared_ptr<Histogram> diff =
|
||||
(*histogram)->Diff(*(other->histogram()), &error);
|
||||
switch (error) {
|
||||
case Histogram::DiffError::kNone:
|
||||
break;
|
||||
case Histogram::DiffError::kOutOfMemory:
|
||||
return THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
|
||||
case Histogram::DiffError::kIncompatible:
|
||||
return THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "other must have the same configuration as the histogram");
|
||||
case Histogram::DiffError::kReset:
|
||||
return THROW_ERR_INVALID_STATE(
|
||||
env, "Values were removed from the histogram after other was taken");
|
||||
case Histogram::DiffError::kNotEarlier:
|
||||
return THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "other contains values that are not in the histogram");
|
||||
}
|
||||
|
||||
BaseObjectPtr<HistogramBase> result =
|
||||
HistogramBase::Create(env, std::move(diff));
|
||||
if (result) args.GetReturnValue().Set(result->object());
|
||||
}
|
||||
|
||||
void HistogramImpl::GetResetCount(const FunctionCallbackInfo<Value>& args) {
|
||||
HistogramImpl* histogram = HistogramImpl::FromJSObject(args.This());
|
||||
args.GetReturnValue().Set(static_cast<double>((*histogram)->ResetCount()));
|
||||
}
|
||||
|
||||
void HistogramImpl::GetPercentilesAt(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
HistogramImpl* histogram = HistogramImpl::FromJSObject(args.This());
|
||||
|
||||
@@ -65,6 +65,23 @@ class Histogram : public MemoryRetainer {
|
||||
// if the copy cannot be allocated.
|
||||
std::shared_ptr<Histogram> Clone() const;
|
||||
|
||||
enum class DiffError {
|
||||
kNone,
|
||||
kOutOfMemory,
|
||||
// `other` has a different layout.
|
||||
kIncompatible,
|
||||
// Values were removed from this histogram after `other` was taken.
|
||||
kReset,
|
||||
// `other` contains values that this histogram does not.
|
||||
kNotEarlier,
|
||||
};
|
||||
|
||||
// Returns a new histogram containing the values recorded in this histogram
|
||||
// after `other`, an earlier copy of it, was taken. Returns nullptr and sets
|
||||
// `error` if the difference cannot be computed.
|
||||
std::shared_ptr<Histogram> Diff(const Histogram& other,
|
||||
DiffError* error) const;
|
||||
|
||||
Histogram(HistogramPointer histogram, const Options& options);
|
||||
virtual ~Histogram() = default;
|
||||
|
||||
@@ -80,6 +97,7 @@ class Histogram : public MemoryRetainer {
|
||||
inline int64_t Percentile(double percentile) const;
|
||||
inline size_t Exceeds() const;
|
||||
inline size_t Count() const;
|
||||
inline uint64_t ResetCount() const;
|
||||
|
||||
inline uint64_t RecordDelta();
|
||||
|
||||
@@ -165,10 +183,13 @@ class Histogram : public MemoryRetainer {
|
||||
inline void UpdateEwma(double value);
|
||||
inline void InvalidateRecordedSnapshot();
|
||||
size_t GetCachedRecordedSnapshotMemorySize() const;
|
||||
std::shared_ptr<Histogram> CreateWithSameLayout() const;
|
||||
|
||||
HistogramPointer histogram_;
|
||||
uint64_t prev_ = 0;
|
||||
size_t exceeds_ = 0;
|
||||
// Incremented whenever recorded values are removed by Reset() or Subtract().
|
||||
uint64_t reset_count_ = 0;
|
||||
|
||||
// EWMA state (active when ewma_alpha_ > 0)
|
||||
double ewma_alpha_ = 0;
|
||||
@@ -242,6 +263,8 @@ class HistogramImpl {
|
||||
static void DoExport(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void DoImport(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void DoSnapshot(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void DoDiff(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetResetCount(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void FastReset(v8::Local<v8::Value> receiver);
|
||||
static double FastGetCount(v8::Local<v8::Value> receiver);
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { setTimeout: delay } = require('timers/promises');
|
||||
const {
|
||||
createHistogram,
|
||||
createSlidingWindowHistogram,
|
||||
importHistogram,
|
||||
monitorEventLoopDelay,
|
||||
} = require('perf_hooks');
|
||||
|
||||
{
|
||||
const histogram = createHistogram({ highest: 1000, halfLife: 4, threshold: 50 });
|
||||
for (let i = 1; i <= 10; i++) histogram.record(i);
|
||||
histogram.record(2000);
|
||||
const previous = histogram.snapshot();
|
||||
for (let i = 101; i <= 120; i++) histogram.record(i);
|
||||
histogram.record(3000);
|
||||
histogram.record(4000);
|
||||
const current = histogram.snapshot();
|
||||
const exportedPrevious = previous.export();
|
||||
const exportedCurrent = current.export();
|
||||
|
||||
const delta = current.diff(previous);
|
||||
assert.strictEqual(delta.constructor.name, 'Histogram');
|
||||
assert.strictEqual(delta.record, undefined);
|
||||
assert.strictEqual(delta.count, 20);
|
||||
assert.strictEqual(delta.exceeds, 2);
|
||||
assert.strictEqual(delta.min, 101);
|
||||
assert.strictEqual(delta.max, 120);
|
||||
assert.strictEqual(delta.resetCount, 0);
|
||||
|
||||
// The difference has the same distribution as a histogram of the values
|
||||
// recorded between the snapshots, and no EWMA state.
|
||||
const expected = createHistogram({ highest: 1000 });
|
||||
for (let i = 101; i <= 120; i++) expected.record(i);
|
||||
assert.deepStrictEqual(delta.percentiles, expected.percentiles);
|
||||
assert.strictEqual(delta.ewmaMean, 0);
|
||||
assert.strictEqual(delta.ewmaStddev, 0);
|
||||
assert.strictEqual(delta.ewmaErrorRate, 0);
|
||||
|
||||
// Neither histogram is changed.
|
||||
assert.deepStrictEqual(previous.export(), exportedPrevious);
|
||||
assert.deepStrictEqual(current.export(), exportedCurrent);
|
||||
|
||||
assert.strictEqual(histogram.diff(previous).count, 20);
|
||||
assert.strictEqual(histogram.diff(histogram).count, 0);
|
||||
assert.strictEqual(current.diff(current).count, 0);
|
||||
|
||||
// Reversed arguments.
|
||||
assert.throws(() => previous.diff(current), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// Consumers with different intervals each keep their own previous snapshot.
|
||||
const histogram = createHistogram();
|
||||
const consumers = [3, 10].map((interval) => ({
|
||||
interval,
|
||||
previous: histogram.snapshot(),
|
||||
pending: 0,
|
||||
total: 0,
|
||||
}));
|
||||
for (let i = 1; i <= 100; i++) {
|
||||
histogram.record(i);
|
||||
for (const consumer of consumers) {
|
||||
consumer.pending++;
|
||||
if (i % consumer.interval !== 0) continue;
|
||||
const current = histogram.snapshot();
|
||||
const delta = current.diff(consumer.previous);
|
||||
assert.strictEqual(delta.count, consumer.pending);
|
||||
assert.strictEqual(delta.min, i - consumer.pending + 1);
|
||||
assert.strictEqual(delta.max, i);
|
||||
consumer.total += delta.count;
|
||||
consumer.previous = current;
|
||||
consumer.pending = 0;
|
||||
}
|
||||
}
|
||||
assert.strictEqual(consumers[0].total, 99);
|
||||
assert.strictEqual(consumers[1].total, 100);
|
||||
}
|
||||
|
||||
{
|
||||
const histogram = createHistogram();
|
||||
assert.strictEqual(histogram.resetCount, 0);
|
||||
histogram.record(1);
|
||||
histogram.recordCorrected(100, 10);
|
||||
histogram.add(createHistogram());
|
||||
assert.strictEqual(histogram.resetCount, 0);
|
||||
|
||||
histogram.reset();
|
||||
assert.strictEqual(histogram.resetCount, 1);
|
||||
histogram.record(1);
|
||||
const previous = histogram.snapshot();
|
||||
histogram.reset();
|
||||
assert.strictEqual(histogram.resetCount, 2);
|
||||
assert.strictEqual(previous.resetCount, 1);
|
||||
|
||||
// A reset is detected even when every count has grown past its previous
|
||||
// value since.
|
||||
for (let i = 0; i < 10; i++) histogram.record(1);
|
||||
assert.throws(() => histogram.diff(previous), {
|
||||
code: 'ERR_INVALID_STATE',
|
||||
});
|
||||
|
||||
// subtract() also removes values. Subtracting an empty histogram leaves
|
||||
// every count unchanged.
|
||||
const snapshot = histogram.snapshot();
|
||||
assert.strictEqual(snapshot.resetCount, 2);
|
||||
histogram.subtract(createHistogram());
|
||||
assert.strictEqual(histogram.resetCount, 3);
|
||||
assert.throws(() => histogram.diff(snapshot), {
|
||||
code: 'ERR_INVALID_STATE',
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const histogram = createHistogram();
|
||||
for (const options of [{ lowest: 2 }, { highest: 1000 }, { figures: 2 }]) {
|
||||
assert.throws(() => histogram.diff(createHistogram(options)), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
}
|
||||
|
||||
// Histograms with a different normalizing index offset map values to
|
||||
// different indexes.
|
||||
const data = createHistogram().export();
|
||||
const offset = Buffer.from(data).indexOf(Buffer.from([0x06, 0x00, 0x07, 0x00]));
|
||||
assert.notStrictEqual(offset, -1);
|
||||
data[offset + 3] = 1;
|
||||
assert.throws(() => histogram.diff(importHistogram(data)), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
|
||||
assert.throws(() => histogram.diff.call({}, histogram), {
|
||||
code: 'ERR_INVALID_THIS',
|
||||
});
|
||||
const { get } = Object.getOwnPropertyDescriptor(
|
||||
Object.getPrototypeOf(histogram.snapshot()), 'resetCount');
|
||||
assert.throws(() => get.call({}), { code: 'ERR_INVALID_THIS' });
|
||||
const window = createSlidingWindowHistogram({ chunks: 1, recordsPerChunk: 1 });
|
||||
for (const other of [undefined, null, {}, 1, window]) {
|
||||
assert.throws(() => histogram.diff(other), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const histogram = monitorEventLoopDelay({ samplePerIteration: true });
|
||||
histogram.enable();
|
||||
while (histogram.count < 2) await delay(1);
|
||||
const previous = histogram.snapshot();
|
||||
while (histogram.count < previous.count + 3) await delay(1);
|
||||
|
||||
// Samples are only recorded while the event loop is running.
|
||||
const current = histogram.snapshot();
|
||||
assert.strictEqual(current.diff(previous).count,
|
||||
current.count - previous.count);
|
||||
|
||||
histogram.disable();
|
||||
histogram.reset();
|
||||
assert.strictEqual(histogram.resetCount, 1);
|
||||
assert.throws(() => histogram.diff(previous), {
|
||||
code: 'ERR_INVALID_STATE',
|
||||
});
|
||||
})().then(common.mustCall());
|
||||
+2
@@ -42,6 +42,8 @@ declare namespace InternalPerformanceBinding {
|
||||
ewmaStddev(): number;
|
||||
ewmaErrorRate(): number;
|
||||
snapshot(): Histogram;
|
||||
diff(other: HistogramBase): Histogram;
|
||||
resetCount(): number;
|
||||
}
|
||||
|
||||
interface ELDHistogram extends HistogramBase {
|
||||
|
||||
Reference in new issue
Block a user