1/*@internal*/ 2/** Performance measurements for the compiler. */ 3namespace ts.performance { 4 let perfHooks: PerformanceHooks | undefined; 5 // when set, indicates the implementation of `Performance` to use for user timing. 6 // when unset, indicates user timing is unavailable or disabled. 7 let performanceImpl: Performance | undefined; 8 9 export interface Timer { 10 enter(): void; 11 exit(): void; 12 } 13 14 export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { 15 return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; 16 } 17 18 export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { 19 let enterCount = 0; 20 return { 21 enter, 22 exit 23 }; 24 25 function enter() { 26 if (++enterCount === 1) { 27 mark(startMarkName); 28 } 29 } 30 31 function exit() { 32 if (--enterCount === 0) { 33 mark(endMarkName); 34 measure(measureName, startMarkName, endMarkName); 35 } 36 else if (enterCount < 0) { 37 Debug.fail("enter/exit count does not match."); 38 } 39 } 40 } 41 42 export const nullTimer: Timer = { enter: noop, exit: noop }; 43 44 let enabled = false; 45 let timeorigin = timestamp(); 46 const marks = new Map<string, number>(); 47 const counts = new Map<string, number>(); 48 const durations = new Map<string, number>(); 49 50 /** 51 * Marks a performance event. 52 * 53 * @param markName The name of the mark. 54 */ 55 export function mark(markName: string) { 56 if (enabled) { 57 const count = counts.get(markName) ?? 0; 58 counts.set(markName, count + 1); 59 marks.set(markName, timestamp()); 60 performanceImpl?.mark(markName); 61 } 62 } 63 64 /** 65 * Adds a performance measurement with the specified name. 66 * 67 * @param measureName The name of the performance measurement. 68 * @param startMarkName The name of the starting mark. If not supplied, the point at which the 69 * profiler was enabled is used. 70 * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is 71 * used. 72 */ 73 export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { 74 if (enabled) { 75 const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp(); 76 const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin; 77 const previousDuration = durations.get(measureName) || 0; 78 durations.set(measureName, previousDuration + (end - start)); 79 performanceImpl?.measure(measureName, startMarkName, endMarkName); 80 } 81 } 82 83 /** 84 * Gets the number of times a marker was encountered. 85 * 86 * @param markName The name of the mark. 87 */ 88 export function getCount(markName: string) { 89 return counts.get(markName) || 0; 90 } 91 92 /** 93 * Gets the total duration of all measurements with the supplied name. 94 * 95 * @param measureName The name of the measure whose durations should be accumulated. 96 */ 97 export function getDuration(measureName: string) { 98 return durations.get(measureName) || 0; 99 } 100 101 /** 102 * Iterate over each measure, performing some action 103 * 104 * @param cb The action to perform for each measure 105 */ 106 export function forEachMeasure(cb: (measureName: string, duration: number) => void) { 107 durations.forEach((duration, measureName) => cb(measureName, duration)); 108 } 109 110 export function forEachMark(cb: (markName: string) => void) { 111 marks.forEach((_time, markName) => cb(markName)); 112 } 113 114 export function clearMeasures(name?: string) { 115 if (name !== undefined) durations.delete(name); 116 else durations.clear(); 117 performanceImpl?.clearMeasures(name); 118 } 119 120 export function clearMarks(name?: string) { 121 if (name !== undefined) { 122 counts.delete(name); 123 marks.delete(name); 124 } 125 else { 126 counts.clear(); 127 marks.clear(); 128 } 129 performanceImpl?.clearMarks(name); 130 } 131 132 /** 133 * Indicates whether the performance API is enabled. 134 */ 135 export function isEnabled() { 136 return enabled; 137 } 138 139 /** Enables (and resets) performance measurements for the compiler. */ 140 export function enable(system: System = sys) { 141 if (!enabled) { 142 enabled = true; 143 perfHooks ||= tryGetNativePerformanceHooks(); 144 if (perfHooks) { 145 timeorigin = perfHooks.performance.timeOrigin; 146 // NodeJS's Web Performance API is currently slower than expected, but we'd still like 147 // to be able to leverage native trace events when node is run with either `--cpu-prof` 148 // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when 149 // running in debug mode (since its possible to generate a cpu profile while debugging). 150 if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { 151 performanceImpl = perfHooks.performance; 152 } 153 } 154 } 155 return true; 156 } 157 158 /** Disables performance measurements for the compiler. */ 159 export function disable() { 160 if (enabled) { 161 marks.clear(); 162 counts.clear(); 163 durations.clear(); 164 performanceImpl = undefined; 165 enabled = false; 166 } 167 } 168} 169