mirror of
https://github.com/nodejs/node.git
synced 2025-05-03 02:06:12 +00:00

Simplify the implementation by implementing it directly in C++. This improves performance and also makes structuredClone supported in custom snapshots. PR-URL: https://github.com/nodejs/node/pull/50330 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
247 lines
6.1 KiB
JavaScript
247 lines
6.1 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ObjectDefineProperties,
|
|
SafeMap,
|
|
SafeSet,
|
|
SafeArrayIterator,
|
|
Symbol,
|
|
SymbolToStringTag,
|
|
} = primordials;
|
|
|
|
const { PerformanceEntry, kSkipThrow } = require('internal/perf/performance_entry');
|
|
const { now } = require('internal/perf/utils');
|
|
const { enqueue, bufferUserTiming } = require('internal/perf/observe');
|
|
const nodeTiming = require('internal/perf/nodetiming');
|
|
|
|
const {
|
|
validateNumber,
|
|
validateObject,
|
|
validateString,
|
|
validateInternalField,
|
|
} = require('internal/validators');
|
|
|
|
const {
|
|
codes: {
|
|
ERR_ILLEGAL_CONSTRUCTOR,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_MISSING_ARGS,
|
|
ERR_PERFORMANCE_INVALID_TIMESTAMP,
|
|
ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS,
|
|
},
|
|
} = require('internal/errors');
|
|
|
|
const { structuredClone } = internalBinding('messaging');
|
|
const {
|
|
lazyDOMException,
|
|
kEnumerableProperty,
|
|
} = require('internal/util');
|
|
|
|
const kDetail = Symbol('kDetail');
|
|
|
|
const markTimings = new SafeMap();
|
|
|
|
const nodeTimingReadOnlyAttributes = new SafeSet(new SafeArrayIterator([
|
|
'nodeStart',
|
|
'v8Start',
|
|
'environment',
|
|
'loopStart',
|
|
'loopExit',
|
|
'bootstrapComplete',
|
|
]));
|
|
|
|
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 = markTimings.get(name);
|
|
if (ts === undefined)
|
|
throw lazyDOMException(`The "${name}" performance mark has not been set`, 'SyntaxError');
|
|
return ts;
|
|
}
|
|
|
|
class PerformanceMark extends PerformanceEntry {
|
|
constructor(name, options = undefined) {
|
|
if (arguments.length === 0) {
|
|
throw new ERR_MISSING_ARGS('name');
|
|
}
|
|
name = `${name}`;
|
|
if (nodeTimingReadOnlyAttributes.has(name))
|
|
throw new ERR_INVALID_ARG_VALUE('name', name);
|
|
if (options != null) {
|
|
validateObject(options, 'options');
|
|
}
|
|
const startTime = options?.startTime ?? now();
|
|
validateNumber(startTime, 'startTime');
|
|
if (startTime < 0)
|
|
throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(startTime);
|
|
markTimings.set(name, startTime);
|
|
|
|
let detail = options?.detail;
|
|
detail = detail != null ?
|
|
structuredClone(detail) :
|
|
null;
|
|
|
|
super(kSkipThrow, name, 'mark', startTime, 0);
|
|
this[kDetail] = detail;
|
|
}
|
|
|
|
get detail() {
|
|
validateInternalField(this, kDetail, 'PerformanceMark');
|
|
return this[kDetail];
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
name: this.name,
|
|
entryType: this.entryType,
|
|
startTime: this.startTime,
|
|
duration: this.duration,
|
|
detail: this[kDetail],
|
|
};
|
|
}
|
|
}
|
|
|
|
ObjectDefineProperties(PerformanceMark.prototype, {
|
|
detail: kEnumerableProperty,
|
|
[SymbolToStringTag]: {
|
|
__proto__: null,
|
|
configurable: true,
|
|
value: 'PerformanceMark',
|
|
},
|
|
});
|
|
|
|
class PerformanceMeasure extends PerformanceEntry {
|
|
constructor(
|
|
skipThrowSymbol = undefined,
|
|
name = undefined,
|
|
type = undefined,
|
|
start = undefined,
|
|
duration = undefined,
|
|
) {
|
|
if (skipThrowSymbol !== kSkipThrow) {
|
|
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
|
}
|
|
|
|
super(skipThrowSymbol, name, type, start, duration);
|
|
}
|
|
|
|
get detail() {
|
|
validateInternalField(this, kDetail, 'PerformanceMeasure');
|
|
return this[kDetail];
|
|
}
|
|
}
|
|
ObjectDefineProperties(PerformanceMeasure.prototype, {
|
|
detail: kEnumerableProperty,
|
|
[SymbolToStringTag]: {
|
|
__proto__: null,
|
|
configurable: true,
|
|
value: 'PerformanceMeasure',
|
|
},
|
|
});
|
|
|
|
function createPerformanceMeasure(name, start, duration, detail) {
|
|
const measure = new PerformanceMeasure(kSkipThrow, name, 'measure', start, duration);
|
|
|
|
measure[kDetail] = detail;
|
|
|
|
return measure;
|
|
}
|
|
|
|
function mark(name, options) {
|
|
const mark = new PerformanceMark(name, options);
|
|
enqueue(mark);
|
|
bufferUserTiming(mark);
|
|
return mark;
|
|
}
|
|
|
|
function calculateStartDuration(startOrMeasureOptions, endMark) {
|
|
startOrMeasureOptions ??= 0;
|
|
let start;
|
|
let end;
|
|
let duration;
|
|
let optionsValid = false;
|
|
if (typeof startOrMeasureOptions === 'object') {
|
|
({ start, end, duration } = startOrMeasureOptions);
|
|
optionsValid = start !== undefined || end !== undefined;
|
|
}
|
|
if (optionsValid) {
|
|
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');
|
|
}
|
|
}
|
|
|
|
if (endMark !== undefined) {
|
|
end = getMark(endMark);
|
|
} else if (optionsValid && end !== undefined) {
|
|
end = getMark(end);
|
|
} else if (optionsValid && start !== undefined && duration !== undefined) {
|
|
end = getMark(start) + getMark(duration);
|
|
} else {
|
|
end = now();
|
|
}
|
|
|
|
if (typeof startOrMeasureOptions === 'string') {
|
|
start = getMark(startOrMeasureOptions);
|
|
} else if (optionsValid && start !== undefined) {
|
|
start = getMark(start);
|
|
} else if (optionsValid && duration !== undefined && end !== undefined) {
|
|
start = end - getMark(duration);
|
|
} else {
|
|
start = 0;
|
|
}
|
|
|
|
duration = end - start;
|
|
return { start, duration };
|
|
}
|
|
|
|
function measure(name, startOrMeasureOptions, endMark) {
|
|
validateString(name, 'name');
|
|
const {
|
|
start,
|
|
duration,
|
|
} = calculateStartDuration(startOrMeasureOptions, endMark);
|
|
let detail = startOrMeasureOptions?.detail;
|
|
detail = detail != null ? structuredClone(detail) : null;
|
|
const measure = createPerformanceMeasure(name, start, duration, detail);
|
|
enqueue(measure);
|
|
bufferUserTiming(measure);
|
|
return measure;
|
|
}
|
|
|
|
function clearMarkTimings(name) {
|
|
if (name !== undefined) {
|
|
name = `${name}`;
|
|
if (nodeTimingReadOnlyAttributes.has(name))
|
|
throw new ERR_INVALID_ARG_VALUE('name', name);
|
|
markTimings.delete(name);
|
|
return;
|
|
}
|
|
markTimings.clear();
|
|
}
|
|
|
|
module.exports = {
|
|
PerformanceMark,
|
|
PerformanceMeasure,
|
|
clearMarkTimings,
|
|
mark,
|
|
measure,
|
|
};
|