node/lib/internal/perf/usertiming.js
James M Snell f3eb224c83
perf_hooks: complete overhaul of the implementation
* Update the user timing implementation to conform to
  User Timing Level 3.
* Reimplement user timing and timerify with pure JavaScript
  implementations
* Simplify the C++ implementation for gc and http2 perf
* Runtime deprecate additional perf entry properties
  in favor of the standard detail argument
* Disable the `buffered` option on PerformanceObserver,
  all entries are queued and dispatched on setImmediate.
  Only entries with active observers are buffered.
* This does remove the user timing and timerify
  trace events. Because the trace_events are still
  considered experimental, those can be removed without
  a deprecation cycle. They are removed to improve
  performance and reduce complexity.

Old: `perf_hooks/usertiming.js n=100000: 92,378.01249733355`
New: perf_hooks/usertiming.js n=100000: 270,393.5280638482`

PR-URL: https://github.com/nodejs/node/pull/37136
Refs: https://github.com/nodejs/diagnostics/issues/464
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
2021-02-22 08:46:11 -08:00

154 lines
3.7 KiB
JavaScript

'use strict';
const {
ObjectKeys,
SafeMap,
} = primordials;
const {
InternalPerformanceEntry,
kReadOnlyAttributes,
now,
} = require('internal/perf/perf');
const { enqueue } = require('internal/perf/observe');
const nodeTiming = require('internal/perf/nodetiming');
const {
validateNumber,
validateObject,
validateString,
} = require('internal/validators');
const {
codes: {
ERR_INVALID_ARG_VALUE,
ERR_INVALID_PERFORMANCE_MARK,
ERR_PERFORMANCE_INVALID_TIMESTAMP,
ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS,
},
} = require('internal/errors');
const marks = new SafeMap();
const nodeTimingReadOnlyAttributes =
nodeTiming.constructor[kReadOnlyAttributes];
function getMark(name) {
if (name === undefined) return;
if (typeof name === 'number') {
if (name < 0)
throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(name);
return name;
}
name = `${name}`;
if (nodeTimingReadOnlyAttributes.has(name))
return nodeTiming[name];
const ts = marks.get(name);
if (ts === undefined)
throw new ERR_INVALID_PERFORMANCE_MARK(name);
return ts;
}
class PerformanceMark extends InternalPerformanceEntry {
constructor(name, options = {}) {
name = `${name}`;
if (nodeTimingReadOnlyAttributes.has(name))
throw new ERR_INVALID_ARG_VALUE('name', name);
validateObject(options, 'options');
const {
detail,
startTime = now(),
} = options;
validateNumber(startTime, 'startTime');
if (startTime < 0)
throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(startTime);
marks.set(name, startTime);
super(name, 'mark', startTime, 0, detail);
enqueue(this);
}
}
class PerformanceMeasure extends InternalPerformanceEntry {
constructor(name, start, duration, detail) {
super(name, 'measure', start, duration, detail);
enqueue(this);
}
}
function mark(name, options = {}) {
return new PerformanceMark(name, options);
}
function calculateStartDuration(startOrMeasureOptions, endMark) {
startOrMeasureOptions ??= 0;
let detail;
let start;
let end;
let duration;
if (typeof startOrMeasureOptions === 'object' &&
ObjectKeys(startOrMeasureOptions).length) {
({
start,
end,
duration,
detail,
} = startOrMeasureOptions);
if (endMark !== undefined) {
throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
'endMark must not be specified');
}
if (start === undefined && end === undefined) {
throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
'One of options.start or options.end is required');
}
if (start !== undefined && end !== undefined && duration !== undefined) {
throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
'Must not have options.start, options.end, and ' +
'options.duration specified');
}
start = getMark(start);
duration = getMark(duration);
} else {
start = getMark(startOrMeasureOptions);
}
end = getMark(endMark || end) ??
((start !== undefined && duration !== undefined) ?
start + duration : now());
start ??= (duration !== undefined) ? end - duration : 0;
duration ??= end - start;
return { start, duration, detail };
}
function measure(name, startOrMeasureOptions, endMark) {
validateString(name, 'name');
const {
start,
duration,
detail
} = calculateStartDuration(startOrMeasureOptions, endMark);
return new PerformanceMeasure(name, start, duration, detail);
}
function clearMarks(name) {
if (name !== undefined) {
name = `${name}`;
if (nodeTimingReadOnlyAttributes.has(name))
throw new ERR_INVALID_ARG_VALUE('name', name);
marks.delete(name);
return;
}
marks.clear();
}
module.exports = {
PerformanceMark,
clearMarks,
mark,
measure,
};