diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index 6aa947ccbce78d..6883712c435664 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -651,6 +651,22 @@ performance.mark('test'); performance.mark('meow'); ``` +## `perf_hooks.createHistogram([options])` + + +* `options` {Object} + * `min` {number|bigint} The minimum recordable value. Must be an integer + value greater than 0. **Defaults**: `1`. + * `max` {number|bigint} The maximum recordable value. Must be an integer + value greater than `min`. **Defaults**: `Number.MAX_SAFE_INTEGER`. + * `figures` {number} The number of accuracy digits. Must be a number between + `1` and `5`. **Defaults**: `3`. +* Returns {RecordableHistogram} + +Returns a {RecordableHistogram}. + ## `perf_hooks.monitorEventLoopDelay([options])` -Tracks the event loop delay at a given sampling rate. The constructor of -this class not exposed to users. - -_This property is an extension by Node.js. It is not available in Web browsers._ - -#### `histogram.disable()` - - -* Returns: {boolean} - -Disables the event loop delay sample timer. Returns `true` if the timer was -stopped, `false` if it was already stopped. - -#### `histogram.enable()` +## Class: `Histogram` -* Returns: {boolean} - -Enables the event loop delay sample timer. Returns `true` if the timer was -started, `false` if it was already started. - -#### `histogram.exceeds` +### `histogram.exceeds` @@ -726,7 +718,7 @@ added: v11.10.0 The number of times the event loop delay exceeded the maximum 1 hour event loop delay threshold. -#### `histogram.max` +### `histogram.max` @@ -735,7 +727,7 @@ added: v11.10.0 The maximum recorded event loop delay. -#### `histogram.mean` +### `histogram.mean` @@ -744,7 +736,7 @@ added: v11.10.0 The mean of the recorded event loop delays. -#### `histogram.min` +### `histogram.min` @@ -753,7 +745,7 @@ added: v11.10.0 The minimum recorded event loop delay. -#### `histogram.percentile(percentile)` +### `histogram.percentile(percentile)` @@ -763,7 +755,7 @@ added: v11.10.0 Returns the value at the given percentile. -#### `histogram.percentiles` +### `histogram.percentiles` @@ -772,14 +764,14 @@ added: v11.10.0 Returns a `Map` object detailing the accumulated percentile distribution. -#### `histogram.reset()` +### `histogram.reset()` Resets the collected histogram data. -#### `histogram.stddev` +### `histogram.stddev` @@ -788,6 +780,56 @@ added: v11.10.0 The standard deviation of the recorded event loop delays. +## Class: `IntervalHistogram extends Histogram` + +A `Histogram` that is periodically updated on a given interval. + +### `histogram.disable()` + + +* Returns: {boolean} + +Disables the update interval timer. Returns `true` if the timer was +stopped, `false` if it was already stopped. + +### `histogram.enable()` + + +* Returns: {boolean} + +Enables the update interval timer. Returns `true` if the timer was +started, `false` if it was already started. + +### Cloning an `IntervalHistogram` + +{IntervalHistogram} instances can be cloned via {MessagePort}. On the receiving +end, the histogram is cloned as a plain {Histogram} object that does not +implement the `enable()` and `disable()` methods. + +## Class: `RecordableHistogram extends Histogram` + + +### `histogram.record(val)` + + +* `val` {number|bigint} The amount to record in the histogram. + +### `histogram.recordDelta()` + + +Calculates the amount of time (in nanoseconds) that has passed since the +previous call to `recordDelta()` and records that amount in the histogram. + ## Examples ### Measuring the duration of async operations diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 18517609eeeb46..37f19bc135d826 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -381,6 +381,9 @@ changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/37917 description: Add 'BlockList' to the list of cloneable types. + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/37155 + description: Add 'Histogram' types to the list of cloneable types. - version: v14.5.0 pr-url: https://github.com/nodejs/node/pull/33360 description: Added `KeyObject` to the list of cloneable types. @@ -406,6 +409,7 @@ In particular, the significant differences to `JSON` are: * `value` may contain [`WebAssembly.Module`][] instances. * `value` may not contain native (C++-backed) objects other than: * {FileHandle}s, + * {Histogram}s, * {KeyObject}s, * {MessagePort}s, * {net.BlockList}s, diff --git a/lib/internal/histogram.js b/lib/internal/histogram.js index f599e4b3edb5f5..4a8e51f958f3bb 100644 --- a/lib/internal/histogram.js +++ b/lib/internal/histogram.js @@ -1,40 +1,67 @@ 'use strict'; +const { + NumberIsNaN, + NumberIsInteger, + NumberMAX_SAFE_INTEGER, + ObjectSetPrototypeOf, + SafeMap, + Symbol, + TypeError, +} = primordials; + +const { + Histogram: _Histogram +} = internalBinding('performance'); + const { customInspectSymbol: kInspect, } = require('internal/util'); -const { format } = require('util'); -const { NumberIsNaN, SafeMap, Symbol } = primordials; +const { inspect } = require('util'); const { - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, -} = require('internal/errors').codes; + codes: { + ERR_INVALID_ARG_VALUE, + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + }, +} = require('internal/errors'); const kDestroy = Symbol('kDestroy'); const kHandle = Symbol('kHandle'); +const kMap = Symbol('kMap'); -// Histograms are created internally by Node.js and used to -// record various metrics. This Histogram class provides a -// generally read-only view of the internal histogram. -class Histogram { - #map = new SafeMap(); +const { + kClone, + kDeserialize, + JSTransferable, +} = require('internal/worker/js_transferable'); +class Histogram extends JSTransferable { constructor(internal) { + super(); this[kHandle] = internal; + this[kMap] = new SafeMap(); } - [kInspect]() { - const obj = { + [kInspect](depth, options) { + if (depth < 0) + return this; + + const opts = { + ...options, + depth: options.depth == null ? null : options.depth - 1 + }; + + return `Histogram ${inspect({ min: this.min, max: this.max, mean: this.mean, exceeds: this.exceeds, stddev: this.stddev, percentiles: this.percentiles, - }; - return `Histogram ${format(obj)}`; + }, opts)}`; } get min() { @@ -68,9 +95,9 @@ class Histogram { } get percentiles() { - this.#map.clear(); - this[kHandle]?.percentiles(this.#map); - return this.#map; + this[kMap].clear(); + this[kHandle]?.percentiles(this[kMap]); + return this[kMap]; } reset() { @@ -80,10 +107,90 @@ class Histogram { [kDestroy]() { this[kHandle] = undefined; } + + [kClone]() { + const handle = this[kHandle]; + return { + data: { handle }, + deserializeInfo: 'internal/histogram:InternalHistogram' + }; + } + + [kDeserialize]({ handle }) { + this[kHandle] = handle; + } +} + +class RecordableHistogram extends Histogram { + constructor() { + // eslint-disable-next-line no-restricted-syntax + throw new TypeError('illegal constructor'); + } + + record(val) { + if (typeof val === 'bigint') { + this[kHandle]?.record(val); + return; + } + + if (!NumberIsInteger(val)) + throw new ERR_INVALID_ARG_TYPE('val', ['integer', 'bigint'], val); + + if (val < 1 || val > NumberMAX_SAFE_INTEGER) + throw new ERR_OUT_OF_RANGE('val', 'a safe integer greater than 0', val); + + this[kHandle]?.record(val); + } + + recordDelta() { + this[kHandle]?.recordDelta(); + } + + [kClone]() { + const handle = this[kHandle]; + return { + data: { handle }, + deserializeInfo: 'internal/histogram:InternalRecordableHistogram' + }; + } +} + +class InternalHistogram extends JSTransferable { + constructor(handle) { + super(); + this[kHandle] = handle; + this[kMap] = new SafeMap(); + } +} + +class InternalRecordableHistogram extends JSTransferable { + constructor(handle) { + super(); + this[kHandle] = handle; + this[kMap] = new SafeMap(); + } +} + +InternalHistogram.prototype.constructor = Histogram; +ObjectSetPrototypeOf( + InternalHistogram.prototype, + Histogram.prototype); + +InternalRecordableHistogram.prototype.constructor = RecordableHistogram; +ObjectSetPrototypeOf( + InternalRecordableHistogram.prototype, + RecordableHistogram.prototype); + +function createHistogram() { + return new InternalRecordableHistogram(new _Histogram()); } module.exports = { Histogram, + RecordableHistogram, + InternalHistogram, + InternalRecordableHistogram, kDestroy, kHandle, + createHistogram, }; diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js index 2b9baa043ab85e..b5544db68ff3de 100644 --- a/lib/perf_hooks.js +++ b/lib/perf_hooks.js @@ -16,6 +16,7 @@ const { ObjectKeys, SafeSet, Symbol, + TypeError, } = primordials; const { @@ -67,6 +68,7 @@ const { const { Histogram, + createHistogram, kHandle, } = require('internal/histogram'); @@ -82,6 +84,7 @@ const kInsertEntry = Symbol('insert-entry'); const kGetEntries = Symbol('get-entries'); const kIndex = Symbol('index'); const kMarks = Symbol('marks'); +const kEnabled = Symbol('kEnabled'); const observers = {}; const observerableTypes = [ @@ -636,9 +639,26 @@ function sortedInsert(list, entry) { } class ELDHistogram extends Histogram { - constructor(i) { super(i); } // eslint-disable-line no-useless-constructor - enable() { return this[kHandle].enable(); } - disable() { return this[kHandle].disable(); } + constructor(i) { + if (!(i instanceof _ELDHistogram)) { + // eslint-disable-next-line no-restricted-syntax + throw new TypeError('illegal constructor'); + } + super(i); + this[kEnabled] = false; + } + enable() { + if (this[kEnabled]) return false; + this[kEnabled] = true; + this[kHandle].start(); + return true; + } + disable() { + if (!this[kEnabled]) return false; + this[kEnabled] = false; + this[kHandle].stop(); + return true; + } } function monitorEventLoopDelay(options = {}) { @@ -659,7 +679,8 @@ function monitorEventLoopDelay(options = {}) { module.exports = { performance, PerformanceObserver, - monitorEventLoopDelay + monitorEventLoopDelay, + createHistogram, }; ObjectDefineProperty(module.exports, 'constants', { diff --git a/src/env.h b/src/env.h index d4d9c29118a9cb..10c4e0fbf72958 100644 --- a/src/env.h +++ b/src/env.h @@ -421,11 +421,12 @@ constexpr size_t kFsStatsBufferLength = V(filehandlereadwrap_template, v8::ObjectTemplate) \ V(fsreqpromise_constructor_template, v8::ObjectTemplate) \ V(handle_wrap_ctor_template, v8::FunctionTemplate) \ - V(histogram_instance_template, v8::ObjectTemplate) \ + V(histogram_ctor_template, v8::FunctionTemplate) \ V(http2settings_constructor_template, v8::ObjectTemplate) \ V(http2stream_constructor_template, v8::ObjectTemplate) \ V(http2ping_constructor_template, v8::ObjectTemplate) \ V(i18n_converter_template, v8::ObjectTemplate) \ + V(intervalhistogram_constructor_template, v8::FunctionTemplate) \ V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \ V(message_port_constructor_template, v8::FunctionTemplate) \ V(microtask_queue_ctor_template, v8::FunctionTemplate) \ diff --git a/src/histogram-inl.h b/src/histogram-inl.h index 58911dae8f2dae..18a1668512e1ce 100644 --- a/src/histogram-inl.h +++ b/src/histogram-inl.h @@ -10,30 +10,34 @@ namespace node { void Histogram::Reset() { + Mutex::ScopedLock lock(mutex_); hdr_reset(histogram_.get()); -} - -bool Histogram::Record(int64_t value) { - return hdr_record_value(histogram_.get(), value); + exceeds_ = 0; + prev_ = 0; } int64_t Histogram::Min() { + Mutex::ScopedLock lock(mutex_); return hdr_min(histogram_.get()); } int64_t Histogram::Max() { + Mutex::ScopedLock lock(mutex_); return hdr_max(histogram_.get()); } double Histogram::Mean() { + Mutex::ScopedLock lock(mutex_); return hdr_mean(histogram_.get()); } double Histogram::Stddev() { + Mutex::ScopedLock lock(mutex_); return hdr_stddev(histogram_.get()); } double Histogram::Percentile(double percentile) { + Mutex::ScopedLock lock(mutex_); CHECK_GT(percentile, 0); CHECK_LE(percentile, 100); return static_cast( @@ -42,6 +46,7 @@ double Histogram::Percentile(double percentile) { template void Histogram::Percentiles(Iterator&& fn) { + Mutex::ScopedLock lock(mutex_); hdr_iter iter; hdr_iter_percentile_init(&iter, histogram_.get(), 1); while (hdr_iter_next(&iter)) { @@ -51,29 +56,29 @@ void Histogram::Percentiles(Iterator&& fn) { } } -bool HistogramBase::RecordDelta() { +bool Histogram::Record(int64_t value) { + Mutex::ScopedLock lock(mutex_); + return hdr_record_value(histogram_.get(), value); +} + +uint64_t Histogram::RecordDelta() { + Mutex::ScopedLock lock(mutex_); uint64_t time = uv_hrtime(); - bool ret = true; + uint64_t delta = 0; if (prev_ > 0) { - int64_t delta = time - prev_; + delta = time - prev_; if (delta > 0) { - ret = Record(delta); - TraceDelta(delta); - if (!ret) { - if (exceeds_ < 0xFFFFFFFF) - exceeds_++; - TraceExceeds(delta); - } + if (!hdr_record_value(histogram_.get(), delta) && exceeds_ < 0xFFFFFFFF) + exceeds_++; } } prev_ = time; - return ret; + return delta; } -void HistogramBase::ResetState() { - Reset(); - exceeds_ = 0; - prev_ = 0; +size_t Histogram::GetMemorySize() const { + Mutex::ScopedLock lock(mutex_); + return hdr_get_memory_size(histogram_.get()); } } // namespace node diff --git a/src/histogram.cc b/src/histogram.cc index 8d1eb77b1bc88e..d21cf2883a0ca8 100644 --- a/src/histogram.cc +++ b/src/histogram.cc @@ -1,15 +1,17 @@ #include "histogram.h" // NOLINT(build/include_inline) #include "histogram-inl.h" +#include "base_object-inl.h" #include "memory_tracker-inl.h" - +#include "node_errors.h" namespace node { +using v8::BigInt; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Local; using v8::Map; using v8::Number; -using v8::ObjectTemplate; +using v8::Object; using v8::String; using v8::Value; @@ -19,71 +21,88 @@ Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { histogram_.reset(histogram); } +void Histogram::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackFieldWithSize("histogram", GetMemorySize()); +} + +HistogramImpl::HistogramImpl(int64_t lowest, int64_t highest, int figures) + : histogram_(new Histogram(lowest, highest, figures)) {} + +HistogramImpl::HistogramImpl(std::shared_ptr histogram) + : histogram_(std::move(histogram)) {} + HistogramBase::HistogramBase( Environment* env, - v8::Local wrap, + Local wrap, int64_t lowest, int64_t highest, int figures) : BaseObject(env, wrap), - Histogram(lowest, highest, figures) { + HistogramImpl(lowest, highest, figures) { + MakeWeak(); +} + +HistogramBase::HistogramBase( + Environment* env, + Local wrap, + std::shared_ptr histogram) + : BaseObject(env, wrap), + HistogramImpl(std::move(histogram)) { MakeWeak(); } void HistogramBase::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackFieldWithSize("histogram", GetMemorySize()); + tracker->TrackField("histogram", histogram()); } void HistogramBase::GetMin(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Min()); + double value = static_cast((*histogram)->Min()); args.GetReturnValue().Set(value); } void HistogramBase::GetMax(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Max()); + double value = static_cast((*histogram)->Max()); args.GetReturnValue().Set(value); } void HistogramBase::GetMean(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Mean()); + args.GetReturnValue().Set((*histogram)->Mean()); } void HistogramBase::GetExceeds(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Exceeds()); + double value = static_cast((*histogram)->Exceeds()); args.GetReturnValue().Set(value); } void HistogramBase::GetStddev(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Stddev()); + args.GetReturnValue().Set((*histogram)->Stddev()); } -void HistogramBase::GetPercentile( - const FunctionCallbackInfo& args) { +void HistogramBase::GetPercentile(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); CHECK(args[0]->IsNumber()); double percentile = args[0].As()->Value(); - args.GetReturnValue().Set(histogram->Percentile(percentile)); + args.GetReturnValue().Set((*histogram)->Percentile(percentile)); } -void HistogramBase::GetPercentiles( - const FunctionCallbackInfo& args) { +void HistogramBase::GetPercentiles(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); CHECK(args[0]->IsMap()); Local map = args[0].As(); - histogram->Percentiles([map, env](double key, double value) { + (*histogram)->Percentiles([map, env](double key, double value) { map->Set( env->context(), Number::New(env->isolate(), key), @@ -94,48 +113,254 @@ void HistogramBase::GetPercentiles( void HistogramBase::DoReset(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - histogram->ResetState(); + (*histogram)->Reset(); +} + +void HistogramBase::RecordDelta(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + (*histogram)->RecordDelta(); } -BaseObjectPtr HistogramBase::New( +void HistogramBase::Record(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_IMPLIES(!args[0]->IsNumber(), args[0]->IsBigInt()); + bool lossless = true; + int64_t value = args[0]->IsBigInt() + ? args[0].As()->Int64Value(&lossless) + : static_cast(args[0].As()->Value()); + if (!lossless || value < 1) + return THROW_ERR_OUT_OF_RANGE(env, "value is out of range"); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + (*histogram)->Record(value); +} + +BaseObjectPtr HistogramBase::Create( Environment* env, int64_t lowest, int64_t highest, int figures) { - CHECK_LE(lowest, highest); - CHECK_GT(figures, 0); - v8::Local obj; - auto tmpl = env->histogram_instance_template(); - if (!tmpl->NewInstance(env->context()).ToLocal(&obj)) - return {}; - - return MakeDetachedBaseObject( + Local obj; + if (!GetConstructorTemplate(env) + ->InstanceTemplate() + ->NewInstance(env->context()).ToLocal(&obj)) { + return BaseObjectPtr(); + } + + return MakeBaseObject( env, obj, lowest, highest, figures); } -void HistogramBase::Initialize(Environment* env) { - // Guard against multiple initializations - if (!env->histogram_instance_template().IsEmpty()) - return; +BaseObjectPtr HistogramBase::Create( + Environment* env, + std::shared_ptr histogram) { + Local obj; + if (!GetConstructorTemplate(env) + ->InstanceTemplate() + ->NewInstance(env->context()).ToLocal(&obj)) { + return BaseObjectPtr(); + } + return MakeBaseObject(env, obj, std::move(histogram)); +} + +void HistogramBase::New(const FunctionCallbackInfo& args) { + CHECK(args.IsConstructCall()); + Environment* env = Environment::GetCurrent(args); + new HistogramBase(env, args.This()); +} + +Local HistogramBase::GetConstructorTemplate( + Environment* env) { + Local tmpl = env->histogram_ctor_template(); + if (tmpl.IsEmpty()) { + tmpl = env->NewFunctionTemplate(New); + Local classname = + FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); + tmpl->SetClassName(classname); + tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); + + tmpl->InstanceTemplate()->SetInternalFieldCount( + HistogramBase::kInternalFieldCount); + env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds); + env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin); + env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax); + env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean); + env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev); + env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile); + env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles); + env->SetProtoMethod(tmpl, "reset", DoReset); + env->SetProtoMethod(tmpl, "record", Record); + env->SetProtoMethod(tmpl, "recordDelta", RecordDelta); + env->set_histogram_ctor_template(tmpl); + } + return tmpl; +} + +void HistogramBase::Initialize(Environment* env, Local target) { + env->SetConstructorFunction(target, "Histogram", GetConstructorTemplate(env)); +} + +BaseObjectPtr HistogramBase::HistogramTransferData::Deserialize( + Environment* env, + v8::Local context, + std::unique_ptr self) { + return Create(env, std::move(histogram_)); +} + +std::unique_ptr HistogramBase::CloneForMessaging() const { + return std::make_unique(this); +} + +void HistogramBase::HistogramTransferData::MemoryInfo( + MemoryTracker* tracker) const { + tracker->TrackField("histogram", histogram_); +} + +Local IntervalHistogram::GetConstructorTemplate( + Environment* env) { + Local tmpl = env->intervalhistogram_constructor_template(); + if (tmpl.IsEmpty()) { + tmpl = FunctionTemplate::New(env->isolate()); + tmpl->Inherit(HandleWrap::GetConstructorTemplate(env)); + tmpl->InstanceTemplate()->SetInternalFieldCount( + HistogramBase::kInternalFieldCount); + env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds); + env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin); + env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax); + env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean); + env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev); + env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile); + env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles); + env->SetProtoMethod(tmpl, "reset", DoReset); + env->SetProtoMethod(tmpl, "start", Start); + env->SetProtoMethod(tmpl, "stop", Stop); + env->set_intervalhistogram_constructor_template(tmpl); + } + return tmpl; +} + +IntervalHistogram::IntervalHistogram( + Environment* env, + Local wrap, + AsyncWrap::ProviderType type, + int32_t interval, + int64_t lowest, + int64_t highest, + int figures) + : HandleWrap( + env, + wrap, + reinterpret_cast(&timer_), + type), + HistogramImpl(lowest, highest, figures), + interval_(interval) { + MakeWeak(); + uv_timer_init(env->event_loop(), &timer_); +} + +void IntervalHistogram::TimerCB(uv_timer_t* handle) { + IntervalHistogram* histogram = + ContainerOf(&IntervalHistogram::timer_, handle); + histogram->OnInterval(); +} + +void IntervalHistogram::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("histogram", histogram()); +} + +void IntervalHistogram::OnStart(StartFlags flags) { + if (enabled_ || IsHandleClosing()) return; + enabled_ = true; + if (flags == StartFlags::RESET) + histogram()->Reset(); + uv_timer_start(&timer_, TimerCB, interval_, interval_); + uv_unref(reinterpret_cast(&timer_)); +} + +void IntervalHistogram::OnStop() { + if (!enabled_ || IsHandleClosing()) return; + enabled_ = false; + uv_timer_stop(&timer_); +} + +void IntervalHistogram::Start(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + histogram->OnStart(args[0]->IsTrue() ? StartFlags::RESET : StartFlags::NONE); +} + +void IntervalHistogram::Stop(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + histogram->OnStop(); +} + +void IntervalHistogram::GetMin(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast((*histogram)->Min()); + args.GetReturnValue().Set(value); +} + +void IntervalHistogram::GetMax(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast((*histogram)->Max()); + args.GetReturnValue().Set(value); +} + +void IntervalHistogram::GetMean(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set((*histogram)->Mean()); +} + +void IntervalHistogram::GetExceeds(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast((*histogram)->Exceeds()); + args.GetReturnValue().Set(value); +} - Local histogram = FunctionTemplate::New(env->isolate()); - Local classname = FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); - histogram->SetClassName(classname); +void IntervalHistogram::GetStddev(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set((*histogram)->Stddev()); +} - Local histogramt = - histogram->InstanceTemplate(); +void IntervalHistogram::GetPercentile(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As()->Value(); + args.GetReturnValue().Set((*histogram)->Percentile(percentile)); +} - histogramt->SetInternalFieldCount(1); - env->SetProtoMethod(histogram, "exceeds", HistogramBase::GetExceeds); - env->SetProtoMethod(histogram, "min", HistogramBase::GetMin); - env->SetProtoMethod(histogram, "max", HistogramBase::GetMax); - env->SetProtoMethod(histogram, "mean", HistogramBase::GetMean); - env->SetProtoMethod(histogram, "stddev", HistogramBase::GetStddev); - env->SetProtoMethod(histogram, "percentile", HistogramBase::GetPercentile); - env->SetProtoMethod(histogram, "percentiles", HistogramBase::GetPercentiles); - env->SetProtoMethod(histogram, "reset", HistogramBase::DoReset); +void IntervalHistogram::GetPercentiles( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local map = args[0].As(); + (*histogram)->Percentiles([map, env](double key, double value) { + map->Set( + env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), value)).IsEmpty(); + }); +} + +void IntervalHistogram::DoReset(const FunctionCallbackInfo& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + (*histogram)->Reset(); +} - env->set_histogram_instance_template(histogramt); +std::unique_ptr +IntervalHistogram::CloneForMessaging() const { + return std::make_unique(histogram()); } } // namespace node diff --git a/src/histogram.h b/src/histogram.h index e92c31c4724ac6..8c164f54cfd9ed 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -5,20 +5,25 @@ #include "hdr_histogram.h" #include "base_object.h" +#include "memory_tracker.h" +#include "node_messaging.h" #include "util.h" +#include "v8.h" +#include "uv.h" #include #include #include +#include namespace node { constexpr int kDefaultHistogramFigures = 3; -class Histogram { +class Histogram : public MemoryRetainer { public: Histogram( - int64_t lowest = std::numeric_limits::min(), + int64_t lowest = 1, int64_t highest = std::numeric_limits::max(), int figures = kDefaultHistogramFigures); virtual ~Histogram() = default; @@ -30,32 +35,61 @@ class Histogram { inline double Mean(); inline double Stddev(); inline double Percentile(double percentile); + inline int64_t Exceeds() const { return exceeds_; } + + inline uint64_t RecordDelta(); // Iterator is a function type that takes two doubles as argument, one for // percentile and one for the value at that percentile. template inline void Percentiles(Iterator&& fn); - size_t GetMemorySize() const { - return hdr_get_memory_size(histogram_.get()); - } + inline size_t GetMemorySize() const; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(Histogram) + SET_SELF_SIZE(Histogram) private: using HistogramPointer = DeleteFnPtr; HistogramPointer histogram_; + int64_t exceeds_ = 0; + uint64_t prev_ = 0; + + Mutex mutex_; }; -class HistogramBase : public BaseObject, public Histogram { +class HistogramImpl { public: - virtual ~HistogramBase() = default; + HistogramImpl(int64_t lowest, int64_t highest, int figures); + explicit HistogramImpl(std::shared_ptr histogram); - virtual void TraceDelta(int64_t delta) {} - virtual void TraceExceeds(int64_t delta) {} + Histogram* operator->() { return histogram_.get(); } - inline bool RecordDelta(); - inline void ResetState(); + protected: + const std::shared_ptr& histogram() const { return histogram_; } - int64_t Exceeds() const { return exceeds_; } + private: + std::shared_ptr histogram_; +}; + +class HistogramBase : public BaseObject, public HistogramImpl { + public: + static v8::Local GetConstructorTemplate( + Environment* env); + static void Initialize(Environment* env, v8::Local target); + + static BaseObjectPtr Create( + Environment* env, + int64_t lowest = 1, + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + + static BaseObjectPtr Create( + Environment* env, + std::shared_ptr histogram); + + static void New(const v8::FunctionCallbackInfo& args); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(HistogramBase) @@ -71,24 +105,103 @@ class HistogramBase : public BaseObject, public Histogram { static void GetPercentiles( const v8::FunctionCallbackInfo& args); static void DoReset(const v8::FunctionCallbackInfo& args); - static void Initialize(Environment* env); + static void Record(const v8::FunctionCallbackInfo& args); + static void RecordDelta(const v8::FunctionCallbackInfo& args); - static BaseObjectPtr New( + HistogramBase( Environment* env, - int64_t lowest = std::numeric_limits::min(), + v8::Local wrap, + int64_t lowest = 1, int64_t highest = std::numeric_limits::max(), int figures = kDefaultHistogramFigures); HistogramBase( Environment* env, v8::Local wrap, - int64_t lowest = std::numeric_limits::min(), + std::shared_ptr histogram); + + TransferMode GetTransferMode() const override { + return TransferMode::kCloneable; + } + std::unique_ptr CloneForMessaging() const override; + + class HistogramTransferData : public worker::TransferData { + public: + explicit HistogramTransferData(const HistogramBase* histogram) + : histogram_(histogram->histogram()) {} + + explicit HistogramTransferData(std::shared_ptr histogram) + : histogram_(std::move(histogram)) {} + + BaseObjectPtr Deserialize( + Environment* env, + v8::Local context, + std::unique_ptr self) override; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(HistogramTransferData) + SET_SELF_SIZE(HistogramTransferData) + + private: + std::shared_ptr histogram_; + }; +}; + +class IntervalHistogram : public HandleWrap, public HistogramImpl { + public: + enum class StartFlags { + NONE, + RESET + }; + + static v8::Local GetConstructorTemplate( + Environment* env); + + static BaseObjectPtr Create( + Environment* env, + int64_t lowest = 1, + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + + virtual void OnInterval() = 0; + + void MemoryInfo(MemoryTracker* tracker) const override; + + IntervalHistogram( + Environment* env, + v8::Local wrap, + AsyncWrap::ProviderType type, + int32_t interval, + int64_t lowest = 1, int64_t highest = std::numeric_limits::max(), int figures = kDefaultHistogramFigures); + static void GetMin(const v8::FunctionCallbackInfo& args); + static void GetMax(const v8::FunctionCallbackInfo& args); + static void GetMean(const v8::FunctionCallbackInfo& args); + static void GetExceeds(const v8::FunctionCallbackInfo& args); + static void GetStddev(const v8::FunctionCallbackInfo& args); + static void GetPercentile( + const v8::FunctionCallbackInfo& args); + static void GetPercentiles( + const v8::FunctionCallbackInfo& args); + static void DoReset(const v8::FunctionCallbackInfo& args); + static void Start(const v8::FunctionCallbackInfo& args); + static void Stop(const v8::FunctionCallbackInfo& args); + + TransferMode GetTransferMode() const override { + return TransferMode::kCloneable; + } + std::unique_ptr CloneForMessaging() const override; + private: - int64_t exceeds_ = 0; - uint64_t prev_ = 0; + static void TimerCB(uv_timer_t* handle); + void OnStart(StartFlags flags = StartFlags::RESET); + void OnStop(); + + bool enabled_ = false; + int32_t interval_ = 0; + uv_timer_t timer_; }; } // namespace node diff --git a/src/node.cc b/src/node.cc index 19c2f6ad3b3cd7..7588e3924de27a 100644 --- a/src/node.cc +++ b/src/node.cc @@ -26,6 +26,7 @@ #include "debug_utils-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" +#include "histogram-inl.h" #include "node_binding.h" #include "node_errors.h" #include "node_internals.h" diff --git a/src/node_http2.cc b/src/node_http2.cc index 9c077bea1e5d38..83e8b5fc6bfee5 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -2,6 +2,7 @@ #include "allocated_buffer-inl.h" #include "aliased_struct-inl.h" #include "debug_utils-inl.h" +#include "histogram-inl.h" #include "memory_tracker-inl.h" #include "node.h" #include "node_buffer.h" diff --git a/src/node_perf.cc b/src/node_perf.cc index aa6db069fa0c26..2f6fab9242f656 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -1,4 +1,5 @@ #include "aliased_buffer.h" +#include "histogram-inl.h" #include "memory_tracker-inl.h" #include "node_internals.h" #include "node_perf.h" @@ -19,10 +20,10 @@ using v8::FunctionTemplate; using v8::GCCallbackFlags; using v8::GCType; using v8::HandleScope; +using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Local; -using v8::Map; using v8::MaybeLocal; using v8::Number; using v8::Object; @@ -401,156 +402,45 @@ void LoopIdleTime(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(1.0 * idle_time / 1e6); } - // Event Loop Timing Histogram -namespace { -static void ELDHistogramMin(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Min()); - args.GetReturnValue().Set(value); -} - -static void ELDHistogramMax(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Max()); - args.GetReturnValue().Set(value); -} - -static void ELDHistogramMean(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Mean()); -} - -static void ELDHistogramExceeds(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Exceeds()); - args.GetReturnValue().Set(value); -} - -static void ELDHistogramStddev(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Stddev()); -} - -static void ELDHistogramPercentile(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - CHECK(args[0]->IsNumber()); - double percentile = args[0].As()->Value(); - args.GetReturnValue().Set(histogram->Percentile(percentile)); -} - -static void ELDHistogramPercentiles(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - CHECK(args[0]->IsMap()); - Local map = args[0].As(); - histogram->Percentiles([&](double key, double value) { - map->Set(env->context(), - Number::New(env->isolate(), key), - Number::New(env->isolate(), value)).IsEmpty(); - }); -} - -static void ELDHistogramEnable(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Enable()); -} - -static void ELDHistogramDisable(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Disable()); -} - -static void ELDHistogramReset(const FunctionCallbackInfo& args) { - ELDHistogram* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - histogram->ResetState(); -} - -static void ELDHistogramNew(const FunctionCallbackInfo& args) { +void ELDHistogram::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args.IsConstructCall()); - int32_t resolution = args[0]->IntegerValue(env->context()).FromJust(); + int32_t resolution = args[0].As()->Value(); CHECK_GT(resolution, 0); new ELDHistogram(env, args.This(), resolution); } -} // namespace + +void ELDHistogram::Initialize(Environment* env, Local target) { + Local tmpl = env->NewFunctionTemplate(New); + tmpl->Inherit(IntervalHistogram::GetConstructorTemplate(env)); + tmpl->InstanceTemplate()->SetInternalFieldCount( + ELDHistogram::kInternalFieldCount); + env->SetConstructorFunction(target, "ELDHistogram", tmpl); +} ELDHistogram::ELDHistogram( Environment* env, Local wrap, - int32_t resolution) : HandleWrap(env, - wrap, - reinterpret_cast(&timer_), - AsyncWrap::PROVIDER_ELDHISTOGRAM), - Histogram(1, 3.6e12), - resolution_(resolution) { - MakeWeak(); - uv_timer_init(env->event_loop(), &timer_); -} - -void ELDHistogram::DelayIntervalCallback(uv_timer_t* req) { - ELDHistogram* histogram = ContainerOf(&ELDHistogram::timer_, req); - histogram->RecordDelta(); + int32_t interval) + : IntervalHistogram( + env, + wrap, + AsyncWrap::PROVIDER_ELDHISTOGRAM, + interval, 1, 3.6e12, 3) {} + +void ELDHistogram::OnInterval() { + uint64_t delta = histogram()->RecordDelta(); TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "min", histogram->Min()); + "delay", delta); TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "max", histogram->Max()); + "min", histogram()->Min()); TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "mean", histogram->Mean()); + "max", histogram()->Max()); TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "stddev", histogram->Stddev()); -} - -bool ELDHistogram::RecordDelta() { - uint64_t time = uv_hrtime(); - bool ret = true; - if (prev_ > 0) { - int64_t delta = time - prev_; - if (delta > 0) { - ret = Record(delta); - TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "delay", delta); - if (!ret) { - if (exceeds_ < 0xFFFFFFFF) - exceeds_++; - ProcessEmitWarning( - env(), - "Event loop delay exceeded 1 hour: %" PRId64 " nanoseconds", - delta); - } - } - } - prev_ = time; - return ret; -} - -bool ELDHistogram::Enable() { - if (enabled_ || IsHandleClosing()) return false; - enabled_ = true; - prev_ = 0; - uv_timer_start(&timer_, - DelayIntervalCallback, - resolution_, - resolution_); - uv_unref(reinterpret_cast(&timer_)); - return true; -} - -bool ELDHistogram::Disable() { - if (!enabled_ || IsHandleClosing()) return false; - enabled_ = false; - uv_timer_stop(&timer_); - return true; + "mean", histogram()->Mean()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "stddev", histogram()->Stddev()); } void Initialize(Local target, @@ -643,24 +533,8 @@ void Initialize(Local target, constants, attr).ToChecked(); - Local eldh_classname = FIXED_ONE_BYTE_STRING(isolate, "ELDHistogram"); - Local eldh = - env->NewFunctionTemplate(ELDHistogramNew); - eldh->SetClassName(eldh_classname); - eldh->InstanceTemplate()->SetInternalFieldCount( - ELDHistogram::kInternalFieldCount); - eldh->Inherit(BaseObject::GetConstructorTemplate(env)); - env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds); - env->SetProtoMethod(eldh, "min", ELDHistogramMin); - env->SetProtoMethod(eldh, "max", ELDHistogramMax); - env->SetProtoMethod(eldh, "mean", ELDHistogramMean); - env->SetProtoMethod(eldh, "stddev", ELDHistogramStddev); - env->SetProtoMethod(eldh, "percentile", ELDHistogramPercentile); - env->SetProtoMethod(eldh, "percentiles", ELDHistogramPercentiles); - env->SetProtoMethod(eldh, "enable", ELDHistogramEnable); - env->SetProtoMethod(eldh, "disable", ELDHistogramDisable); - env->SetProtoMethod(eldh, "reset", ELDHistogramReset); - env->SetConstructorFunction(target, eldh_classname, eldh); + HistogramBase::Initialize(env, target); + ELDHistogram::Initialize(env, target); } } // namespace performance diff --git a/src/node_perf.h b/src/node_perf.h index a8a913bddeaad0..ddd99b33040381 100644 --- a/src/node_perf.h +++ b/src/node_perf.h @@ -6,7 +6,7 @@ #include "node.h" #include "node_perf_common.h" #include "base_object-inl.h" -#include "histogram-inl.h" +#include "histogram.h" #include "v8.h" #include "uv.h" @@ -140,37 +140,20 @@ class GCPerformanceEntry : public PerformanceEntry { PerformanceGCFlags gcflags_; }; -class ELDHistogram : public HandleWrap, public Histogram { +class ELDHistogram : public IntervalHistogram { public: - ELDHistogram(Environment* env, - v8::Local wrap, - int32_t resolution); - - bool RecordDelta(); - bool Enable(); - bool Disable(); - void ResetState() { - Reset(); - exceeds_ = 0; - prev_ = 0; - } - int64_t Exceeds() const { return exceeds_; } + static void Initialize(Environment* env, v8::Local target); + static void New(const v8::FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize("histogram", GetMemorySize()); - } + ELDHistogram( + Environment* env, + v8::Local wrap, + int32_t interval); + + void OnInterval() override; SET_MEMORY_INFO_NAME(ELDHistogram) SET_SELF_SIZE(ELDHistogram) - - private: - static void DelayIntervalCallback(uv_timer_t* req); - - bool enabled_ = false; - int32_t resolution_ = 0; - int64_t exceeds_ = 0; - uint64_t prev_ = 0; - uv_timer_t timer_; }; } // namespace performance diff --git a/src/node_worker.cc b/src/node_worker.cc index 4167e285683191..6c1e7b99d10235 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -1,5 +1,6 @@ #include "node_worker.h" #include "debug_utils-inl.h" +#include "histogram-inl.h" #include "memory_tracker-inl.h" #include "node_errors.h" #include "node_buffer.h" diff --git a/test/parallel/test-perf-hooks-histogram.js b/test/parallel/test-perf-hooks-histogram.js new file mode 100644 index 00000000000000..c9ae957bb42411 --- /dev/null +++ b/test/parallel/test-perf-hooks-histogram.js @@ -0,0 +1,70 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + createHistogram, + monitorEventLoopDelay, +} = require('perf_hooks'); +const { MessageChannel } = require('worker_threads'); + +{ + const h = createHistogram(); + + assert.strictEqual(h.min, 9223372036854776000); + assert.strictEqual(h.max, 0); + assert.strictEqual(h.exceeds, 0); + assert(Number.isNaN(h.mean)); + assert(Number.isNaN(h.stddev)); + + h.record(1); + + [false, '', {}, undefined, null].forEach((i) => { + assert.throws(() => h.record(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + assert.throws(() => h.record(0, Number.MAX_SAFE_INTEGER + 1), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.strictEqual(h.min, 1); + assert.strictEqual(h.max, 1); + assert.strictEqual(h.exceeds, 0); + assert.strictEqual(h.mean, 1); + assert.strictEqual(h.stddev, 0); + + assert.strictEqual(h.percentile(1), 1); + assert.strictEqual(h.percentile(100), 1); + + const mc = new MessageChannel(); + mc.port1.onmessage = common.mustCall(({ data }) => { + assert.strictEqual(h.min, 1); + assert.strictEqual(h.max, 1); + assert.strictEqual(h.exceeds, 0); + assert.strictEqual(h.mean, 1); + assert.strictEqual(h.stddev, 0); + + data.record(2n); + data.recordDelta(); + + assert.strictEqual(h.max, 2); + + mc.port1.close(); + }); + mc.port2.postMessage(h); +} + +{ + const e = monitorEventLoopDelay(); + e.enable(); + const mc = new MessageChannel(); + mc.port1.onmessage = common.mustCall(({ data }) => { + assert(typeof data.min, 'number'); + assert(data.min > 0); + assert.strictEqual(data.disable, undefined); + assert.strictEqual(data.enable, undefined); + mc.port1.close(); + }); + setTimeout(() => mc.port2.postMessage(e), 100); +} diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index 72a16a44055c64..e49f6898bba3eb 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -133,6 +133,10 @@ const customTypesMap = { 'os.constants.dlopen': 'os.html#os_dlopen_constants', 'Histogram': 'perf_hooks.html#perf_hooks_class_histogram', + 'IntervalHistogram': + 'perf_hooks.html#perf_hooks_class_intervalhistogram_extends_histogram', + 'RecordableHistogram': + 'perf_hooks.html#perf_hooks_class_recordablehistogram_extends_histogram', 'PerformanceEntry': 'perf_hooks.html#perf_hooks_class_performanceentry', 'PerformanceNodeTiming': 'perf_hooks.html#perf_hooks_class_performancenodetiming',