'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} */ 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} */ 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, };