• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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());