From 4a88ddeecad73516c0397f76cd77cc68cf42198f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 30 Jan 2021 09:26:15 -0800 Subject: [PATCH] perf_hooks: introduce createHistogram Adds a new `perf_hooks.createHistogram()` API for creating histogram instances that allow user recording. Makes Histogram instances cloneable via MessagePort. This allows, for instance, an event loop delay monitor to be running on the main thread while the histogram data can be monitored actively from a worker thread. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/37155 Reviewed-By: Matteo Collina --- doc/api/perf_hooks.md | 114 +++++--- doc/api/worker_threads.md | 4 + lib/internal/histogram.js | 141 +++++++-- lib/perf_hooks.js | 29 +- src/env.h | 3 +- src/histogram-inl.h | 43 +-- src/histogram.cc | 315 ++++++++++++++++++--- src/histogram.h | 149 ++++++++-- src/node.cc | 1 + src/node_http2.cc | 1 + src/node_perf.cc | 184 ++---------- src/node_perf.h | 37 +-- src/node_worker.cc | 1 + test/parallel/test-perf-hooks-histogram.js | 70 +++++ tools/doc/type-parser.mjs | 4 + 15 files changed, 774 insertions(+), 322 deletions(-) create mode 100644 test/parallel/test-perf-hooks-histogram.js 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',