node/lib/internal/histogram.js

396 lines
8.3 KiB
JavaScript
Raw Permalink Normal View History

'use strict';
const {
MapPrototypeEntries,
NumberIsNaN,
NumberMAX_SAFE_INTEGER,
ObjectFromEntries,
ReflectConstruct,
SafeMap,
Symbol,
} = primordials;
const {
Histogram: _Histogram,
} = internalBinding('performance');
const {
customInspectSymbol: kInspect,
kEmptyObject,
} = require('internal/util');
const { inspect } = require('util');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_THIS,
ERR_OUT_OF_RANGE,
},
} = require('internal/errors');
const {
validateInteger,
validateNumber,
validateObject,
} = require('internal/validators');
const kDestroy = Symbol('kDestroy');
const kHandle = Symbol('kHandle');
const kMap = Symbol('kMap');
const kRecordable = Symbol('kRecordable');
const {
kClone,
kDeserialize,
markTransferMode,
} = require('internal/worker/js_transferable');
function isHistogram(object) {
return object?.[kHandle] !== undefined;
}
const kSkipThrow = Symbol('kSkipThrow');
class Histogram {
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `Histogram ${inspect({
min: this.min,
max: this.max,
mean: this.mean,
exceeds: this.exceeds,
stddev: this.stddev,
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)
throw new ERR_OUT_OF_RANGE('percentile', '> 0 && <= 100', 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_OUT_OF_RANGE('percentile', '> 0 && <= 100', 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];
}
/**
* @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];
}
/**
* @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:ClonedHistogram',
};
}
[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 {
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
super(skipThrowSymbol);
}
/**
* @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;
}
validateInteger(val, 'val', 1);
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:ClonedRecordableHistogram',
};
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
}
}
function ClonedHistogram(handle) {
return ReflectConstruct(
function() {
markTransferMode(this, true, false);
this[kHandle] = handle;
this[kMap] = new SafeMap();
}, [], Histogram);
}
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);
}
/**
* @param {{
* lowest? : number,
* highest? : number,
* figures? : number
* }} [options]
* @returns {RecordableHistogram}
*/
function createHistogram(options = kEmptyObject) {
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);
}
validateInteger(figures, 'options.figures', 1, 5);
return createRecordableHistogram(new _Histogram(lowest, highest, figures));
}
module.exports = {
Histogram,
RecordableHistogram,
ClonedHistogram,
ClonedRecordableHistogram,
isHistogram,
kDestroy,
kHandle,
kMap,
createHistogram,
};