1import { isNodeLikeSystem, Version, VersionRange } from "./_namespaces/ts"; 2 3// The following definitions provide the minimum compatible support for the Web Performance User Timings API 4// between browsers and NodeJS: 5 6/** @internal */ 7export interface PerformanceHooks { 8 /** Indicates whether we should write native performance events */ 9 shouldWriteNativeEvents: boolean; 10 performance: Performance; 11 PerformanceObserver: PerformanceObserverConstructor; 12} 13 14/** @internal */ 15export interface Performance { 16 mark(name: string): void; 17 measure(name: string, startMark?: string, endMark?: string): void; 18 clearMeasures(name?: string): void; 19 clearMarks(name?: string): void; 20 now(): number; 21 timeOrigin: number; 22} 23 24/** @internal */ 25export interface PerformanceEntry { 26 name: string; 27 entryType: string; 28 startTime: number; 29 duration: number; 30} 31 32/** @internal */ 33export interface PerformanceObserverEntryList { 34 getEntries(): PerformanceEntryList; 35 getEntriesByName(name: string, type?: string): PerformanceEntryList; 36 getEntriesByType(type: string): PerformanceEntryList; 37} 38 39/** @internal */ 40export interface PerformanceObserver { 41 disconnect(): void; 42 observe(options: { entryTypes: readonly ("mark" | "measure")[] }): void; 43} 44 45/** @internal */ 46export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; 47/** @internal */ 48export type PerformanceEntryList = PerformanceEntry[]; 49 50// Browser globals for the Web Performance User Timings API 51declare const process: any; 52declare const performance: Performance | undefined; 53declare const PerformanceObserver: PerformanceObserverConstructor | undefined; 54 55// eslint-disable-next-line @typescript-eslint/naming-convention 56function hasRequiredAPI(performance: Performance | undefined, PerformanceObserver: PerformanceObserverConstructor | undefined) { 57 return typeof performance === "object" && 58 typeof performance.timeOrigin === "number" && 59 typeof performance.mark === "function" && 60 typeof performance.measure === "function" && 61 typeof performance.now === "function" && 62 typeof performance.clearMarks === "function" && 63 typeof performance.clearMeasures === "function" && 64 typeof PerformanceObserver === "function"; 65} 66 67function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { 68 if (typeof performance === "object" && 69 typeof PerformanceObserver === "function" && 70 hasRequiredAPI(performance, PerformanceObserver)) { 71 return { 72 // For now we always write native performance events when running in the browser. We may 73 // make this conditional in the future if we find that native web performance hooks 74 // in the browser also slow down compilation. 75 shouldWriteNativeEvents: true, 76 performance, 77 PerformanceObserver 78 }; 79 } 80} 81 82function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { 83 if (isNodeLikeSystem()) { 84 try { 85 let performance: Performance; 86 const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks"); 87 if (hasRequiredAPI(nodePerformance as unknown as Performance, PerformanceObserver)) { 88 performance = nodePerformance as unknown as Performance; 89 // There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not 90 // match the Web Performance API specification. Node's implementation did not allow 91 // optional `start` and `end` arguments for `performance.measure`. 92 // See https://github.com/nodejs/node/pull/32651 for more information. 93 const version = new Version(process.versions.node); 94 const range = new VersionRange("<12.16.3 || 13 <13.13"); 95 if (range.test(version)) { 96 performance = { 97 get timeOrigin() { return nodePerformance.timeOrigin; }, 98 now() { return nodePerformance.now(); }, 99 mark(name) { return nodePerformance.mark(name); }, 100 measure(name, start = "nodeStart", end?) { 101 if (end === undefined) { 102 end = "__performance.measure-fix__"; 103 nodePerformance.mark(end); 104 } 105 nodePerformance.measure(name, start, end); 106 if (end === "__performance.measure-fix__") { 107 nodePerformance.clearMarks("__performance.measure-fix__"); 108 } 109 }, 110 clearMarks(name) { return nodePerformance.clearMarks(name); }, 111 clearMeasures(name) { return (nodePerformance as unknown as Performance).clearMeasures(name); }, 112 }; 113 } 114 return { 115 // By default, only write native events when generating a cpu profile or using the v8 profiler. 116 shouldWriteNativeEvents: false, 117 performance, 118 PerformanceObserver 119 }; 120 } 121 } 122 catch { 123 // ignore errors 124 } 125 } 126} 127 128// Unlike with the native Map/Set 'tryGet' functions in corePublic.ts, we eagerly evaluate these 129// since we will need them for `timestamp`, below. 130const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); 131const nativePerformance = nativePerformanceHooks?.performance; 132 133/** @internal */ 134export function tryGetNativePerformanceHooks() { 135 return nativePerformanceHooks; 136} 137 138/** Gets a timestamp with (at least) ms resolution 139 * 140 * @internal 141 */ 142export const timestamp = 143 nativePerformance ? () => nativePerformance.now() : 144 Date.now ? Date.now : 145 () => +(new Date());