2020-02-27 21:14:38 +00:00
|
|
|
'use strict';
|
|
|
|
|
2021-01-30 17:26:15 +00:00
|
|
|
const {
|
2021-12-12 00:59:48 +00:00
|
|
|
MapPrototypeEntries,
|
2021-01-30 17:26:15 +00:00
|
|
|
NumberIsNaN,
|
|
|
|
NumberMAX_SAFE_INTEGER,
|
2021-12-12 00:59:48 +00:00
|
|
|
ObjectFromEntries,
|
|
|
|
ReflectConstruct,
|
2021-01-30 17:26:15 +00:00
|
|
|
SafeMap,
|
|
|
|
Symbol,
|
|
|
|
} = primordials;
|
|
|
|
|
|
|
|
const {
|
2023-02-26 10:34:02 +00:00
|
|
|
Histogram: _Histogram,
|
2021-01-30 17:26:15 +00:00
|
|
|
} = internalBinding('performance');
|
|
|
|
|
2020-02-27 21:14:38 +00:00
|
|
|
const {
|
|
|
|
customInspectSymbol: kInspect,
|
2022-05-21 09:59:38 +00:00
|
|
|
kEmptyObject,
|
2020-02-27 21:14:38 +00:00
|
|
|
} = require('internal/util');
|
|
|
|
|
2021-01-30 17:26:15 +00:00
|
|
|
const { inspect } = require('util');
|
|
|
|
|
|
|
|
const {
|
|
|
|
codes: {
|
2021-07-28 01:41:18 +00:00
|
|
|
ERR_ILLEGAL_CONSTRUCTOR,
|
2021-01-30 17:26:15 +00:00
|
|
|
ERR_INVALID_ARG_TYPE,
|
2024-04-23 17:05:38 +00:00
|
|
|
ERR_INVALID_ARG_VALUE,
|
2021-12-12 00:59:48 +00:00
|
|
|
ERR_INVALID_THIS,
|
2021-01-30 17:26:15 +00:00
|
|
|
ERR_OUT_OF_RANGE,
|
|
|
|
},
|
|
|
|
} = require('internal/errors');
|
2020-02-27 21:14:38 +00:00
|
|
|
|
2021-01-30 17:26:15 +00:00
|
|
|
const {
|
2021-12-12 00:59:48 +00:00
|
|
|
validateInteger,
|
2021-01-30 17:26:15 +00:00
|
|
|
validateNumber,
|
2021-12-12 00:59:48 +00:00
|
|
|
validateObject,
|
2021-01-30 17:26:15 +00:00
|
|
|
} = require('internal/validators');
|
2020-02-27 21:14:38 +00:00
|
|
|
|
|
|
|
const kDestroy = Symbol('kDestroy');
|
|
|
|
const kHandle = Symbol('kHandle');
|
2021-01-30 17:26:15 +00:00
|
|
|
const kMap = Symbol('kMap');
|
2021-12-12 00:59:48 +00:00
|
|
|
const kRecordable = Symbol('kRecordable');
|
2020-02-27 21:14:38 +00:00
|
|
|
|
2021-01-30 17:26:15 +00:00
|
|
|
const {
|
|
|
|
kClone,
|
|
|
|
kDeserialize,
|
2023-07-07 17:00:00 +00:00
|
|
|
markTransferMode,
|
2021-01-30 17:26:15 +00:00
|
|
|
} = require('internal/worker/js_transferable');
|
2020-02-27 21:14:38 +00:00
|
|
|
|
2021-02-22 17:16:32 +00:00
|
|
|
function isHistogram(object) {
|
|
|
|
return object?.[kHandle] !== undefined;
|
|
|
|
}
|
|
|
|
|
2023-10-06 00:39:55 +00:00
|
|
|
const kSkipThrow = Symbol('kSkipThrow');
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
class Histogram {
|
2023-10-06 00:39:55 +00:00
|
|
|
constructor(skipThrowSymbol = undefined) {
|
|
|
|
if (skipThrowSymbol !== kSkipThrow) {
|
|
|
|
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
|
|
|
}
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 17:26:15 +00:00
|
|
|
[kInspect](depth, options) {
|
|
|
|
if (depth < 0)
|
|
|
|
return this;
|
|
|
|
|
|
|
|
const opts = {
|
|
|
|
...options,
|
2023-02-26 10:34:02 +00:00
|
|
|
depth: options.depth == null ? null : options.depth - 1,
|
2021-01-30 17:26:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return `Histogram ${inspect({
|
2020-02-27 21:14:38 +00:00
|
|
|
min: this.min,
|
|
|
|
max: this.max,
|
|
|
|
mean: this.mean,
|
|
|
|
exceeds: this.exceeds,
|
|
|
|
stddev: this.stddev,
|
2021-12-12 00:59:48 +00:00
|
|
|
count: this.count,
|
2020-02-27 21:14:38 +00:00
|
|
|
percentiles: this.percentiles,
|
2021-01-30 17:26:15 +00:00
|
|
|
}, opts)}`;
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @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}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
get min() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2020-12-09 13:43:07 +00:00
|
|
|
return this[kHandle]?.min();
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {bigint}
|
|
|
|
*/
|
|
|
|
get minBigInt() {
|
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
|
|
|
return this[kHandle]?.minBigInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {number}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
get max() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2020-12-09 13:43:07 +00:00
|
|
|
return this[kHandle]?.max();
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {bigint}
|
|
|
|
*/
|
|
|
|
get maxBigInt() {
|
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
|
|
|
return this[kHandle]?.maxBigInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {number}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
get mean() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2020-12-09 13:43:07 +00:00
|
|
|
return this[kHandle]?.mean();
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {number}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
get exceeds() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2020-12-09 13:43:07 +00:00
|
|
|
return this[kHandle]?.exceeds();
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {bigint}
|
|
|
|
*/
|
|
|
|
get exceedsBigInt() {
|
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
|
|
|
return this[kHandle]?.exceedsBigInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {number}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
get stddev() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2020-12-09 13:43:07 +00:00
|
|
|
return this[kHandle]?.stddev();
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @param {number} percentile
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
percentile(percentile) {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2021-01-19 11:23:27 +00:00
|
|
|
validateNumber(percentile, 'percentile');
|
2021-01-15 04:49:53 +00:00
|
|
|
if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100)
|
2023-02-03 21:12:05 +00:00
|
|
|
throw new ERR_OUT_OF_RANGE('percentile', '> 0 && <= 100', percentile);
|
2020-02-27 21:14:38 +00:00
|
|
|
|
2020-12-09 13:43:07 +00:00
|
|
|
return this[kHandle]?.percentile(percentile);
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @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)
|
2023-02-03 21:12:05 +00:00
|
|
|
throw new ERR_OUT_OF_RANGE('percentile', '> 0 && <= 100', percentile);
|
2021-12-12 00:59:48 +00:00
|
|
|
|
|
|
|
return this[kHandle]?.percentileBigInt(percentile);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @readonly
|
|
|
|
* @type {Map<number,number>}
|
|
|
|
*/
|
2020-02-27 21:14:38 +00:00
|
|
|
get percentiles() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
2021-01-30 17:26:15 +00:00
|
|
|
this[kMap].clear();
|
|
|
|
this[kHandle]?.percentiles(this[kMap]);
|
|
|
|
return this[kMap];
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @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];
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
reset() {
|
|
|
|
if (!isHistogram(this))
|
|
|
|
throw new ERR_INVALID_THIS('Histogram');
|
|
|
|
this[kHandle]?.reset();
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
2021-01-30 17:26:15 +00:00
|
|
|
|
|
|
|
[kClone]() {
|
|
|
|
const handle = this[kHandle];
|
|
|
|
return {
|
|
|
|
data: { handle },
|
2023-10-06 00:39:55 +00:00
|
|
|
deserializeInfo: 'internal/histogram:ClonedHistogram',
|
2021-01-30 17:26:15 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
[kDeserialize]({ handle }) {
|
|
|
|
this[kHandle] = handle;
|
|
|
|
}
|
2021-12-12 00:59:48 +00:00
|
|
|
|
|
|
|
toJSON() {
|
|
|
|
return {
|
|
|
|
count: this.count,
|
|
|
|
min: this.min,
|
|
|
|
max: this.max,
|
|
|
|
mean: this.mean,
|
|
|
|
exceeds: this.exceeds,
|
|
|
|
stddev: this.stddev,
|
2023-02-26 10:34:02 +00:00
|
|
|
percentiles: ObjectFromEntries(MapPrototypeEntries(this.percentiles)),
|
2021-12-12 00:59:48 +00:00
|
|
|
};
|
|
|
|
}
|
2021-01-30 17:26:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class RecordableHistogram extends Histogram {
|
2023-10-06 00:39:55 +00:00
|
|
|
constructor(skipThrowSymbol = undefined) {
|
|
|
|
if (skipThrowSymbol !== kSkipThrow) {
|
|
|
|
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
|
|
|
}
|
|
|
|
|
|
|
|
super(skipThrowSymbol);
|
2021-01-30 17:26:15 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @param {number|bigint} val
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2021-01-30 17:26:15 +00:00
|
|
|
record(val) {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (this[kRecordable] === undefined)
|
|
|
|
throw new ERR_INVALID_THIS('RecordableHistogram');
|
2021-01-30 17:26:15 +00:00
|
|
|
if (typeof val === 'bigint') {
|
|
|
|
this[kHandle]?.record(val);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-02-03 21:12:05 +00:00
|
|
|
validateInteger(val, 'val', 1);
|
2021-01-30 17:26:15 +00:00
|
|
|
|
|
|
|
this[kHandle]?.record(val);
|
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2021-01-30 17:26:15 +00:00
|
|
|
recordDelta() {
|
2021-12-12 00:59:48 +00:00
|
|
|
if (this[kRecordable] === undefined)
|
|
|
|
throw new ERR_INVALID_THIS('RecordableHistogram');
|
2021-01-30 17:26:15 +00:00
|
|
|
this[kHandle]?.recordDelta();
|
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
/**
|
|
|
|
* @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]);
|
|
|
|
}
|
|
|
|
|
2021-01-30 17:26:15 +00:00
|
|
|
[kClone]() {
|
|
|
|
const handle = this[kHandle];
|
|
|
|
return {
|
|
|
|
data: { handle },
|
2023-10-06 00:39:55 +00:00
|
|
|
deserializeInfo: 'internal/histogram:ClonedRecordableHistogram',
|
2021-01-30 17:26:15 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-12 00:59:48 +00:00
|
|
|
[kDeserialize]({ handle }) {
|
2021-01-30 17:26:15 +00:00
|
|
|
this[kHandle] = handle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 00:39:55 +00:00
|
|
|
function ClonedHistogram(handle) {
|
2023-07-07 17:00:00 +00:00
|
|
|
return ReflectConstruct(
|
2021-12-12 00:59:48 +00:00
|
|
|
function() {
|
2023-07-07 17:00:00 +00:00
|
|
|
markTransferMode(this, true, false);
|
2021-12-12 00:59:48 +00:00
|
|
|
this[kHandle] = handle;
|
|
|
|
this[kMap] = new SafeMap();
|
2023-07-07 17:00:00 +00:00
|
|
|
}, [], Histogram);
|
2021-01-30 17:26:15 +00:00
|
|
|
}
|
2021-12-12 00:59:48 +00:00
|
|
|
|
2023-10-06 00:39:55 +00:00
|
|
|
ClonedHistogram.prototype[kDeserialize] = () => { };
|
|
|
|
|
|
|
|
function ClonedRecordableHistogram(handle) {
|
|
|
|
const histogram = new RecordableHistogram(kSkipThrow);
|
|
|
|
|
|
|
|
markTransferMode(histogram, true, false);
|
|
|
|
histogram[kRecordable] = true;
|
|
|
|
histogram[kMap] = new SafeMap();
|
|
|
|
histogram[kHandle] = handle;
|
|
|
|
histogram.constructor = RecordableHistogram;
|
|
|
|
|
|
|
|
return histogram;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClonedRecordableHistogram.prototype[kDeserialize] = () => { };
|
|
|
|
|
|
|
|
function createRecordableHistogram(handle) {
|
|
|
|
return new ClonedRecordableHistogram(handle);
|
2021-12-12 00:59:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {{
|
|
|
|
* lowest? : number,
|
|
|
|
* highest? : number,
|
|
|
|
* figures? : number
|
|
|
|
* }} [options]
|
|
|
|
* @returns {RecordableHistogram}
|
|
|
|
*/
|
2022-05-21 09:59:38 +00:00
|
|
|
function createHistogram(options = kEmptyObject) {
|
2021-12-12 00:59:48 +00:00
|
|
|
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);
|
|
|
|
}
|
2023-01-10 20:11:06 +00:00
|
|
|
validateInteger(figures, 'options.figures', 1, 5);
|
2023-10-06 00:39:55 +00:00
|
|
|
return createRecordableHistogram(new _Histogram(lowest, highest, figures));
|
2020-02-27 21:14:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
Histogram,
|
2021-01-30 17:26:15 +00:00
|
|
|
RecordableHistogram,
|
2023-10-06 00:39:55 +00:00
|
|
|
ClonedHistogram,
|
|
|
|
ClonedRecordableHistogram,
|
2021-02-22 17:16:32 +00:00
|
|
|
isHistogram,
|
2020-02-27 21:14:38 +00:00
|
|
|
kDestroy,
|
|
|
|
kHandle,
|
2021-12-12 00:59:48 +00:00
|
|
|
kMap,
|
2021-01-30 17:26:15 +00:00
|
|
|
createHistogram,
|
2020-02-27 21:14:38 +00:00
|
|
|
};
|