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