diff options
author | James M Snell <jasnell@gmail.com> | 2021-12-11 16:59:48 -0800 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2021-12-19 09:56:36 -0800 |
commit | 23637e9a3b208892449268290143dad4391fee45 (patch) | |
tree | c8f83283bb0d357b7db22298e217cbe5e877a656 | |
parent | 665b404e6512c6fdfe811e793d4f14bf8f8a7d36 (diff) | |
download | node-new-23637e9a3b208892449268290143dad4391fee45.tar.gz |
perf_hooks: multiple fixes for Histogram
* The createHistogram(options) options weren't actually implemented
* Add a new count property that tracks the number of samples
* Adds BigInt options for relevant properties
* Adds add(other) method for RecordableHistogram
* Cleans up and expands tests
* Eliminates unnecessary ELDHistogram native class
* Improve/Simplify histogram transfer impl
Signed-off-by: James M Snell <jasnell@gmail.com>
perf_hooks: simplify Histogram constructor options
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/41153
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
-rw-r--r-- | doc/api/perf_hooks.md | 89 | ||||
-rw-r--r-- | lib/internal/histogram.js | 259 | ||||
-rw-r--r-- | lib/internal/perf/event_loop_delay.js | 41 | ||||
-rw-r--r-- | src/histogram-inl.h | 49 | ||||
-rw-r--r-- | src/histogram.cc | 280 | ||||
-rw-r--r-- | src/histogram.h | 83 | ||||
-rw-r--r-- | src/node_perf.cc | 67 | ||||
-rw-r--r-- | src/node_perf.h | 17 | ||||
-rw-r--r-- | test/parallel/test-perf-hooks-histogram.js | 133 |
9 files changed, 807 insertions, 211 deletions
diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index 34d2a58bd0..d3978958c5 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -817,10 +817,11 @@ added: --> * `options` {Object} - * `min` {number|bigint} The minimum recordable value. Must be an integer + * `lowest` {number|bigint} The lowest discernible value. Must be an integer value greater than 0. **Default:** `1`. - * `max` {number|bigint} The maximum recordable value. Must be an integer - value greater than `min`. **Default:** `Number.MAX_SAFE_INTEGER`. + * `highest` {number|bigint} The highest recordable value. Must be an integer + value that is equal to or greater than two times `min`. + **Default:** `Number.MAX_SAFE_INTEGER`. * `figures` {number} The number of accuracy digits. Must be a number between `1` and `5`. **Default:** `3`. * Returns {RecordableHistogram} @@ -870,6 +871,26 @@ console.log(h.percentile(99)); added: v11.10.0 --> +### `histogram.count` + +<!-- YAML +added: REPLACEME +--> + +* {number} + +The number of samples recorded by the histogram. + +### `histogram.countBigInt` + +<!-- YAML +added: REPLACEME +--> + +* {bigint} + +The number of samples recorded by the histogram. + ### `histogram.exceeds` <!-- YAML @@ -881,6 +902,17 @@ added: v11.10.0 The number of times the event loop delay exceeded the maximum 1 hour event loop delay threshold. +### `histogram.exceedsBigInt` + +<!-- YAML +added: REPLACEME +--> + +* {bigint} + +The number of times the event loop delay exceeded the maximum 1 hour event +loop delay threshold. + ### `histogram.max` <!-- YAML @@ -891,6 +923,16 @@ added: v11.10.0 The maximum recorded event loop delay. +### `histogram.maxBigInt` + +<!-- YAML +added: REPLACEME +--> + +* {bigint} + +The maximum recorded event loop delay. + ### `histogram.mean` <!-- YAML @@ -911,6 +953,16 @@ added: v11.10.0 The minimum recorded event loop delay. +### `histogram.minBigInt` + +<!-- YAML +added: REPLACEME +--> + +* {bigint} + +The minimum recorded event loop delay. + ### `histogram.percentile(percentile)` <!-- YAML @@ -922,6 +974,17 @@ added: v11.10.0 Returns the value at the given percentile. +### `histogram.percentileBigInt(percentile)` + +<!-- YAML +added: REPLACEME +--> + +* `percentile` {number} A percentile value in the range (0, 100). +* Returns: {bigint} + +Returns the value at the given percentile. + ### `histogram.percentiles` <!-- YAML @@ -932,6 +995,16 @@ added: v11.10.0 Returns a `Map` object detailing the accumulated percentile distribution. +### `histogram.percentilesBigInt` + +<!-- YAML +added: REPLACEME +--> + +* {Map} + +Returns a `Map` object detailing the accumulated percentile distribution. + ### `histogram.reset()` <!-- YAML @@ -990,6 +1063,16 @@ added: - v14.18.0 --> +### `histogram.add(other)` + +<!-- YAML +added: REPLACEME +--> + +* `other` {RecordableHistogram} + +Adds the values from `other` to this histogram. + ### `histogram.record(val)` <!-- YAML diff --git a/lib/internal/histogram.js b/lib/internal/histogram.js index f437bfd4d7..4237e716dc 100644 --- a/lib/internal/histogram.js +++ b/lib/internal/histogram.js @@ -1,10 +1,12 @@ 'use strict'; const { + MapPrototypeEntries, NumberIsNaN, NumberIsInteger, NumberMAX_SAFE_INTEGER, - ObjectSetPrototypeOf, + ObjectFromEntries, + ReflectConstruct, SafeMap, Symbol, } = primordials; @@ -24,33 +26,36 @@ const { ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_TYPE, + ERR_INVALID_THIS, ERR_OUT_OF_RANGE, }, } = require('internal/errors'); const { + validateInteger, validateNumber, + validateObject, + validateUint32, } = require('internal/validators'); const kDestroy = Symbol('kDestroy'); const kHandle = Symbol('kHandle'); const kMap = Symbol('kMap'); +const kRecordable = Symbol('kRecordable'); const { kClone, kDeserialize, - JSTransferable, + makeTransferable, } = require('internal/worker/js_transferable'); function isHistogram(object) { return object?.[kHandle] !== undefined; } -class Histogram extends JSTransferable { - constructor(internal) { - super(); - this[kHandle] = internal; - this[kMap] = new SafeMap(); +class Histogram { + constructor() { + throw new ERR_ILLEGAL_CONSTRUCTOR(); } [kInspect](depth, options) { @@ -68,31 +73,118 @@ class Histogram extends JSTransferable { mean: this.mean, exceeds: this.exceeds, stddev: this.stddev, + count: this.count, percentiles: this.percentiles, }, opts)}`; } + /** + * @readonly + * @type {number} + */ + get count() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + return this[kHandle]?.count(); + } + + /** + * @readonly + * @type {bigint} + */ + get countBigInt() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + return this[kHandle]?.countBigInt(); + } + + /** + * @readonly + * @type {number} + */ get min() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); return this[kHandle]?.min(); } + /** + * @readonly + * @type {bigint} + */ + get minBigInt() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + return this[kHandle]?.minBigInt(); + } + + /** + * @readonly + * @type {number} + */ get max() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); return this[kHandle]?.max(); } + /** + * @readonly + * @type {bigint} + */ + get maxBigInt() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + return this[kHandle]?.maxBigInt(); + } + + /** + * @readonly + * @type {number} + */ get mean() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); return this[kHandle]?.mean(); } + /** + * @readonly + * @type {number} + */ get exceeds() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); return this[kHandle]?.exceeds(); } + /** + * @readonly + * @type {bigint} + */ + get exceedsBigInt() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + return this[kHandle]?.exceedsBigInt(); + } + + /** + * @readonly + * @type {number} + */ get stddev() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); return this[kHandle]?.stddev(); } + /** + * @param {number} percentile + * @returns {number} + */ percentile(percentile) { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); validateNumber(percentile, 'percentile'); if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100) @@ -101,31 +193,77 @@ class Histogram extends JSTransferable { return this[kHandle]?.percentile(percentile); } + /** + * @param {number} percentile + * @returns {bigint} + */ + percentileBigInt(percentile) { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + validateNumber(percentile, 'percentile'); + + if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100) + throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile); + + return this[kHandle]?.percentileBigInt(percentile); + } + + /** + * @readonly + * @type {Map<number,number>} + */ get percentiles() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); this[kMap].clear(); this[kHandle]?.percentiles(this[kMap]); return this[kMap]; } - reset() { - this[kHandle]?.reset(); + /** + * @readonly + * @type {Map<number,bigint>} + */ + get percentilesBigInt() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + this[kMap].clear(); + this[kHandle]?.percentilesBigInt(this[kMap]); + return this[kMap]; } - [kDestroy]() { - this[kHandle] = undefined; + /** + * @returns {void} + */ + reset() { + if (!isHistogram(this)) + throw new ERR_INVALID_THIS('Histogram'); + this[kHandle]?.reset(); } [kClone]() { const handle = this[kHandle]; return { data: { handle }, - deserializeInfo: 'internal/histogram:InternalHistogram' + deserializeInfo: 'internal/histogram:internalHistogram' }; } [kDeserialize]({ handle }) { this[kHandle] = handle; } + + toJSON() { + return { + count: this.count, + min: this.min, + max: this.max, + mean: this.mean, + exceeds: this.exceeds, + stddev: this.stddev, + percentiles: ObjectFromEntries(MapPrototypeEntries(this.percentiles)) + }; + } } class RecordableHistogram extends Histogram { @@ -133,7 +271,13 @@ class RecordableHistogram extends Histogram { throw new ERR_ILLEGAL_CONSTRUCTOR(); } + /** + * @param {number|bigint} val + * @returns {void} + */ record(val) { + if (this[kRecordable] === undefined) + throw new ERR_INVALID_THIS('RecordableHistogram'); if (typeof val === 'bigint') { this[kHandle]?.record(val); return; @@ -148,56 +292,93 @@ class RecordableHistogram extends Histogram { this[kHandle]?.record(val); } + /** + * @returns {void} + */ recordDelta() { + if (this[kRecordable] === undefined) + throw new ERR_INVALID_THIS('RecordableHistogram'); this[kHandle]?.recordDelta(); } + /** + * @param {RecordableHistogram} other + */ + add(other) { + if (this[kRecordable] === undefined) + throw new ERR_INVALID_THIS('RecordableHistogram'); + if (other[kRecordable] === undefined) + throw new ERR_INVALID_ARG_TYPE('other', 'RecordableHistogram', other); + this[kHandle]?.add(other[kHandle]); + } + [kClone]() { const handle = this[kHandle]; return { data: { handle }, - deserializeInfo: 'internal/histogram:InternalRecordableHistogram' + deserializeInfo: 'internal/histogram:internalRecordableHistogram' }; } -} -class InternalHistogram extends JSTransferable { - constructor(handle) { - super(); + [kDeserialize]({ handle }) { this[kHandle] = handle; - this[kMap] = new SafeMap(); } } -class InternalRecordableHistogram extends JSTransferable { - constructor(handle) { - super(); - this[kHandle] = handle; - this[kMap] = new SafeMap(); - } +function internalHistogram(handle) { + return makeTransferable(ReflectConstruct( + function() { + this[kHandle] = handle; + this[kMap] = new SafeMap(); + }, [], Histogram)); } - -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()); +internalHistogram.prototype[kDeserialize] = () => {}; + +function internalRecordableHistogram(handle) { + return makeTransferable(ReflectConstruct( + function() { + this[kHandle] = handle; + this[kMap] = new SafeMap(); + this[kRecordable] = true; + }, [], RecordableHistogram)); +} +internalRecordableHistogram.prototype[kDeserialize] = () => {}; + +/** + * @param {{ + * lowest? : number, + * highest? : number, + * figures? : number + * }} [options] + * @returns {RecordableHistogram} + */ +function createHistogram(options = {}) { + validateObject(options, 'options'); + const { + lowest = 1, + highest = NumberMAX_SAFE_INTEGER, + figures = 3, + } = options; + if (typeof lowest !== 'bigint') + validateInteger(lowest, 'options.lowest', 1, NumberMAX_SAFE_INTEGER); + if (typeof highest !== 'bigint') { + validateInteger(highest, 'options.highest', + 2 * lowest, NumberMAX_SAFE_INTEGER); + } else if (highest < 2n * lowest) { + throw new ERR_INVALID_ARG_VALUE.RangeError('options.highest', highest); + } + validateUint32(figures, 'options.figures', 1, 5); + return internalRecordableHistogram(new _Histogram(lowest, highest, figures)); } module.exports = { Histogram, RecordableHistogram, - InternalHistogram, - InternalRecordableHistogram, + internalHistogram, + internalRecordableHistogram, isHistogram, kDestroy, kHandle, + kMap, createHistogram, }; diff --git a/lib/internal/perf/event_loop_delay.js b/lib/internal/perf/event_loop_delay.js index f5d0eb74d5..efd45f4ed7 100644 --- a/lib/internal/perf/event_loop_delay.js +++ b/lib/internal/perf/event_loop_delay.js @@ -1,16 +1,19 @@ 'use strict'; const { + ReflectConstruct, + SafeMap, Symbol, } = primordials; const { codes: { ERR_ILLEGAL_CONSTRUCTOR, + ERR_INVALID_THIS, } } = require('internal/errors'); const { - ELDHistogram: _ELDHistogram, + createELDHistogram, } = internalBinding('performance'); const { @@ -21,25 +24,38 @@ const { const { Histogram, kHandle, + kMap, } = require('internal/histogram'); +const { + makeTransferable, +} = require('internal/worker/js_transferable'); + const kEnabled = Symbol('kEnabled'); class ELDHistogram extends Histogram { constructor(i) { - if (!(i instanceof _ELDHistogram)) { - throw new ERR_ILLEGAL_CONSTRUCTOR(); - } - super(i); - this[kEnabled] = false; + throw new ERR_ILLEGAL_CONSTRUCTOR(); } + + /** + * @returns {boolean} + */ enable() { + if (this[kEnabled] === undefined) + throw new ERR_INVALID_THIS('ELDHistogram'); if (this[kEnabled]) return false; this[kEnabled] = true; this[kHandle].start(); return true; } + + /** + * @returns {boolean} + */ disable() { + if (this[kEnabled] === undefined) + throw new ERR_INVALID_THIS('ELDHistogram'); if (!this[kEnabled]) return false; this[kEnabled] = false; this[kHandle].stop(); @@ -47,13 +63,24 @@ class ELDHistogram extends Histogram { } } +/** + * @param {{ + * resolution : number + * }} [options] + * @returns {ELDHistogram} + */ function monitorEventLoopDelay(options = {}) { validateObject(options, 'options'); const { resolution = 10 } = options; validateInteger(resolution, 'options.resolution', 1); - return new ELDHistogram(new _ELDHistogram(resolution)); + return makeTransferable(ReflectConstruct( + function() { + this[kEnabled] = false; + this[kHandle] = createELDHistogram(resolution); + this[kMap] = new SafeMap(); + }, [], ELDHistogram)); } module.exports = monitorEventLoopDelay; diff --git a/src/histogram-inl.h b/src/histogram-inl.h index 18a1668512..3b8712c878 100644 --- a/src/histogram-inl.h +++ b/src/histogram-inl.h @@ -13,35 +13,49 @@ void Histogram::Reset() { Mutex::ScopedLock lock(mutex_); hdr_reset(histogram_.get()); exceeds_ = 0; + count_ = 0; prev_ = 0; } -int64_t Histogram::Min() { +double Histogram::Add(const Histogram& other) { + Mutex::ScopedLock lock(mutex_); + count_ += other.count_; + exceeds_ += other.exceeds_; + if (other.prev_ > prev_) + prev_ = other.prev_; + return static_cast<double>(hdr_add(histogram_.get(), other.histogram_.get())); +} + +size_t Histogram::Count() const { + Mutex::ScopedLock lock(mutex_); + return count_; +} + +int64_t Histogram::Min() const { Mutex::ScopedLock lock(mutex_); return hdr_min(histogram_.get()); } -int64_t Histogram::Max() { +int64_t Histogram::Max() const { Mutex::ScopedLock lock(mutex_); return hdr_max(histogram_.get()); } -double Histogram::Mean() { +double Histogram::Mean() const { Mutex::ScopedLock lock(mutex_); return hdr_mean(histogram_.get()); } -double Histogram::Stddev() { +double Histogram::Stddev() const { Mutex::ScopedLock lock(mutex_); return hdr_stddev(histogram_.get()); } -double Histogram::Percentile(double percentile) { +int64_t Histogram::Percentile(double percentile) const { Mutex::ScopedLock lock(mutex_); CHECK_GT(percentile, 0); CHECK_LE(percentile, 100); - return static_cast<double>( - hdr_value_at_percentile(histogram_.get(), percentile)); + return hdr_value_at_percentile(histogram_.get(), percentile); } template <typename Iterator> @@ -51,26 +65,31 @@ void Histogram::Percentiles(Iterator&& fn) { hdr_iter_percentile_init(&iter, histogram_.get(), 1); while (hdr_iter_next(&iter)) { double key = iter.specifics.percentiles.percentile; - double value = static_cast<double>(iter.value); - fn(key, value); + fn(key, iter.value); } } bool Histogram::Record(int64_t value) { Mutex::ScopedLock lock(mutex_); - return hdr_record_value(histogram_.get(), value); + bool recorded = hdr_record_value(histogram_.get(), value); + if (!recorded) + exceeds_++; + else + count_++; + return recorded; } uint64_t Histogram::RecordDelta() { Mutex::ScopedLock lock(mutex_); uint64_t time = uv_hrtime(); - uint64_t delta = 0; + int64_t delta = 0; if (prev_ > 0) { + CHECK_GE(time, prev_); delta = time - prev_; - if (delta > 0) { - if (!hdr_record_value(histogram_.get(), delta) && exceeds_ < 0xFFFFFFFF) - exceeds_++; - } + if (hdr_record_value(histogram_.get(), delta)) + count_++; + else + exceeds_++; } prev_ = time; return delta; diff --git a/src/histogram.cc b/src/histogram.cc index 6fbb0eda6c..87641a4a36 100644 --- a/src/histogram.cc +++ b/src/histogram.cc @@ -10,16 +10,21 @@ namespace node { using v8::BigInt; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; +using v8::Integer; using v8::Local; using v8::Map; using v8::Number; using v8::Object; using v8::String; +using v8::Uint32; using v8::Value; -Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { +Histogram::Histogram(const Options& options) { hdr_histogram* histogram; - CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram)); + CHECK_EQ(0, hdr_init(options.lowest, + options.highest, + options.figures, + &histogram)); histogram_.reset(histogram); } @@ -27,8 +32,8 @@ 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(const Histogram::Options& options) + : histogram_(new Histogram(options)) {} HistogramImpl::HistogramImpl(std::shared_ptr<Histogram> histogram) : histogram_(std::move(histogram)) {} @@ -36,11 +41,9 @@ HistogramImpl::HistogramImpl(std::shared_ptr<Histogram> histogram) HistogramBase::HistogramBase( Environment* env, Local<Object> wrap, - int64_t lowest, - int64_t highest, - int figures) + const Histogram::Options& options) : BaseObject(env, wrap), - HistogramImpl(lowest, highest, figures) { + HistogramImpl(options) { MakeWeak(); } @@ -57,6 +60,22 @@ void HistogramBase::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("histogram", histogram()); } +void HistogramBase::GetCount(const v8::FunctionCallbackInfo<v8::Value>& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast<double>((*histogram)->Count()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetCountBigInt( + const v8::FunctionCallbackInfo<v8::Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set( + BigInt::NewFromUnsigned(env->isolate(), (*histogram)->Count())); +} + void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -64,6 +83,13 @@ void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(value); } +void HistogramBase::GetMinBigInt(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(BigInt::New(env->isolate(), (*histogram)->Min())); +} + void HistogramBase::GetMax(const FunctionCallbackInfo<Value>& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -71,6 +97,14 @@ void HistogramBase::GetMax(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(value); } +void HistogramBase::GetMaxBigInt(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set( + BigInt::New(env->isolate(), (*histogram)->Max())); +} + void HistogramBase::GetMean(const FunctionCallbackInfo<Value>& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -84,6 +118,14 @@ void HistogramBase::GetExceeds(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(value); } +void HistogramBase::GetExceedsBigInt(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set( + BigInt::NewFromUnsigned(env->isolate(), (*histogram)->Exceeds())); +} + void HistogramBase::GetStddev(const FunctionCallbackInfo<Value>& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -95,7 +137,19 @@ void HistogramBase::GetPercentile(const FunctionCallbackInfo<Value>& args) { ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); CHECK(args[0]->IsNumber()); double percentile = args[0].As<Number>()->Value(); - args.GetReturnValue().Set((*histogram)->Percentile(percentile)); + double value = static_cast<double>((*histogram)->Percentile(percentile)); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetPercentileBigInt( + const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As<Number>()->Value(); + int64_t value = (*histogram)->Percentile(percentile); + args.GetReturnValue().Set(BigInt::New(env->isolate(), value)); } void HistogramBase::GetPercentiles(const FunctionCallbackInfo<Value>& args) { @@ -104,11 +158,26 @@ void HistogramBase::GetPercentiles(const FunctionCallbackInfo<Value>& args) { ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); CHECK(args[0]->IsMap()); Local<Map> map = args[0].As<Map>(); - (*histogram)->Percentiles([map, env](double key, double value) { + (*histogram)->Percentiles([map, env](double key, int64_t value) { + map->Set( + env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), static_cast<double>(value))).IsEmpty(); + }); +} + +void HistogramBase::GetPercentilesBigInt( + const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local<Map> map = args[0].As<Map>(); + (*histogram)->Percentiles([map, env](double key, int64_t value) { map->Set( env->context(), Number::New(env->isolate(), key), - Number::New(env->isolate(), value)).IsEmpty(); + BigInt::New(env->isolate(), value)).IsEmpty(); }); } @@ -138,11 +207,22 @@ void HistogramBase::Record(const FunctionCallbackInfo<Value>& args) { (*histogram)->Record(value); } +void HistogramBase::Add(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + + CHECK(GetConstructorTemplate(env)->HasInstance(args[0])); + HistogramBase* other; + ASSIGN_OR_RETURN_UNWRAP(&other, args[0]); + + double count = (*histogram)->Add(*(other->histogram())); + args.GetReturnValue().Set(count); +} + BaseObjectPtr<HistogramBase> HistogramBase::Create( Environment* env, - int64_t lowest, - int64_t highest, - int figures) { + const Histogram::Options& options) { Local<Object> obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() @@ -150,8 +230,7 @@ BaseObjectPtr<HistogramBase> HistogramBase::Create( return BaseObjectPtr<HistogramBase>(); } - return MakeBaseObject<HistogramBase>( - env, obj, lowest, highest, figures); + return MakeBaseObject<HistogramBase>(env, obj, options); } BaseObjectPtr<HistogramBase> HistogramBase::Create( @@ -169,7 +248,32 @@ BaseObjectPtr<HistogramBase> HistogramBase::Create( void HistogramBase::New(const FunctionCallbackInfo<Value>& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - new HistogramBase(env, args.This()); + + CHECK_IMPLIES(!args[0]->IsNumber(), args[0]->IsBigInt()); + CHECK_IMPLIES(!args[1]->IsNumber(), args[1]->IsBigInt()); + CHECK(args[2]->IsUint32()); + + int64_t lowest = 1; + int64_t highest = std::numeric_limits<int64_t>::max(); + + bool lossless_ignored; + + if (args[0]->IsNumber()) { + lowest = args[0].As<Integer>()->Value(); + } else if (args[0]->IsBigInt()) { + lowest = args[0].As<BigInt>()->Int64Value(&lossless_ignored); + } + + if (args[1]->IsNumber()) { + highest = args[1].As<Integer>()->Value(); + } else if (args[1]->IsBigInt()) { + highest = args[1].As<BigInt>()->Int64Value(&lossless_ignored); + } + + int32_t figures = args[2].As<Uint32>()->Value(); + new HistogramBase(env, args.This(), Histogram::Options { + lowest, highest, figures + }); } Local<FunctionTemplate> HistogramBase::GetConstructorTemplate( @@ -184,16 +288,26 @@ Local<FunctionTemplate> HistogramBase::GetConstructorTemplate( tmpl->InstanceTemplate()->SetInternalFieldCount( HistogramBase::kInternalFieldCount); + env->SetProtoMethodNoSideEffect(tmpl, "count", GetCount); + env->SetProtoMethodNoSideEffect(tmpl, "countBigInt", GetCountBigInt); env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds); + env->SetProtoMethodNoSideEffect(tmpl, "exceedsBigInt", GetExceedsBigInt); env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin); + env->SetProtoMethodNoSideEffect(tmpl, "minBigInt", GetMinBigInt); env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax); + env->SetProtoMethodNoSideEffect(tmpl, "maxBigInt", GetMaxBigInt); env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean); env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev); env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile); + env->SetProtoMethodNoSideEffect(tmpl, "percentileBigInt", + GetPercentileBigInt); env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles); + env->SetProtoMethodNoSideEffect(tmpl, "percentilesBigInt", + GetPercentilesBigInt); env->SetProtoMethod(tmpl, "reset", DoReset); env->SetProtoMethod(tmpl, "record", Record); env->SetProtoMethod(tmpl, "recordDelta", RecordDelta); + env->SetProtoMethod(tmpl, "add", Add); env->set_histogram_ctor_template(tmpl); } return tmpl; @@ -202,16 +316,24 @@ Local<FunctionTemplate> HistogramBase::GetConstructorTemplate( void HistogramBase::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(New); + registry->Register(GetCount); + registry->Register(GetCountBigInt); registry->Register(GetExceeds); + registry->Register(GetExceedsBigInt); registry->Register(GetMin); + registry->Register(GetMinBigInt); registry->Register(GetMax); + registry->Register(GetMaxBigInt); registry->Register(GetMean); registry->Register(GetStddev); registry->Register(GetPercentile); + registry->Register(GetPercentileBigInt); registry->Register(GetPercentiles); + registry->Register(GetPercentilesBigInt); registry->Register(DoReset); registry->Register(Record); registry->Register(RecordDelta); + registry->Register(Add); } void HistogramBase::Initialize(Environment* env, Local<Object> target) { @@ -242,13 +364,22 @@ Local<FunctionTemplate> IntervalHistogram::GetConstructorTemplate( tmpl->Inherit(HandleWrap::GetConstructorTemplate(env)); tmpl->InstanceTemplate()->SetInternalFieldCount( HistogramBase::kInternalFieldCount); + env->SetProtoMethodNoSideEffect(tmpl, "count", GetCount); + env->SetProtoMethodNoSideEffect(tmpl, "countBigInt", GetCountBigInt); env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds); + env->SetProtoMethodNoSideEffect(tmpl, "exceedsBigInt", GetExceedsBigInt); env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin); + env->SetProtoMethodNoSideEffect(tmpl, "minBigInt", GetMinBigInt); env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax); + env->SetProtoMethodNoSideEffect(tmpl, "maxBigInt", GetMaxBigInt); env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean); env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev); env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile); + env->SetProtoMethodNoSideEffect(tmpl, "percentileBigInt", + GetPercentileBigInt); env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles); + env->SetProtoMethodNoSideEffect(tmpl, "percentilesBigInt", + GetPercentilesBigInt); env->SetProtoMethod(tmpl, "reset", DoReset); env->SetProtoMethod(tmpl, "start", Start); env->SetProtoMethod(tmpl, "stop", Stop); @@ -259,13 +390,20 @@ Local<FunctionTemplate> IntervalHistogram::GetConstructorTemplate( void IntervalHistogram::RegisterExternalReferences( ExternalReferenceRegistry* registry) { + registry->Register(GetCount); + registry->Register(GetCountBigInt); registry->Register(GetExceeds); + registry->Register(GetExceedsBigInt); registry->Register(GetMin); + registry->Register(GetMinBigInt); registry->Register(GetMax); + registry->Register(GetMaxBigInt); registry->Register(GetMean); registry->Register(GetStddev); registry->Register(GetPercentile); + registry->Register(GetPercentileBigInt); registry->Register(GetPercentiles); + registry->Register(GetPercentilesBigInt); registry->Register(DoReset); registry->Register(Start); registry->Register(Stop); @@ -276,24 +414,48 @@ IntervalHistogram::IntervalHistogram( Local<Object> wrap, AsyncWrap::ProviderType type, int32_t interval, - int64_t lowest, - int64_t highest, - int figures) + std::function<void(Histogram&)> on_interval, + const Histogram::Options& options) : HandleWrap( env, wrap, reinterpret_cast<uv_handle_t*>(&timer_), type), - HistogramImpl(lowest, highest, figures), - interval_(interval) { + HistogramImpl(options), + interval_(interval), + on_interval_(std::move(on_interval)) { MakeWeak(); uv_timer_init(env->event_loop(), &timer_); } +BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create( + Environment* env, + int32_t interval, + std::function<void(Histogram&)> on_interval, + const Histogram::Options& options) { + Local<Object> obj; + if (!GetConstructorTemplate(env) + ->InstanceTemplate() + ->NewInstance(env->context()).ToLocal(&obj)) { + return BaseObjectPtr<IntervalHistogram>(); + } + + return MakeBaseObject<IntervalHistogram>( + env, + obj, + AsyncWrap::PROVIDER_ELDHISTOGRAM, + interval, + std::move(on_interval), + options); +} + void IntervalHistogram::TimerCB(uv_timer_t* handle) { IntervalHistogram* histogram = ContainerOf(&IntervalHistogram::timer_, handle); - histogram->OnInterval(); + + Histogram* h = histogram->histogram().get(); + + histogram->on_interval_(*h); } void IntervalHistogram::MemoryInfo(MemoryTracker* tracker) const { @@ -327,6 +489,22 @@ void IntervalHistogram::Stop(const FunctionCallbackInfo<Value>& args) { histogram->OnStop(); } +void IntervalHistogram::GetCount(const FunctionCallbackInfo<Value>& args) { + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast<double>((*histogram)->Count()); + args.GetReturnValue().Set(value); +} + +void IntervalHistogram::GetCountBigInt( + const v8::FunctionCallbackInfo<v8::Value>& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set( + BigInt::NewFromUnsigned(env->isolate(), (*histogram)->Count())); +} + void IntervalHistogram::GetMin(const FunctionCallbackInfo<Value>& args) { IntervalHistogram* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -334,6 +512,13 @@ void IntervalHistogram::GetMin(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(value); } +void IntervalHistogram::GetMinBigInt(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(BigInt::New(env->isolate(), (*histogram)->Min())); +} + void IntervalHistogram::GetMax(const FunctionCallbackInfo<Value>& args) { IntervalHistogram* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -341,6 +526,13 @@ void IntervalHistogram::GetMax(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(value); } +void IntervalHistogram::GetMaxBigInt(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(BigInt::New(env->isolate(), (*histogram)->Min())); +} + void IntervalHistogram::GetMean(const FunctionCallbackInfo<Value>& args) { IntervalHistogram* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -354,6 +546,15 @@ void IntervalHistogram::GetExceeds(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(value); } +void IntervalHistogram::GetExceedsBigInt( + const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set( + BigInt::New(env->isolate(), (*histogram)->Exceeds())); +} + void IntervalHistogram::GetStddev(const FunctionCallbackInfo<Value>& args) { IntervalHistogram* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); @@ -365,7 +566,19 @@ void IntervalHistogram::GetPercentile(const FunctionCallbackInfo<Value>& args) { ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); CHECK(args[0]->IsNumber()); double percentile = args[0].As<Number>()->Value(); - args.GetReturnValue().Set((*histogram)->Percentile(percentile)); + double value = static_cast<double>((*histogram)->Percentile(percentile)); + args.GetReturnValue().Set(value); +} + +void IntervalHistogram::GetPercentileBigInt( + const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As<Number>()->Value(); + int64_t value = (*histogram)->Percentile(percentile); + args.GetReturnValue().Set(BigInt::New(env->isolate(), value)); } void IntervalHistogram::GetPercentiles( @@ -375,11 +588,26 @@ void IntervalHistogram::GetPercentiles( ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); CHECK(args[0]->IsMap()); Local<Map> map = args[0].As<Map>(); - (*histogram)->Percentiles([map, env](double key, double value) { + (*histogram)->Percentiles([map, env](double key, int64_t value) { + map->Set( + env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), static_cast<double>(value))).IsEmpty(); + }); +} + +void IntervalHistogram::GetPercentilesBigInt( + const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + IntervalHistogram* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local<Map> map = args[0].As<Map>(); + (*histogram)->Percentiles([map, env](double key, int64_t value) { map->Set( env->context(), Number::New(env->isolate(), key), - Number::New(env->isolate(), value)).IsEmpty(); + BigInt::New(env->isolate(), value)).IsEmpty(); }); } diff --git a/src/histogram.h b/src/histogram.h index 00b4f7796f..d526bba8a1 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -24,23 +24,29 @@ constexpr int kDefaultHistogramFigures = 3; class Histogram : public MemoryRetainer { public: - Histogram( - int64_t lowest = 1, - int64_t highest = std::numeric_limits<int64_t>::max(), - int figures = kDefaultHistogramFigures); + struct Options { + int64_t lowest = 1; + int64_t highest = std::numeric_limits<int64_t>::max(); + int figures = kDefaultHistogramFigures; + }; + + explicit Histogram(const Options& options); virtual ~Histogram() = default; inline bool Record(int64_t value); inline void Reset(); - inline int64_t Min(); - inline int64_t Max(); - inline double Mean(); - inline double Stddev(); - inline double Percentile(double percentile); - inline int64_t Exceeds() const { return exceeds_; } + inline int64_t Min() const; + inline int64_t Max() const; + inline double Mean() const; + inline double Stddev() const; + inline int64_t Percentile(double percentile) const; + inline size_t Exceeds() const { return exceeds_; } + inline size_t Count() const; inline uint64_t RecordDelta(); + inline double Add(const Histogram& other); + // Iterator is a function type that takes two doubles as argument, one for // percentile and one for the value at that percentile. template <typename Iterator> @@ -55,20 +61,20 @@ class Histogram : public MemoryRetainer { private: using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>; HistogramPointer histogram_; - int64_t exceeds_ = 0; uint64_t prev_ = 0; - + size_t exceeds_ = 0; + size_t count_ = 0; Mutex mutex_; }; class HistogramImpl { public: - HistogramImpl(int64_t lowest, int64_t highest, int figures); + explicit HistogramImpl( + const Histogram::Options& options = Histogram::Options {}); explicit HistogramImpl(std::shared_ptr<Histogram> histogram); Histogram* operator->() { return histogram_.get(); } - protected: const std::shared_ptr<Histogram>& histogram() const { return histogram_; } private: @@ -84,9 +90,7 @@ class HistogramBase : public BaseObject, public HistogramImpl { static BaseObjectPtr<HistogramBase> Create( Environment* env, - int64_t lowest = 1, - int64_t highest = std::numeric_limits<int64_t>::max(), - int figures = kDefaultHistogramFigures); + const Histogram::Options& options = Histogram::Options {}); static BaseObjectPtr<HistogramBase> Create( Environment* env, @@ -98,6 +102,12 @@ class HistogramBase : public BaseObject, public HistogramImpl { SET_MEMORY_INFO_NAME(HistogramBase) SET_SELF_SIZE(HistogramBase) + static void GetCountBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetMinBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetMaxBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetExceedsBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + + static void GetCount(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMin(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMean(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -105,18 +115,21 @@ class HistogramBase : public BaseObject, public HistogramImpl { static void GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetPercentile( const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetPercentileBigInt( + const v8::FunctionCallbackInfo<v8::Value>& args); static void GetPercentiles( const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetPercentilesBigInt( + const v8::FunctionCallbackInfo<v8::Value>& args); static void DoReset(const v8::FunctionCallbackInfo<v8::Value>& args); static void Record(const v8::FunctionCallbackInfo<v8::Value>& args); static void RecordDelta(const v8::FunctionCallbackInfo<v8::Value>& args); + static void Add(const v8::FunctionCallbackInfo<v8::Value>& args); HistogramBase( Environment* env, v8::Local<v8::Object> wrap, - int64_t lowest = 1, - int64_t highest = std::numeric_limits<int64_t>::max(), - int figures = kDefaultHistogramFigures); + const Histogram::Options& options = Histogram::Options {}); HistogramBase( Environment* env, @@ -164,23 +177,24 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl { static BaseObjectPtr<IntervalHistogram> Create( Environment* env, - int64_t lowest = 1, - int64_t highest = std::numeric_limits<int64_t>::max(), - int figures = kDefaultHistogramFigures); - - virtual void OnInterval() = 0; - - void MemoryInfo(MemoryTracker* tracker) const override; + int32_t interval, + std::function<void(Histogram&)> on_interval, + const Histogram::Options& options); IntervalHistogram( Environment* env, v8::Local<v8::Object> wrap, AsyncWrap::ProviderType type, int32_t interval, - int64_t lowest = 1, - int64_t highest = std::numeric_limits<int64_t>::max(), - int figures = kDefaultHistogramFigures); + std::function<void(Histogram&)> on_interval, + const Histogram::Options& options = Histogram::Options {}); + + static void GetCountBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetMinBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetMaxBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetExceedsBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetCount(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMin(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMean(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -188,8 +202,12 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl { static void GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetPercentile( const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetPercentileBigInt( + const v8::FunctionCallbackInfo<v8::Value>& args); static void GetPercentiles( const v8::FunctionCallbackInfo<v8::Value>& args); + static void GetPercentilesBigInt( + const v8::FunctionCallbackInfo<v8::Value>& args); static void DoReset(const v8::FunctionCallbackInfo<v8::Value>& args); static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -199,6 +217,10 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl { } std::unique_ptr<worker::TransferData> CloneForMessaging() const override; + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(IntervalHistogram) + SET_SELF_SIZE(IntervalHistogram) + private: static void TimerCB(uv_timer_t* handle); void OnStart(StartFlags flags = StartFlags::RESET); @@ -206,6 +228,7 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl { bool enabled_ = false; int32_t interval_ = 0; + std::function<void(Histogram&)> on_interval_; uv_timer_t timer_; }; diff --git a/src/node_perf.cc b/src/node_perf.cc index acbb0e0d90..8bda1791fc 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -234,51 +234,25 @@ void LoopIdleTime(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(1.0 * idle_time / 1e6); } -// Event Loop Timing Histogram -void ELDHistogram::New(const FunctionCallbackInfo<Value>& args) { +void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); - CHECK(args.IsConstructCall()); - int64_t resolution = args[0].As<Integer>()->Value(); - CHECK_GT(resolution, 0); - new ELDHistogram(env, args.This(), resolution); -} - -void ELDHistogram::Initialize(Environment* env, Local<Object> target) { - Local<FunctionTemplate> tmpl = env->NewFunctionTemplate(New); - tmpl->Inherit(IntervalHistogram::GetConstructorTemplate(env)); - tmpl->InstanceTemplate()->SetInternalFieldCount( - ELDHistogram::kInternalFieldCount); - env->SetConstructorFunction(target, "ELDHistogram", tmpl); -} - -void ELDHistogram::RegisterExternalReferences( - ExternalReferenceRegistry* registry) { - registry->Register(New); - IntervalHistogram::RegisterExternalReferences(registry); -} - -ELDHistogram::ELDHistogram( - Environment* env, - Local<Object> wrap, - int64_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), - "delay", delta); - TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "min", histogram()->Min()); - TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "max", histogram()->Max()); - TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "mean", histogram()->Mean()); - TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), - "stddev", histogram()->Stddev()); + int64_t interval = args[0].As<Integer>()->Value(); + CHECK_GT(interval, 0); + BaseObjectPtr<IntervalHistogram> histogram = + IntervalHistogram::Create(env, interval, [](Histogram& histogram) { + uint64_t delta = histogram.RecordDelta(); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "delay", delta); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "min", histogram.Min()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "max", histogram.Max()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "mean", histogram.Mean()); + TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), + "stddev", histogram.Stddev()); + }, Histogram::Options { 1000 }); + args.GetReturnValue().Set(histogram->object()); } void GetTimeOrigin(const FunctionCallbackInfo<Value>& args) { @@ -326,6 +300,7 @@ void Initialize(Local<Object> target, env->SetMethod(target, "loopIdleTime", LoopIdleTime); env->SetMethod(target, "getTimeOrigin", GetTimeOrigin); env->SetMethod(target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); + env->SetMethod(target, "createELDHistogram", CreateELDHistogram); Local<Object> constants = Object::New(isolate); @@ -368,7 +343,6 @@ void Initialize(Local<Object> target, attr).ToChecked(); HistogramBase::Initialize(env, target); - ELDHistogram::Initialize(env, target); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { @@ -380,8 +354,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(LoopIdleTime); registry->Register(GetTimeOrigin); registry->Register(GetTimeOriginTimeStamp); + registry->Register(CreateELDHistogram); HistogramBase::RegisterExternalReferences(registry); - ELDHistogram::RegisterExternalReferences(registry); + IntervalHistogram::RegisterExternalReferences(registry); } } // namespace performance } // namespace node diff --git a/src/node_perf.h b/src/node_perf.h index 64913ab9de..b1a9917138 100644 --- a/src/node_perf.h +++ b/src/node_perf.h @@ -160,23 +160,6 @@ struct GCPerformanceEntryTraits { using GCPerformanceEntry = PerformanceEntry<GCPerformanceEntryTraits>; -class ELDHistogram : public IntervalHistogram { - public: - static void RegisterExternalReferences(ExternalReferenceRegistry* registry); - static void Initialize(Environment* env, v8::Local<v8::Object> target); - static void New(const v8::FunctionCallbackInfo<v8::Value>& args); - - ELDHistogram( - Environment* env, - v8::Local<v8::Object> wrap, - int64_t interval); - - void OnInterval() override; - - SET_MEMORY_INFO_NAME(ELDHistogram) - SET_SELF_SIZE(ELDHistogram) -}; - } // namespace performance } // namespace node diff --git a/test/parallel/test-perf-hooks-histogram.js b/test/parallel/test-perf-hooks-histogram.js index a60d3a94bb..2137c1b2a3 100644 --- a/test/parallel/test-perf-hooks-histogram.js +++ b/test/parallel/test-perf-hooks-histogram.js @@ -1,54 +1,75 @@ 'use strict'; const common = require('../common'); -const assert = require('assert'); + +const { + ok, + strictEqual, + throws, +} = require('assert'); + const { createHistogram, monitorEventLoopDelay, } = require('perf_hooks'); + const { inspect } = require('util'); { 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)); + strictEqual(h.min, 9223372036854776000); + strictEqual(h.minBigInt, 9223372036854775807n); + strictEqual(h.max, 0); + strictEqual(h.maxBigInt, 0n); + strictEqual(h.exceeds, 0); + strictEqual(h.exceedsBigInt, 0n); + ok(Number.isNaN(h.mean)); + ok(Number.isNaN(h.stddev)); + + strictEqual(h.count, 0); + strictEqual(h.countBigInt, 0n); h.record(1); + strictEqual(h.count, 1); + strictEqual(h.countBigInt, 1n); + [false, '', {}, undefined, null].forEach((i) => { - assert.throws(() => h.record(i), { + throws(() => h.record(i), { code: 'ERR_INVALID_ARG_TYPE' }); }); - assert.throws(() => h.record(0, Number.MAX_SAFE_INTEGER + 1), { + 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); + strictEqual(h.min, 1); + strictEqual(h.minBigInt, 1n); + strictEqual(h.max, 1); + strictEqual(h.maxBigInt, 1n); + strictEqual(h.exceeds, 0); + strictEqual(h.mean, 1); + strictEqual(h.stddev, 0); + + strictEqual(h.percentile(1), 1); + strictEqual(h.percentile(100), 1); - assert.strictEqual(h.percentile(1), 1); - assert.strictEqual(h.percentile(100), 1); + strictEqual(h.percentileBigInt(1), 1n); + strictEqual(h.percentileBigInt(100), 1n); 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); + strictEqual(h.min, 1); + strictEqual(h.max, 1); + strictEqual(h.exceeds, 0); + strictEqual(h.mean, 1); + strictEqual(h.stddev, 0); data.record(2n); data.recordDelta(); - assert.strictEqual(h.max, 2); + strictEqual(h.max, 2); mc.port1.close(); }); @@ -57,13 +78,15 @@ const { inspect } = require('util'); { const e = monitorEventLoopDelay(); + strictEqual(e.count, 0); 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); + strictEqual(typeof data.min, 'number'); + ok(data.min > 0); + ok(data.count > 0); + strictEqual(data.disable, undefined); + strictEqual(data.enable, undefined); mc.port1.close(); }); setTimeout(() => mc.port2.postMessage(e), 100); @@ -71,12 +94,66 @@ const { inspect } = require('util'); { const h = createHistogram(); - assert(inspect(h, { depth: null }).startsWith('Histogram')); - assert.strictEqual(inspect(h, { depth: -1 }), '[RecordableHistogram]'); + ok(inspect(h, { depth: null }).startsWith('Histogram')); + strictEqual(inspect(h, { depth: -1 }), '[RecordableHistogram]'); } { // Tests that RecordableHistogram is impossible to construct manually const h = createHistogram(); - assert.throws(() => new h.constructor(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); + throws(() => new h.constructor(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); +} + +{ + [ + 'hello', + 1, + null, + ].forEach((i) => { + throws(() => createHistogram(i), { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [ + 'hello', + false, + null, + {}, + ].forEach((i) => { + throws(() => createHistogram({ lowest: i }), { + code: 'ERR_INVALID_ARG_TYPE', + }); + throws(() => createHistogram({ highest: i }), { + code: 'ERR_INVALID_ARG_TYPE', + }); + throws(() => createHistogram({ figures: i }), { + code: 'ERR_INVALID_ARG_TYPE', + }); + }); + + createHistogram({ lowest: 1, highest: 11, figures: 1 }); +} + +{ + const h1 = createHistogram(); + const h2 = createHistogram(); + + h1.record(1); + + strictEqual(h2.count, 0); + strictEqual(h1.count, 1); + + h2.add(h1); + + strictEqual(h2.count, 1); + + [ + 'hello', + 1, + false, + {}, + ].forEach((i) => { + throws(() => h1.add(i), { + code: 'ERR_INVALID_ARG_TYPE', + }); + }); } |