• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 'use strict';
2 
3 const {
4   SafeMap,
5   SafeSet,
6   SafeArrayIterator,
7   SymbolToStringTag,
8 } = primordials;
9 
10 const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
11 const { now } = require('internal/perf/utils');
12 const { enqueue, bufferUserTiming } = require('internal/perf/observe');
13 const nodeTiming = require('internal/perf/nodetiming');
14 
15 const {
16   validateNumber,
17   validateObject,
18   validateString,
19 } = require('internal/validators');
20 
21 const {
22   codes: {
23     ERR_INVALID_ARG_VALUE,
24     ERR_PERFORMANCE_INVALID_TIMESTAMP,
25     ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS,
26   },
27 } = require('internal/errors');
28 
29 const { structuredClone } = require('internal/structured_clone');
30 const {
31   kEmptyObject,
32   lazyDOMException,
33 } = require('internal/util');
34 
35 const markTimings = new SafeMap();
36 
37 const nodeTimingReadOnlyAttributes = new SafeSet(new SafeArrayIterator([
38   'nodeStart',
39   'v8Start',
40   'environment',
41   'loopStart',
42   'loopExit',
43   'bootstrapComplete',
44 ]));
45 
46 function 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 
62 class 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 
87 class 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 
97 function mark(name, options = kEmptyObject) {
98   const mark = new PerformanceMark(name, options);
99   enqueue(mark);
100   bufferUserTiming(mark);
101   return mark;
102 }
103 
104 function 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 
155 function 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 
169 function 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 
180 module.exports = {
181   PerformanceMark,
182   PerformanceMeasure,
183   clearMarkTimings,
184   mark,
185   measure,
186 };
187