• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  MapPrototypeEntries,
5  NumberIsNaN,
6  NumberIsInteger,
7  NumberMAX_SAFE_INTEGER,
8  ObjectFromEntries,
9  ReflectConstruct,
10  SafeMap,
11  Symbol,
12} = primordials;
13
14const {
15  Histogram: _Histogram,
16} = internalBinding('performance');
17
18const {
19  customInspectSymbol: kInspect,
20  kEmptyObject,
21} = require('internal/util');
22
23const { inspect } = require('util');
24
25const {
26  codes: {
27    ERR_ILLEGAL_CONSTRUCTOR,
28    ERR_INVALID_ARG_VALUE,
29    ERR_INVALID_ARG_TYPE,
30    ERR_INVALID_THIS,
31    ERR_OUT_OF_RANGE,
32  },
33} = require('internal/errors');
34
35const {
36  validateInteger,
37  validateNumber,
38  validateObject,
39} = require('internal/validators');
40
41const kDestroy = Symbol('kDestroy');
42const kHandle = Symbol('kHandle');
43const kMap = Symbol('kMap');
44const kRecordable = Symbol('kRecordable');
45
46const {
47  kClone,
48  kDeserialize,
49  makeTransferable,
50} = require('internal/worker/js_transferable');
51
52function isHistogram(object) {
53  return object?.[kHandle] !== undefined;
54}
55
56class Histogram {
57  constructor() {
58    throw new ERR_ILLEGAL_CONSTRUCTOR();
59  }
60
61  [kInspect](depth, options) {
62    if (depth < 0)
63      return this;
64
65    const opts = {
66      ...options,
67      depth: options.depth == null ? null : options.depth - 1,
68    };
69
70    return `Histogram ${inspect({
71      min: this.min,
72      max: this.max,
73      mean: this.mean,
74      exceeds: this.exceeds,
75      stddev: this.stddev,
76      count: this.count,
77      percentiles: this.percentiles,
78    }, opts)}`;
79  }
80
81  /**
82   * @readonly
83   * @type {number}
84   */
85  get count() {
86    if (!isHistogram(this))
87      throw new ERR_INVALID_THIS('Histogram');
88    return this[kHandle]?.count();
89  }
90
91  /**
92   * @readonly
93   * @type {bigint}
94   */
95  get countBigInt() {
96    if (!isHistogram(this))
97      throw new ERR_INVALID_THIS('Histogram');
98    return this[kHandle]?.countBigInt();
99  }
100
101  /**
102   * @readonly
103   * @type {number}
104   */
105  get min() {
106    if (!isHistogram(this))
107      throw new ERR_INVALID_THIS('Histogram');
108    return this[kHandle]?.min();
109  }
110
111  /**
112   * @readonly
113   * @type {bigint}
114   */
115  get minBigInt() {
116    if (!isHistogram(this))
117      throw new ERR_INVALID_THIS('Histogram');
118    return this[kHandle]?.minBigInt();
119  }
120
121  /**
122   * @readonly
123   * @type {number}
124   */
125  get max() {
126    if (!isHistogram(this))
127      throw new ERR_INVALID_THIS('Histogram');
128    return this[kHandle]?.max();
129  }
130
131  /**
132   * @readonly
133   * @type {bigint}
134   */
135  get maxBigInt() {
136    if (!isHistogram(this))
137      throw new ERR_INVALID_THIS('Histogram');
138    return this[kHandle]?.maxBigInt();
139  }
140
141  /**
142   * @readonly
143   * @type {number}
144   */
145  get mean() {
146    if (!isHistogram(this))
147      throw new ERR_INVALID_THIS('Histogram');
148    return this[kHandle]?.mean();
149  }
150
151  /**
152   * @readonly
153   * @type {number}
154   */
155  get exceeds() {
156    if (!isHistogram(this))
157      throw new ERR_INVALID_THIS('Histogram');
158    return this[kHandle]?.exceeds();
159  }
160
161  /**
162   * @readonly
163   * @type {bigint}
164   */
165  get exceedsBigInt() {
166    if (!isHistogram(this))
167      throw new ERR_INVALID_THIS('Histogram');
168    return this[kHandle]?.exceedsBigInt();
169  }
170
171  /**
172   * @readonly
173   * @type {number}
174   */
175  get stddev() {
176    if (!isHistogram(this))
177      throw new ERR_INVALID_THIS('Histogram');
178    return this[kHandle]?.stddev();
179  }
180
181  /**
182   * @param {number} percentile
183   * @returns {number}
184   */
185  percentile(percentile) {
186    if (!isHistogram(this))
187      throw new ERR_INVALID_THIS('Histogram');
188    validateNumber(percentile, 'percentile');
189
190    if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100)
191      throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile);
192
193    return this[kHandle]?.percentile(percentile);
194  }
195
196  /**
197   * @param {number} percentile
198   * @returns {bigint}
199   */
200  percentileBigInt(percentile) {
201    if (!isHistogram(this))
202      throw new ERR_INVALID_THIS('Histogram');
203    validateNumber(percentile, 'percentile');
204
205    if (NumberIsNaN(percentile) || percentile <= 0 || percentile > 100)
206      throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile);
207
208    return this[kHandle]?.percentileBigInt(percentile);
209  }
210
211  /**
212   * @readonly
213   * @type {Map<number,number>}
214   */
215  get percentiles() {
216    if (!isHistogram(this))
217      throw new ERR_INVALID_THIS('Histogram');
218    this[kMap].clear();
219    this[kHandle]?.percentiles(this[kMap]);
220    return this[kMap];
221  }
222
223  /**
224   * @readonly
225   * @type {Map<number,bigint>}
226   */
227  get percentilesBigInt() {
228    if (!isHistogram(this))
229      throw new ERR_INVALID_THIS('Histogram');
230    this[kMap].clear();
231    this[kHandle]?.percentilesBigInt(this[kMap]);
232    return this[kMap];
233  }
234
235  /**
236   * @returns {void}
237   */
238  reset() {
239    if (!isHistogram(this))
240      throw new ERR_INVALID_THIS('Histogram');
241    this[kHandle]?.reset();
242  }
243
244  [kClone]() {
245    const handle = this[kHandle];
246    return {
247      data: { handle },
248      deserializeInfo: 'internal/histogram:internalHistogram',
249    };
250  }
251
252  [kDeserialize]({ handle }) {
253    this[kHandle] = handle;
254  }
255
256  toJSON() {
257    return {
258      count: this.count,
259      min: this.min,
260      max: this.max,
261      mean: this.mean,
262      exceeds: this.exceeds,
263      stddev: this.stddev,
264      percentiles: ObjectFromEntries(MapPrototypeEntries(this.percentiles)),
265    };
266  }
267}
268
269class RecordableHistogram extends Histogram {
270  constructor() {
271    throw new ERR_ILLEGAL_CONSTRUCTOR();
272  }
273
274  /**
275   * @param {number|bigint} val
276   * @returns {void}
277   */
278  record(val) {
279    if (this[kRecordable] === undefined)
280      throw new ERR_INVALID_THIS('RecordableHistogram');
281    if (typeof val === 'bigint') {
282      this[kHandle]?.record(val);
283      return;
284    }
285
286    if (!NumberIsInteger(val))
287      throw new ERR_INVALID_ARG_TYPE('val', ['integer', 'bigint'], val);
288
289    if (val < 1 || val > NumberMAX_SAFE_INTEGER)
290      throw new ERR_OUT_OF_RANGE('val', 'a safe integer greater than 0', val);
291
292    this[kHandle]?.record(val);
293  }
294
295  /**
296   * @returns {void}
297   */
298  recordDelta() {
299    if (this[kRecordable] === undefined)
300      throw new ERR_INVALID_THIS('RecordableHistogram');
301    this[kHandle]?.recordDelta();
302  }
303
304  /**
305   * @param {RecordableHistogram} other
306   */
307  add(other) {
308    if (this[kRecordable] === undefined)
309      throw new ERR_INVALID_THIS('RecordableHistogram');
310    if (other[kRecordable] === undefined)
311      throw new ERR_INVALID_ARG_TYPE('other', 'RecordableHistogram', other);
312    this[kHandle]?.add(other[kHandle]);
313  }
314
315  [kClone]() {
316    const handle = this[kHandle];
317    return {
318      data: { handle },
319      deserializeInfo: 'internal/histogram:internalRecordableHistogram',
320    };
321  }
322
323  [kDeserialize]({ handle }) {
324    this[kHandle] = handle;
325  }
326}
327
328function internalHistogram(handle) {
329  return makeTransferable(ReflectConstruct(
330    function() {
331      this[kHandle] = handle;
332      this[kMap] = new SafeMap();
333    }, [], Histogram));
334}
335internalHistogram.prototype[kDeserialize] = () => {};
336
337function internalRecordableHistogram(handle) {
338  return makeTransferable(ReflectConstruct(
339    function() {
340      this[kHandle] = handle;
341      this[kMap] = new SafeMap();
342      this[kRecordable] = true;
343    }, [], RecordableHistogram));
344}
345internalRecordableHistogram.prototype[kDeserialize] = () => {};
346
347/**
348 * @param {{
349 *   lowest? : number,
350 *   highest? : number,
351 *   figures? : number
352 * }} [options]
353 * @returns {RecordableHistogram}
354 */
355function createHistogram(options = kEmptyObject) {
356  validateObject(options, 'options');
357  const {
358    lowest = 1,
359    highest = NumberMAX_SAFE_INTEGER,
360    figures = 3,
361  } = options;
362  if (typeof lowest !== 'bigint')
363    validateInteger(lowest, 'options.lowest', 1, NumberMAX_SAFE_INTEGER);
364  if (typeof highest !== 'bigint') {
365    validateInteger(highest, 'options.highest',
366                    2 * lowest, NumberMAX_SAFE_INTEGER);
367  } else if (highest < 2n * lowest) {
368    throw new ERR_INVALID_ARG_VALUE.RangeError('options.highest', highest);
369  }
370  validateInteger(figures, 'options.figures', 1, 5);
371  return internalRecordableHistogram(new _Histogram(lowest, highest, figures));
372}
373
374module.exports = {
375  Histogram,
376  RecordableHistogram,
377  internalHistogram,
378  internalRecordableHistogram,
379  isHistogram,
380  kDestroy,
381  kHandle,
382  kMap,
383  createHistogram,
384};
385