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>
This commit is contained in:
James M Snell 2021-12-11 16:59:48 -08:00
parent 665b404e65
commit 23637e9a3b
No known key found for this signature in database
GPG Key ID: 7341B15C070877AC
9 changed files with 805 additions and 209 deletions

View File

@ -817,10 +817,11 @@ added:
--> -->
* `options` {Object} * `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`. value greater than 0. **Default:** `1`.
* `max` {number|bigint} The maximum recordable value. Must be an integer * `highest` {number|bigint} The highest recordable value. Must be an integer
value greater than `min`. **Default:** `Number.MAX_SAFE_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 * `figures` {number} The number of accuracy digits. Must be a number between
`1` and `5`. **Default:** `3`. `1` and `5`. **Default:** `3`.
* Returns {RecordableHistogram} * Returns {RecordableHistogram}
@ -870,6 +871,26 @@ console.log(h.percentile(99));
added: v11.10.0 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` ### `histogram.exceeds`
<!-- YAML <!-- YAML
@ -881,6 +902,17 @@ added: v11.10.0
The number of times the event loop delay exceeded the maximum 1 hour event The number of times the event loop delay exceeded the maximum 1 hour event
loop delay threshold. 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` ### `histogram.max`
<!-- YAML <!-- YAML
@ -891,6 +923,16 @@ added: v11.10.0
The maximum recorded event loop delay. The maximum recorded event loop delay.
### `histogram.maxBigInt`
<!-- YAML
added: REPLACEME
-->
* {bigint}
The maximum recorded event loop delay.
### `histogram.mean` ### `histogram.mean`
<!-- YAML <!-- YAML
@ -911,6 +953,16 @@ added: v11.10.0
The minimum recorded event loop delay. The minimum recorded event loop delay.
### `histogram.minBigInt`
<!-- YAML
added: REPLACEME
-->
* {bigint}
The minimum recorded event loop delay.
### `histogram.percentile(percentile)` ### `histogram.percentile(percentile)`
<!-- YAML <!-- YAML
@ -922,6 +974,17 @@ added: v11.10.0
Returns the value at the given percentile. 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` ### `histogram.percentiles`
<!-- YAML <!-- YAML
@ -932,6 +995,16 @@ added: v11.10.0
Returns a `Map` object detailing the accumulated percentile distribution. 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()` ### `histogram.reset()`
<!-- YAML <!-- YAML
@ -990,6 +1063,16 @@ added:
- v14.18.0 - v14.18.0
--> -->
### `histogram.add(other)`
<!-- YAML
added: REPLACEME
-->
* `other` {RecordableHistogram}
Adds the values from `other` to this histogram.
### `histogram.record(val)` ### `histogram.record(val)`
<!-- YAML <!-- YAML

View File

@ -1,10 +1,12 @@
'use strict'; 'use strict';
const { const {
MapPrototypeEntries,
NumberIsNaN, NumberIsNaN,
NumberIsInteger, NumberIsInteger,
NumberMAX_SAFE_INTEGER, NumberMAX_SAFE_INTEGER,
ObjectSetPrototypeOf, ObjectFromEntries,
ReflectConstruct,
SafeMap, SafeMap,
Symbol, Symbol,
} = primordials; } = primordials;
@ -24,33 +26,36 @@ const {
ERR_ILLEGAL_CONSTRUCTOR, ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_VALUE,
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
ERR_OUT_OF_RANGE, ERR_OUT_OF_RANGE,
}, },
} = require('internal/errors'); } = require('internal/errors');
const { const {
validateInteger,
validateNumber, validateNumber,
validateObject,
validateUint32,
} = require('internal/validators'); } = require('internal/validators');
const kDestroy = Symbol('kDestroy'); const kDestroy = Symbol('kDestroy');
const kHandle = Symbol('kHandle'); const kHandle = Symbol('kHandle');
const kMap = Symbol('kMap'); const kMap = Symbol('kMap');
const kRecordable = Symbol('kRecordable');
const { const {
kClone, kClone,
kDeserialize, kDeserialize,
JSTransferable, makeTransferable,
} = require('internal/worker/js_transferable'); } = require('internal/worker/js_transferable');
function isHistogram(object) { function isHistogram(object) {
return object?.[kHandle] !== undefined; return object?.[kHandle] !== undefined;
} }
class Histogram extends JSTransferable { class Histogram {
constructor(internal) { constructor() {
super(); throw new ERR_ILLEGAL_CONSTRUCTOR();
this[kHandle] = internal;
this[kMap] = new SafeMap();
} }
[kInspect](depth, options) { [kInspect](depth, options) {
@ -68,31 +73,118 @@ class Histogram extends JSTransferable {
mean: this.mean, mean: this.mean,
exceeds: this.exceeds, exceeds: this.exceeds,
stddev: this.stddev, stddev: this.stddev,
count: this.count,
percentiles: this.percentiles, percentiles: this.percentiles,
}, opts)}`; }, 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() { get min() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return this[kHandle]?.min(); 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() { get max() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return this[kHandle]?.max(); 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() { get mean() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return this[kHandle]?.mean(); return this[kHandle]?.mean();
} }
/**
* @readonly
* @type {number}
*/
get exceeds() { get exceeds() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return this[kHandle]?.exceeds(); 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() { get stddev() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
return this[kHandle]?.stddev(); return this[kHandle]?.stddev();
} }
/**
* @param {number} percentile
* @returns {number}
*/
percentile(percentile) { percentile(percentile) {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
validateNumber(percentile, 'percentile'); validateNumber(percentile, 'percentile');
if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100) if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100)
@ -101,31 +193,77 @@ class Histogram extends JSTransferable {
return this[kHandle]?.percentile(percentile); 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() { get percentiles() {
if (!isHistogram(this))
throw new ERR_INVALID_THIS('Histogram');
this[kMap].clear(); this[kMap].clear();
this[kHandle]?.percentiles(this[kMap]); this[kHandle]?.percentiles(this[kMap]);
return 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]() { [kClone]() {
const handle = this[kHandle]; const handle = this[kHandle];
return { return {
data: { handle }, data: { handle },
deserializeInfo: 'internal/histogram:InternalHistogram' deserializeInfo: 'internal/histogram:internalHistogram'
}; };
} }
[kDeserialize]({ handle }) { [kDeserialize]({ handle }) {
this[kHandle] = 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 { class RecordableHistogram extends Histogram {
@ -133,7 +271,13 @@ class RecordableHistogram extends Histogram {
throw new ERR_ILLEGAL_CONSTRUCTOR(); throw new ERR_ILLEGAL_CONSTRUCTOR();
} }
/**
* @param {number|bigint} val
* @returns {void}
*/
record(val) { record(val) {
if (this[kRecordable] === undefined)
throw new ERR_INVALID_THIS('RecordableHistogram');
if (typeof val === 'bigint') { if (typeof val === 'bigint') {
this[kHandle]?.record(val); this[kHandle]?.record(val);
return; return;
@ -148,56 +292,93 @@ class RecordableHistogram extends Histogram {
this[kHandle]?.record(val); this[kHandle]?.record(val);
} }
/**
* @returns {void}
*/
recordDelta() { recordDelta() {
if (this[kRecordable] === undefined)
throw new ERR_INVALID_THIS('RecordableHistogram');
this[kHandle]?.recordDelta(); 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]() { [kClone]() {
const handle = this[kHandle]; const handle = this[kHandle];
return { return {
data: { handle }, data: { handle },
deserializeInfo: 'internal/histogram:InternalRecordableHistogram' deserializeInfo: 'internal/histogram:internalRecordableHistogram'
}; };
} }
}
class InternalHistogram extends JSTransferable { [kDeserialize]({ handle }) {
constructor(handle) {
super();
this[kHandle] = handle; this[kHandle] = handle;
this[kMap] = new SafeMap();
} }
} }
class InternalRecordableHistogram extends JSTransferable { function internalHistogram(handle) {
constructor(handle) { return makeTransferable(ReflectConstruct(
super(); function() {
this[kHandle] = handle; this[kHandle] = handle;
this[kMap] = new SafeMap(); this[kMap] = new SafeMap();
} }, [], Histogram));
} }
internalHistogram.prototype[kDeserialize] = () => {};
InternalHistogram.prototype.constructor = Histogram; function internalRecordableHistogram(handle) {
ObjectSetPrototypeOf( return makeTransferable(ReflectConstruct(
InternalHistogram.prototype, function() {
Histogram.prototype); this[kHandle] = handle;
this[kMap] = new SafeMap();
this[kRecordable] = true;
}, [], RecordableHistogram));
}
internalRecordableHistogram.prototype[kDeserialize] = () => {};
InternalRecordableHistogram.prototype.constructor = RecordableHistogram; /**
ObjectSetPrototypeOf( * @param {{
InternalRecordableHistogram.prototype, * lowest? : number,
RecordableHistogram.prototype); * highest? : number,
* figures? : number
function createHistogram() { * }} [options]
return new InternalRecordableHistogram(new _Histogram()); * @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 = { module.exports = {
Histogram, Histogram,
RecordableHistogram, RecordableHistogram,
InternalHistogram, internalHistogram,
InternalRecordableHistogram, internalRecordableHistogram,
isHistogram, isHistogram,
kDestroy, kDestroy,
kHandle, kHandle,
kMap,
createHistogram, createHistogram,
}; };

View File

@ -1,16 +1,19 @@
'use strict'; 'use strict';
const { const {
ReflectConstruct,
SafeMap,
Symbol, Symbol,
} = primordials; } = primordials;
const { const {
codes: { codes: {
ERR_ILLEGAL_CONSTRUCTOR, ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_THIS,
} }
} = require('internal/errors'); } = require('internal/errors');
const { const {
ELDHistogram: _ELDHistogram, createELDHistogram,
} = internalBinding('performance'); } = internalBinding('performance');
const { const {
@ -21,25 +24,38 @@ const {
const { const {
Histogram, Histogram,
kHandle, kHandle,
kMap,
} = require('internal/histogram'); } = require('internal/histogram');
const {
makeTransferable,
} = require('internal/worker/js_transferable');
const kEnabled = Symbol('kEnabled'); const kEnabled = Symbol('kEnabled');
class ELDHistogram extends Histogram { class ELDHistogram extends Histogram {
constructor(i) { constructor(i) {
if (!(i instanceof _ELDHistogram)) {
throw new ERR_ILLEGAL_CONSTRUCTOR(); throw new ERR_ILLEGAL_CONSTRUCTOR();
} }
super(i);
this[kEnabled] = false; /**
} * @returns {boolean}
*/
enable() { enable() {
if (this[kEnabled] === undefined)
throw new ERR_INVALID_THIS('ELDHistogram');
if (this[kEnabled]) return false; if (this[kEnabled]) return false;
this[kEnabled] = true; this[kEnabled] = true;
this[kHandle].start(); this[kHandle].start();
return true; return true;
} }
/**
* @returns {boolean}
*/
disable() { disable() {
if (this[kEnabled] === undefined)
throw new ERR_INVALID_THIS('ELDHistogram');
if (!this[kEnabled]) return false; if (!this[kEnabled]) return false;
this[kEnabled] = false; this[kEnabled] = false;
this[kHandle].stop(); this[kHandle].stop();
@ -47,13 +63,24 @@ class ELDHistogram extends Histogram {
} }
} }
/**
* @param {{
* resolution : number
* }} [options]
* @returns {ELDHistogram}
*/
function monitorEventLoopDelay(options = {}) { function monitorEventLoopDelay(options = {}) {
validateObject(options, 'options'); validateObject(options, 'options');
const { resolution = 10 } = options; const { resolution = 10 } = options;
validateInteger(resolution, 'options.resolution', 1); 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; module.exports = monitorEventLoopDelay;

View File

@ -13,35 +13,49 @@ void Histogram::Reset() {
Mutex::ScopedLock lock(mutex_); Mutex::ScopedLock lock(mutex_);
hdr_reset(histogram_.get()); hdr_reset(histogram_.get());
exceeds_ = 0; exceeds_ = 0;
count_ = 0;
prev_ = 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_); Mutex::ScopedLock lock(mutex_);
return hdr_min(histogram_.get()); return hdr_min(histogram_.get());
} }
int64_t Histogram::Max() { int64_t Histogram::Max() const {
Mutex::ScopedLock lock(mutex_); Mutex::ScopedLock lock(mutex_);
return hdr_max(histogram_.get()); return hdr_max(histogram_.get());
} }
double Histogram::Mean() { double Histogram::Mean() const {
Mutex::ScopedLock lock(mutex_); Mutex::ScopedLock lock(mutex_);
return hdr_mean(histogram_.get()); return hdr_mean(histogram_.get());
} }
double Histogram::Stddev() { double Histogram::Stddev() const {
Mutex::ScopedLock lock(mutex_); Mutex::ScopedLock lock(mutex_);
return hdr_stddev(histogram_.get()); return hdr_stddev(histogram_.get());
} }
double Histogram::Percentile(double percentile) { int64_t Histogram::Percentile(double percentile) const {
Mutex::ScopedLock lock(mutex_); Mutex::ScopedLock lock(mutex_);
CHECK_GT(percentile, 0); CHECK_GT(percentile, 0);
CHECK_LE(percentile, 100); CHECK_LE(percentile, 100);
return static_cast<double>( return hdr_value_at_percentile(histogram_.get(), percentile);
hdr_value_at_percentile(histogram_.get(), percentile));
} }
template <typename Iterator> template <typename Iterator>
@ -51,27 +65,32 @@ void Histogram::Percentiles(Iterator&& fn) {
hdr_iter_percentile_init(&iter, histogram_.get(), 1); hdr_iter_percentile_init(&iter, histogram_.get(), 1);
while (hdr_iter_next(&iter)) { while (hdr_iter_next(&iter)) {
double key = iter.specifics.percentiles.percentile; double key = iter.specifics.percentiles.percentile;
double value = static_cast<double>(iter.value); fn(key, iter.value);
fn(key, value);
} }
} }
bool Histogram::Record(int64_t value) { bool Histogram::Record(int64_t value) {
Mutex::ScopedLock lock(mutex_); 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() { uint64_t Histogram::RecordDelta() {
Mutex::ScopedLock lock(mutex_); Mutex::ScopedLock lock(mutex_);
uint64_t time = uv_hrtime(); uint64_t time = uv_hrtime();
uint64_t delta = 0; int64_t delta = 0;
if (prev_ > 0) { if (prev_ > 0) {
CHECK_GE(time, prev_);
delta = time - prev_; delta = time - prev_;
if (delta > 0) { if (hdr_record_value(histogram_.get(), delta))
if (!hdr_record_value(histogram_.get(), delta) && exceeds_ < 0xFFFFFFFF) count_++;
else
exceeds_++; exceeds_++;
} }
}
prev_ = time; prev_ = time;
return delta; return delta;
} }

View File

@ -10,16 +10,21 @@ namespace node {
using v8::BigInt; using v8::BigInt;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
using v8::FunctionTemplate; using v8::FunctionTemplate;
using v8::Integer;
using v8::Local; using v8::Local;
using v8::Map; using v8::Map;
using v8::Number; using v8::Number;
using v8::Object; using v8::Object;
using v8::String; using v8::String;
using v8::Uint32;
using v8::Value; using v8::Value;
Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { Histogram::Histogram(const Options& options) {
hdr_histogram* histogram; 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); histogram_.reset(histogram);
} }
@ -27,8 +32,8 @@ void Histogram::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("histogram", GetMemorySize()); tracker->TrackFieldWithSize("histogram", GetMemorySize());
} }
HistogramImpl::HistogramImpl(int64_t lowest, int64_t highest, int figures) HistogramImpl::HistogramImpl(const Histogram::Options& options)
: histogram_(new Histogram(lowest, highest, figures)) {} : histogram_(new Histogram(options)) {}
HistogramImpl::HistogramImpl(std::shared_ptr<Histogram> histogram) HistogramImpl::HistogramImpl(std::shared_ptr<Histogram> histogram)
: histogram_(std::move(histogram)) {} : histogram_(std::move(histogram)) {}
@ -36,11 +41,9 @@ HistogramImpl::HistogramImpl(std::shared_ptr<Histogram> histogram)
HistogramBase::HistogramBase( HistogramBase::HistogramBase(
Environment* env, Environment* env,
Local<Object> wrap, Local<Object> wrap,
int64_t lowest, const Histogram::Options& options)
int64_t highest,
int figures)
: BaseObject(env, wrap), : BaseObject(env, wrap),
HistogramImpl(lowest, highest, figures) { HistogramImpl(options) {
MakeWeak(); MakeWeak();
} }
@ -57,6 +60,22 @@ void HistogramBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("histogram", histogram()); 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) { void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram; HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
@ -64,6 +83,13 @@ void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(value); 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) { void HistogramBase::GetMax(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram; HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
@ -71,6 +97,14 @@ void HistogramBase::GetMax(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(value); 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) { void HistogramBase::GetMean(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram; HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
@ -84,6 +118,14 @@ void HistogramBase::GetExceeds(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(value); 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) { void HistogramBase::GetStddev(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram; HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); 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()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsNumber()); CHECK(args[0]->IsNumber());
double percentile = args[0].As<Number>()->Value(); 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) { 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()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsMap()); CHECK(args[0]->IsMap());
Local<Map> map = args[0].As<Map>(); 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( map->Set(
env->context(), env->context(),
Number::New(env->isolate(), key), Number::New(env->isolate(), key),
Number::New(env->isolate(), value)).IsEmpty(); 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),
BigInt::New(env->isolate(), value)).IsEmpty();
}); });
} }
@ -138,11 +207,22 @@ void HistogramBase::Record(const FunctionCallbackInfo<Value>& args) {
(*histogram)->Record(value); (*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( BaseObjectPtr<HistogramBase> HistogramBase::Create(
Environment* env, Environment* env,
int64_t lowest, const Histogram::Options& options) {
int64_t highest,
int figures) {
Local<Object> obj; Local<Object> obj;
if (!GetConstructorTemplate(env) if (!GetConstructorTemplate(env)
->InstanceTemplate() ->InstanceTemplate()
@ -150,8 +230,7 @@ BaseObjectPtr<HistogramBase> HistogramBase::Create(
return BaseObjectPtr<HistogramBase>(); return BaseObjectPtr<HistogramBase>();
} }
return MakeBaseObject<HistogramBase>( return MakeBaseObject<HistogramBase>(env, obj, options);
env, obj, lowest, highest, figures);
} }
BaseObjectPtr<HistogramBase> HistogramBase::Create( BaseObjectPtr<HistogramBase> HistogramBase::Create(
@ -169,7 +248,32 @@ BaseObjectPtr<HistogramBase> HistogramBase::Create(
void HistogramBase::New(const FunctionCallbackInfo<Value>& args) { void HistogramBase::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall()); CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args); 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( Local<FunctionTemplate> HistogramBase::GetConstructorTemplate(
@ -184,16 +288,26 @@ Local<FunctionTemplate> HistogramBase::GetConstructorTemplate(
tmpl->InstanceTemplate()->SetInternalFieldCount( tmpl->InstanceTemplate()->SetInternalFieldCount(
HistogramBase::kInternalFieldCount); HistogramBase::kInternalFieldCount);
env->SetProtoMethodNoSideEffect(tmpl, "count", GetCount);
env->SetProtoMethodNoSideEffect(tmpl, "countBigInt", GetCountBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds); env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds);
env->SetProtoMethodNoSideEffect(tmpl, "exceedsBigInt", GetExceedsBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin); env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin);
env->SetProtoMethodNoSideEffect(tmpl, "minBigInt", GetMinBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax); env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax);
env->SetProtoMethodNoSideEffect(tmpl, "maxBigInt", GetMaxBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean); env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean);
env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev); env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev);
env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile); env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile);
env->SetProtoMethodNoSideEffect(tmpl, "percentileBigInt",
GetPercentileBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles); env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles);
env->SetProtoMethodNoSideEffect(tmpl, "percentilesBigInt",
GetPercentilesBigInt);
env->SetProtoMethod(tmpl, "reset", DoReset); env->SetProtoMethod(tmpl, "reset", DoReset);
env->SetProtoMethod(tmpl, "record", Record); env->SetProtoMethod(tmpl, "record", Record);
env->SetProtoMethod(tmpl, "recordDelta", RecordDelta); env->SetProtoMethod(tmpl, "recordDelta", RecordDelta);
env->SetProtoMethod(tmpl, "add", Add);
env->set_histogram_ctor_template(tmpl); env->set_histogram_ctor_template(tmpl);
} }
return tmpl; return tmpl;
@ -202,16 +316,24 @@ Local<FunctionTemplate> HistogramBase::GetConstructorTemplate(
void HistogramBase::RegisterExternalReferences( void HistogramBase::RegisterExternalReferences(
ExternalReferenceRegistry* registry) { ExternalReferenceRegistry* registry) {
registry->Register(New); registry->Register(New);
registry->Register(GetCount);
registry->Register(GetCountBigInt);
registry->Register(GetExceeds); registry->Register(GetExceeds);
registry->Register(GetExceedsBigInt);
registry->Register(GetMin); registry->Register(GetMin);
registry->Register(GetMinBigInt);
registry->Register(GetMax); registry->Register(GetMax);
registry->Register(GetMaxBigInt);
registry->Register(GetMean); registry->Register(GetMean);
registry->Register(GetStddev); registry->Register(GetStddev);
registry->Register(GetPercentile); registry->Register(GetPercentile);
registry->Register(GetPercentileBigInt);
registry->Register(GetPercentiles); registry->Register(GetPercentiles);
registry->Register(GetPercentilesBigInt);
registry->Register(DoReset); registry->Register(DoReset);
registry->Register(Record); registry->Register(Record);
registry->Register(RecordDelta); registry->Register(RecordDelta);
registry->Register(Add);
} }
void HistogramBase::Initialize(Environment* env, Local<Object> target) { void HistogramBase::Initialize(Environment* env, Local<Object> target) {
@ -242,13 +364,22 @@ Local<FunctionTemplate> IntervalHistogram::GetConstructorTemplate(
tmpl->Inherit(HandleWrap::GetConstructorTemplate(env)); tmpl->Inherit(HandleWrap::GetConstructorTemplate(env));
tmpl->InstanceTemplate()->SetInternalFieldCount( tmpl->InstanceTemplate()->SetInternalFieldCount(
HistogramBase::kInternalFieldCount); HistogramBase::kInternalFieldCount);
env->SetProtoMethodNoSideEffect(tmpl, "count", GetCount);
env->SetProtoMethodNoSideEffect(tmpl, "countBigInt", GetCountBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds); env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds);
env->SetProtoMethodNoSideEffect(tmpl, "exceedsBigInt", GetExceedsBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin); env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin);
env->SetProtoMethodNoSideEffect(tmpl, "minBigInt", GetMinBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax); env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax);
env->SetProtoMethodNoSideEffect(tmpl, "maxBigInt", GetMaxBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean); env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean);
env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev); env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev);
env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile); env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile);
env->SetProtoMethodNoSideEffect(tmpl, "percentileBigInt",
GetPercentileBigInt);
env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles); env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles);
env->SetProtoMethodNoSideEffect(tmpl, "percentilesBigInt",
GetPercentilesBigInt);
env->SetProtoMethod(tmpl, "reset", DoReset); env->SetProtoMethod(tmpl, "reset", DoReset);
env->SetProtoMethod(tmpl, "start", Start); env->SetProtoMethod(tmpl, "start", Start);
env->SetProtoMethod(tmpl, "stop", Stop); env->SetProtoMethod(tmpl, "stop", Stop);
@ -259,13 +390,20 @@ Local<FunctionTemplate> IntervalHistogram::GetConstructorTemplate(
void IntervalHistogram::RegisterExternalReferences( void IntervalHistogram::RegisterExternalReferences(
ExternalReferenceRegistry* registry) { ExternalReferenceRegistry* registry) {
registry->Register(GetCount);
registry->Register(GetCountBigInt);
registry->Register(GetExceeds); registry->Register(GetExceeds);
registry->Register(GetExceedsBigInt);
registry->Register(GetMin); registry->Register(GetMin);
registry->Register(GetMinBigInt);
registry->Register(GetMax); registry->Register(GetMax);
registry->Register(GetMaxBigInt);
registry->Register(GetMean); registry->Register(GetMean);
registry->Register(GetStddev); registry->Register(GetStddev);
registry->Register(GetPercentile); registry->Register(GetPercentile);
registry->Register(GetPercentileBigInt);
registry->Register(GetPercentiles); registry->Register(GetPercentiles);
registry->Register(GetPercentilesBigInt);
registry->Register(DoReset); registry->Register(DoReset);
registry->Register(Start); registry->Register(Start);
registry->Register(Stop); registry->Register(Stop);
@ -276,24 +414,48 @@ IntervalHistogram::IntervalHistogram(
Local<Object> wrap, Local<Object> wrap,
AsyncWrap::ProviderType type, AsyncWrap::ProviderType type,
int32_t interval, int32_t interval,
int64_t lowest, std::function<void(Histogram&)> on_interval,
int64_t highest, const Histogram::Options& options)
int figures)
: HandleWrap( : HandleWrap(
env, env,
wrap, wrap,
reinterpret_cast<uv_handle_t*>(&timer_), reinterpret_cast<uv_handle_t*>(&timer_),
type), type),
HistogramImpl(lowest, highest, figures), HistogramImpl(options),
interval_(interval) { interval_(interval),
on_interval_(std::move(on_interval)) {
MakeWeak(); MakeWeak();
uv_timer_init(env->event_loop(), &timer_); 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) { void IntervalHistogram::TimerCB(uv_timer_t* handle) {
IntervalHistogram* histogram = IntervalHistogram* histogram =
ContainerOf(&IntervalHistogram::timer_, handle); ContainerOf(&IntervalHistogram::timer_, handle);
histogram->OnInterval();
Histogram* h = histogram->histogram().get();
histogram->on_interval_(*h);
} }
void IntervalHistogram::MemoryInfo(MemoryTracker* tracker) const { void IntervalHistogram::MemoryInfo(MemoryTracker* tracker) const {
@ -327,6 +489,22 @@ void IntervalHistogram::Stop(const FunctionCallbackInfo<Value>& args) {
histogram->OnStop(); 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) { void IntervalHistogram::GetMin(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram; IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
@ -334,6 +512,13 @@ void IntervalHistogram::GetMin(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(value); 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) { void IntervalHistogram::GetMax(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram; IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
@ -341,6 +526,13 @@ void IntervalHistogram::GetMax(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(value); 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) { void IntervalHistogram::GetMean(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram; IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
@ -354,6 +546,15 @@ void IntervalHistogram::GetExceeds(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(value); 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) { void IntervalHistogram::GetStddev(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram; IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); 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()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsNumber()); CHECK(args[0]->IsNumber());
double percentile = args[0].As<Number>()->Value(); 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( void IntervalHistogram::GetPercentiles(
@ -375,11 +588,26 @@ void IntervalHistogram::GetPercentiles(
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsMap()); CHECK(args[0]->IsMap());
Local<Map> map = args[0].As<Map>(); 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( map->Set(
env->context(), env->context(),
Number::New(env->isolate(), key), Number::New(env->isolate(), key),
Number::New(env->isolate(), value)).IsEmpty(); 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),
BigInt::New(env->isolate(), value)).IsEmpty();
}); });
} }

View File

@ -24,23 +24,29 @@ constexpr int kDefaultHistogramFigures = 3;
class Histogram : public MemoryRetainer { class Histogram : public MemoryRetainer {
public: public:
Histogram( struct Options {
int64_t lowest = 1, int64_t lowest = 1;
int64_t highest = std::numeric_limits<int64_t>::max(), int64_t highest = std::numeric_limits<int64_t>::max();
int figures = kDefaultHistogramFigures); int figures = kDefaultHistogramFigures;
};
explicit Histogram(const Options& options);
virtual ~Histogram() = default; virtual ~Histogram() = default;
inline bool Record(int64_t value); inline bool Record(int64_t value);
inline void Reset(); inline void Reset();
inline int64_t Min(); inline int64_t Min() const;
inline int64_t Max(); inline int64_t Max() const;
inline double Mean(); inline double Mean() const;
inline double Stddev(); inline double Stddev() const;
inline double Percentile(double percentile); inline int64_t Percentile(double percentile) const;
inline int64_t Exceeds() const { return exceeds_; } inline size_t Exceeds() const { return exceeds_; }
inline size_t Count() const;
inline uint64_t RecordDelta(); inline uint64_t RecordDelta();
inline double Add(const Histogram& other);
// Iterator is a function type that takes two doubles as argument, one for // Iterator is a function type that takes two doubles as argument, one for
// percentile and one for the value at that percentile. // percentile and one for the value at that percentile.
template <typename Iterator> template <typename Iterator>
@ -55,20 +61,20 @@ class Histogram : public MemoryRetainer {
private: private:
using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>; using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>;
HistogramPointer histogram_; HistogramPointer histogram_;
int64_t exceeds_ = 0;
uint64_t prev_ = 0; uint64_t prev_ = 0;
size_t exceeds_ = 0;
size_t count_ = 0;
Mutex mutex_; Mutex mutex_;
}; };
class HistogramImpl { class HistogramImpl {
public: 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); explicit HistogramImpl(std::shared_ptr<Histogram> histogram);
Histogram* operator->() { return histogram_.get(); } Histogram* operator->() { return histogram_.get(); }
protected:
const std::shared_ptr<Histogram>& histogram() const { return histogram_; } const std::shared_ptr<Histogram>& histogram() const { return histogram_; }
private: private:
@ -84,9 +90,7 @@ class HistogramBase : public BaseObject, public HistogramImpl {
static BaseObjectPtr<HistogramBase> Create( static BaseObjectPtr<HistogramBase> Create(
Environment* env, Environment* env,
int64_t lowest = 1, const Histogram::Options& options = Histogram::Options {});
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
static BaseObjectPtr<HistogramBase> Create( static BaseObjectPtr<HistogramBase> Create(
Environment* env, Environment* env,
@ -98,6 +102,12 @@ class HistogramBase : public BaseObject, public HistogramImpl {
SET_MEMORY_INFO_NAME(HistogramBase) SET_MEMORY_INFO_NAME(HistogramBase)
SET_SELF_SIZE(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 GetMin(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMean(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 GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentile( static void GetPercentile(
const v8::FunctionCallbackInfo<v8::Value>& args); const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentileBigInt(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentiles( static void GetPercentiles(
const v8::FunctionCallbackInfo<v8::Value>& args); 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 DoReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Record(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 RecordDelta(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Add(const v8::FunctionCallbackInfo<v8::Value>& args);
HistogramBase( HistogramBase(
Environment* env, Environment* env,
v8::Local<v8::Object> wrap, v8::Local<v8::Object> wrap,
int64_t lowest = 1, const Histogram::Options& options = Histogram::Options {});
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
HistogramBase( HistogramBase(
Environment* env, Environment* env,
@ -164,23 +177,24 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl {
static BaseObjectPtr<IntervalHistogram> Create( static BaseObjectPtr<IntervalHistogram> Create(
Environment* env, Environment* env,
int64_t lowest = 1, int32_t interval,
int64_t highest = std::numeric_limits<int64_t>::max(), std::function<void(Histogram&)> on_interval,
int figures = kDefaultHistogramFigures); const Histogram::Options& options);
virtual void OnInterval() = 0;
void MemoryInfo(MemoryTracker* tracker) const override;
IntervalHistogram( IntervalHistogram(
Environment* env, Environment* env,
v8::Local<v8::Object> wrap, v8::Local<v8::Object> wrap,
AsyncWrap::ProviderType type, AsyncWrap::ProviderType type,
int32_t interval, int32_t interval,
int64_t lowest = 1, std::function<void(Histogram&)> on_interval,
int64_t highest = std::numeric_limits<int64_t>::max(), const Histogram::Options& options = Histogram::Options {});
int figures = kDefaultHistogramFigures);
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 GetMin(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMean(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 GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentile( static void GetPercentile(
const v8::FunctionCallbackInfo<v8::Value>& args); const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentileBigInt(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentiles( static void GetPercentiles(
const v8::FunctionCallbackInfo<v8::Value>& args); 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 DoReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Stop(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; std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(IntervalHistogram)
SET_SELF_SIZE(IntervalHistogram)
private: private:
static void TimerCB(uv_timer_t* handle); static void TimerCB(uv_timer_t* handle);
void OnStart(StartFlags flags = StartFlags::RESET); void OnStart(StartFlags flags = StartFlags::RESET);
@ -206,6 +228,7 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl {
bool enabled_ = false; bool enabled_ = false;
int32_t interval_ = 0; int32_t interval_ = 0;
std::function<void(Histogram&)> on_interval_;
uv_timer_t timer_; uv_timer_t timer_;
}; };

View File

@ -234,51 +234,25 @@ void LoopIdleTime(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(1.0 * idle_time / 1e6); args.GetReturnValue().Set(1.0 * idle_time / 1e6);
} }
// Event Loop Timing Histogram void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
void ELDHistogram::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall()); int64_t interval = args[0].As<Integer>()->Value();
int64_t resolution = args[0].As<Integer>()->Value(); CHECK_GT(interval, 0);
CHECK_GT(resolution, 0); BaseObjectPtr<IntervalHistogram> histogram =
new ELDHistogram(env, args.This(), resolution); IntervalHistogram::Create(env, interval, [](Histogram& histogram) {
} uint64_t delta = histogram.RecordDelta();
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), TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"delay", delta); "delay", delta);
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"min", histogram()->Min()); "min", histogram.Min());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"max", histogram()->Max()); "max", histogram.Max());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"mean", histogram()->Mean()); "mean", histogram.Mean());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"stddev", histogram()->Stddev()); "stddev", histogram.Stddev());
}, Histogram::Options { 1000 });
args.GetReturnValue().Set(histogram->object());
} }
void GetTimeOrigin(const FunctionCallbackInfo<Value>& args) { void GetTimeOrigin(const FunctionCallbackInfo<Value>& args) {
@ -326,6 +300,7 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "loopIdleTime", LoopIdleTime); env->SetMethod(target, "loopIdleTime", LoopIdleTime);
env->SetMethod(target, "getTimeOrigin", GetTimeOrigin); env->SetMethod(target, "getTimeOrigin", GetTimeOrigin);
env->SetMethod(target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); env->SetMethod(target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp);
env->SetMethod(target, "createELDHistogram", CreateELDHistogram);
Local<Object> constants = Object::New(isolate); Local<Object> constants = Object::New(isolate);
@ -368,7 +343,6 @@ void Initialize(Local<Object> target,
attr).ToChecked(); attr).ToChecked();
HistogramBase::Initialize(env, target); HistogramBase::Initialize(env, target);
ELDHistogram::Initialize(env, target);
} }
void RegisterExternalReferences(ExternalReferenceRegistry* registry) { void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
@ -380,8 +354,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(LoopIdleTime); registry->Register(LoopIdleTime);
registry->Register(GetTimeOrigin); registry->Register(GetTimeOrigin);
registry->Register(GetTimeOriginTimeStamp); registry->Register(GetTimeOriginTimeStamp);
registry->Register(CreateELDHistogram);
HistogramBase::RegisterExternalReferences(registry); HistogramBase::RegisterExternalReferences(registry);
ELDHistogram::RegisterExternalReferences(registry); IntervalHistogram::RegisterExternalReferences(registry);
} }
} // namespace performance } // namespace performance
} // namespace node } // namespace node

View File

@ -160,23 +160,6 @@ struct GCPerformanceEntryTraits {
using GCPerformanceEntry = PerformanceEntry<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 performance
} // namespace node } // namespace node

View File

@ -1,54 +1,75 @@
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
const assert = require('assert');
const {
ok,
strictEqual,
throws,
} = require('assert');
const { const {
createHistogram, createHistogram,
monitorEventLoopDelay, monitorEventLoopDelay,
} = require('perf_hooks'); } = require('perf_hooks');
const { inspect } = require('util'); const { inspect } = require('util');
{ {
const h = createHistogram(); const h = createHistogram();
assert.strictEqual(h.min, 9223372036854776000); strictEqual(h.min, 9223372036854776000);
assert.strictEqual(h.max, 0); strictEqual(h.minBigInt, 9223372036854775807n);
assert.strictEqual(h.exceeds, 0); strictEqual(h.max, 0);
assert(Number.isNaN(h.mean)); strictEqual(h.maxBigInt, 0n);
assert(Number.isNaN(h.stddev)); 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); h.record(1);
strictEqual(h.count, 1);
strictEqual(h.countBigInt, 1n);
[false, '', {}, undefined, null].forEach((i) => { [false, '', {}, undefined, null].forEach((i) => {
assert.throws(() => h.record(i), { throws(() => h.record(i), {
code: 'ERR_INVALID_ARG_TYPE' 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' code: 'ERR_OUT_OF_RANGE'
}); });
assert.strictEqual(h.min, 1); strictEqual(h.min, 1);
assert.strictEqual(h.max, 1); strictEqual(h.minBigInt, 1n);
assert.strictEqual(h.exceeds, 0); strictEqual(h.max, 1);
assert.strictEqual(h.mean, 1); strictEqual(h.maxBigInt, 1n);
assert.strictEqual(h.stddev, 0); strictEqual(h.exceeds, 0);
strictEqual(h.mean, 1);
strictEqual(h.stddev, 0);
assert.strictEqual(h.percentile(1), 1); strictEqual(h.percentile(1), 1);
assert.strictEqual(h.percentile(100), 1); strictEqual(h.percentile(100), 1);
strictEqual(h.percentileBigInt(1), 1n);
strictEqual(h.percentileBigInt(100), 1n);
const mc = new MessageChannel(); const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => { mc.port1.onmessage = common.mustCall(({ data }) => {
assert.strictEqual(h.min, 1); strictEqual(h.min, 1);
assert.strictEqual(h.max, 1); strictEqual(h.max, 1);
assert.strictEqual(h.exceeds, 0); strictEqual(h.exceeds, 0);
assert.strictEqual(h.mean, 1); strictEqual(h.mean, 1);
assert.strictEqual(h.stddev, 0); strictEqual(h.stddev, 0);
data.record(2n); data.record(2n);
data.recordDelta(); data.recordDelta();
assert.strictEqual(h.max, 2); strictEqual(h.max, 2);
mc.port1.close(); mc.port1.close();
}); });
@ -57,13 +78,15 @@ const { inspect } = require('util');
{ {
const e = monitorEventLoopDelay(); const e = monitorEventLoopDelay();
strictEqual(e.count, 0);
e.enable(); e.enable();
const mc = new MessageChannel(); const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => { mc.port1.onmessage = common.mustCall(({ data }) => {
assert(typeof data.min, 'number'); strictEqual(typeof data.min, 'number');
assert(data.min > 0); ok(data.min > 0);
assert.strictEqual(data.disable, undefined); ok(data.count > 0);
assert.strictEqual(data.enable, undefined); strictEqual(data.disable, undefined);
strictEqual(data.enable, undefined);
mc.port1.close(); mc.port1.close();
}); });
setTimeout(() => mc.port2.postMessage(e), 100); setTimeout(() => mc.port2.postMessage(e), 100);
@ -71,12 +94,66 @@ const { inspect } = require('util');
{ {
const h = createHistogram(); const h = createHistogram();
assert(inspect(h, { depth: null }).startsWith('Histogram')); ok(inspect(h, { depth: null }).startsWith('Histogram'));
assert.strictEqual(inspect(h, { depth: -1 }), '[RecordableHistogram]'); strictEqual(inspect(h, { depth: -1 }), '[RecordableHistogram]');
} }
{ {
// Tests that RecordableHistogram is impossible to construct manually // Tests that RecordableHistogram is impossible to construct manually
const h = createHistogram(); 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',
});
});
} }