'use strict'; const { MapPrototypeEntries, NumberIsNaN, NumberIsInteger, 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_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, makeTransferable, } = require('internal/worker/js_transferable'); function isHistogram(object) { return object?.[kHandle] !== undefined; } class Histogram { constructor() { 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_INVALID_ARG_VALUE.RangeError('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} */ 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: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 { constructor() { 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; } if (!NumberIsInteger(val)) throw new ERR_INVALID_ARG_TYPE('val', ['integer', 'bigint'], val); if (val < 1 || val > NumberMAX_SAFE_INTEGER) throw new ERR_OUT_OF_RANGE('val', 'a safe integer greater than 0', val); 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' }; } [kDeserialize]({ handle }) { this[kHandle] = handle; } } function internalHistogram(handle) { return makeTransferable(ReflectConstruct( function() { this[kHandle] = handle; this[kMap] = new SafeMap(); }, [], 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 = 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); } validateUint32(figures, 'options.figures', 1, 5); return internalRecordableHistogram(new _Histogram(lowest, highest, figures)); } module.exports = { Histogram, RecordableHistogram, internalHistogram, internalRecordableHistogram, isHistogram, kDestroy, kHandle, kMap, createHistogram, };