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