1'use strict';
2
3const {
4  SafeMap,
5  SafeSet,
6  SafeArrayIterator,
7  SymbolToStringTag,
8} = primordials;
9
10const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
11const { now } = require('internal/perf/utils');
12const { enqueue, bufferUserTiming } = require('internal/perf/observe');
13const nodeTiming = require('internal/perf/nodetiming');
14
15const {
16  validateNumber,
17  validateObject,
18  validateString,
19} = require('internal/validators');
20
21const {
22  codes: {
23    ERR_INVALID_ARG_VALUE,
24    ERR_PERFORMANCE_INVALID_TIMESTAMP,
25    ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS,
26  },
27} = require('internal/errors');
28
29const { structuredClone } = require('internal/structured_clone');
30const {
31  kEmptyObject,
32  lazyDOMException,
33} = require('internal/util');
34
35const markTimings = new SafeMap();
36
37const nodeTimingReadOnlyAttributes = new SafeSet(new SafeArrayIterator([
38  'nodeStart',
39  'v8Start',
40  'environment',
41  'loopStart',
42  'loopExit',
43  'bootstrapComplete',
44]));
45
46function getMark(name) {
47  if (name === undefined) return;
48  if (typeof name === 'number') {
49    if (name < 0)
50      throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(name);
51    return name;
52  }
53  name = `${name}`;
54  if (nodeTimingReadOnlyAttributes.has(name))
55    return nodeTiming[name];
56  const ts = markTimings.get(name);
57  if (ts === undefined)
58    throw lazyDOMException(`The "${name}" performance mark has not been set`, 'SyntaxError');
59  return ts;
60}
61
62class PerformanceMark extends InternalPerformanceEntry {
63  constructor(name, options) {
64    name = `${name}`;
65    if (nodeTimingReadOnlyAttributes.has(name))
66      throw new ERR_INVALID_ARG_VALUE('name', name);
67    options ??= kEmptyObject;
68    validateObject(options, 'options');
69    const startTime = options.startTime ?? now();
70    validateNumber(startTime, 'startTime');
71    if (startTime < 0)
72      throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(startTime);
73    markTimings.set(name, startTime);
74
75    let detail = options.detail;
76    detail = detail != null ?
77      structuredClone(detail) :
78      null;
79    super(name, 'mark', startTime, 0, detail);
80  }
81
82  get [SymbolToStringTag]() {
83    return 'PerformanceMark';
84  }
85}
86
87class PerformanceMeasure extends InternalPerformanceEntry {
88  constructor(name, start, duration, detail) {
89    super(name, 'measure', start, duration, detail);
90  }
91
92  get [SymbolToStringTag]() {
93    return 'PerformanceMeasure';
94  }
95}
96
97function mark(name, options = kEmptyObject) {
98  const mark = new PerformanceMark(name, options);
99  enqueue(mark);
100  bufferUserTiming(mark);
101  return mark;
102}
103
104function calculateStartDuration(startOrMeasureOptions, endMark) {
105  startOrMeasureOptions ??= 0;
106  let start;
107  let end;
108  let duration;
109  let optionsValid = false;
110  if (typeof startOrMeasureOptions === 'object') {
111    ({ start, end, duration } = startOrMeasureOptions);
112    optionsValid = start !== undefined || end !== undefined;
113  }
114  if (optionsValid) {
115    if (endMark !== undefined) {
116      throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
117        'endMark must not be specified');
118    }
119
120    if (start === undefined && end === undefined) {
121      throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
122        'One of options.start or options.end is required');
123    }
124    if (start !== undefined && end !== undefined && duration !== undefined) {
125      throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
126        'Must not have options.start, options.end, and ' +
127        'options.duration specified');
128    }
129  }
130
131  if (endMark !== undefined) {
132    end = getMark(endMark);
133  } else if (optionsValid && end !== undefined) {
134    end = getMark(end);
135  } else if (optionsValid && start !== undefined && duration !== undefined) {
136    end = getMark(start) + getMark(duration);
137  } else {
138    end = now();
139  }
140
141  if (typeof startOrMeasureOptions === 'string') {
142    start = getMark(startOrMeasureOptions);
143  } else if (optionsValid && start !== undefined) {
144    start = getMark(start);
145  } else if (optionsValid && duration !== undefined && end !== undefined) {
146    start = end - getMark(duration);
147  } else {
148    start = 0;
149  }
150
151  duration = end - start;
152  return { start, duration };
153}
154
155function measure(name, startOrMeasureOptions, endMark) {
156  validateString(name, 'name');
157  const {
158    start,
159    duration,
160  } = calculateStartDuration(startOrMeasureOptions, endMark);
161  let detail = startOrMeasureOptions?.detail;
162  detail = detail != null ? structuredClone(detail) : null;
163  const measure = new PerformanceMeasure(name, start, duration, detail);
164  enqueue(measure);
165  bufferUserTiming(measure);
166  return measure;
167}
168
169function clearMarkTimings(name) {
170  if (name !== undefined) {
171    name = `${name}`;
172    if (nodeTimingReadOnlyAttributes.has(name))
173      throw new ERR_INVALID_ARG_VALUE('name', name);
174    markTimings.delete(name);
175    return;
176  }
177  markTimings.clear();
178}
179
180module.exports = {
181  PerformanceMark,
182  PerformanceMeasure,
183  clearMarkTimings,
184  mark,
185  measure,
186};
187