Skip to content

Commit a197bee

Browse files
rbucktonelibarzilay
authored andcommitted
Migrate 'ts.performance' to use native performance hooks when available
1 parent 868638a commit a197bee

File tree

7 files changed

+307
-52
lines changed

7 files changed

+307
-52
lines changed

src/compiler/performance.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22
/** Performance measurements for the compiler. */
33
namespace ts.performance {
44
declare const onProfilerEvent: { (markName: string): void; profiler: boolean; };
5+
const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : noop;
56

6-
// NOTE: cannot use ts.noop as core.ts loads after this
7-
const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : () => { /*empty*/ };
8-
7+
let perfHooks: PerformanceHooks | undefined;
8+
let perfObserver: PerformanceObserver | undefined;
9+
let perfEntryList: PerformanceObserverEntryList | undefined;
910
let enabled = false;
10-
let profilerStart = 0;
11-
let counts: ESMap<string, number>;
12-
let marks: ESMap<string, number>;
13-
let measures: ESMap<string, number>;
1411

1512
export interface Timer {
1613
enter(): void;
@@ -53,9 +50,8 @@ namespace ts.performance {
5350
* @param markName The name of the mark.
5451
*/
5552
export function mark(markName: string) {
56-
if (enabled) {
57-
marks.set(markName, timestamp());
58-
counts.set(markName, (counts.get(markName) || 0) + 1);
53+
if (perfHooks && enabled) {
54+
perfHooks.performance.mark(markName);
5955
profilerEvent(markName);
6056
}
6157
}
@@ -70,10 +66,8 @@ namespace ts.performance {
7066
* used.
7167
*/
7268
export function measure(measureName: string, startMarkName?: string, endMarkName?: string) {
73-
if (enabled) {
74-
const end = endMarkName && marks.get(endMarkName) || timestamp();
75-
const start = startMarkName && marks.get(startMarkName) || profilerStart;
76-
measures.set(measureName, (measures.get(measureName) || 0) + (end - start));
69+
if (perfHooks && enabled) {
70+
perfHooks.performance.measure(measureName, startMarkName, endMarkName);
7771
}
7872
}
7973

@@ -83,7 +77,7 @@ namespace ts.performance {
8377
* @param markName The name of the mark.
8478
*/
8579
export function getCount(markName: string) {
86-
return counts && counts.get(markName) || 0;
80+
return perfEntryList?.getEntriesByName(markName, "mark").length || 0;
8781
}
8882

8983
/**
@@ -92,7 +86,7 @@ namespace ts.performance {
9286
* @param measureName The name of the measure whose durations should be accumulated.
9387
*/
9488
export function getDuration(measureName: string) {
95-
return measures && measures.get(measureName) || 0;
89+
return perfEntryList?.getEntriesByName(measureName, "measure").reduce((a, entry) => a + entry.duration, 0) || 0;
9690
}
9791

9892
/**
@@ -101,22 +95,25 @@ namespace ts.performance {
10195
* @param cb The action to perform for each measure
10296
*/
10397
export function forEachMeasure(cb: (measureName: string, duration: number) => void) {
104-
measures.forEach((measure, key) => {
105-
cb(key, measure);
98+
perfEntryList?.getEntriesByType("measure").forEach(entry => {
99+
cb(entry.name, entry.duration);
106100
});
107101
}
108102

109103
/** Enables (and resets) performance measurements for the compiler. */
110104
export function enable() {
111-
counts = new Map<string, number>();
112-
marks = new Map<string, number>();
113-
measures = new Map<string, number>();
114-
enabled = true;
115-
profilerStart = timestamp();
105+
if (!enabled) {
106+
perfHooks ||= tryGetNativePerformanceHooks() || ShimPerformance?.createPerformanceHooksShim(timestamp);
107+
if (!perfHooks) throw new Error("TypeScript requires an environment that provides a compatible native Web Performance API implementation.");
108+
perfObserver ||= new perfHooks.PerformanceObserver(list => perfEntryList = list);
109+
perfObserver.observe({ entryTypes: ["mark", "measure"] });
110+
enabled = true;
111+
}
116112
}
117113

118114
/** Disables performance measurements for the compiler. */
119115
export function disable() {
116+
perfObserver?.disconnect();
120117
enabled = false;
121118
}
122119
}

src/compiler/performanceCore.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*@internal*/
2+
namespace ts {
3+
export interface PerformanceHooks {
4+
performance: Performance;
5+
PerformanceObserver: PerformanceObserverConstructor;
6+
}
7+
8+
export interface Performance {
9+
clearMarks(name?: string): void;
10+
mark(name: string): void;
11+
measure(name: string, startMark?: string, endMark?: string): void;
12+
now(): number;
13+
timeOrigin: number;
14+
}
15+
16+
export interface PerformanceEntry {
17+
name: string;
18+
entryType: string;
19+
startTime: number;
20+
duration: number;
21+
}
22+
23+
export interface PerformanceObserverEntryList {
24+
getEntries(): PerformanceEntryList;
25+
getEntriesByName(name: string, type?: string): PerformanceEntryList;
26+
getEntriesByType(type: string): PerformanceEntryList;
27+
}
28+
29+
export interface PerformanceObserver {
30+
disconnect(): void;
31+
observe(options: { entryTypes: readonly string[] }): void;
32+
}
33+
34+
export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver;
35+
export type PerformanceEntryList = PerformanceEntry[];
36+
37+
declare const performance: Performance | undefined;
38+
declare const PerformanceObserver: PerformanceObserverConstructor | undefined;
39+
40+
function tryGetWebPerformanceHooks(): PerformanceHooks | undefined {
41+
if (typeof performance === "object" &&
42+
typeof performance.timeOrigin === "number" &&
43+
typeof performance.clearMarks === "function" &&
44+
typeof performance.mark === "function" &&
45+
typeof performance.measure === "function" &&
46+
typeof performance.now === "function" &&
47+
typeof PerformanceObserver === "function") {
48+
return {
49+
performance,
50+
PerformanceObserver
51+
};
52+
}
53+
}
54+
55+
function tryGetNodePerformanceHooks(): PerformanceHooks | undefined {
56+
if (typeof module === "object" && typeof require === "function") {
57+
try {
58+
return require("perf_hooks") as typeof import("perf_hooks");
59+
}
60+
catch {
61+
// ignore errors
62+
}
63+
}
64+
}
65+
66+
const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks();
67+
68+
export function tryGetNativePerformanceHooks() {
69+
return nativePerformanceHooks;
70+
}
71+
72+
/** Gets a timestamp with (at least) ms resolution */
73+
export const timestamp = (() => {
74+
if (nativePerformanceHooks) {
75+
const performance = nativePerformanceHooks.performance;
76+
return () => performance.now();
77+
}
78+
return Date.now ? Date.now : () => +(new Date());
79+
})();
80+
}

src/compiler/performanceTimestamp.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/compiler/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"corePublic.ts",
1414
"core.ts",
1515
"debug.ts",
16-
"performanceTimestamp.ts",
16+
"performanceCore.ts",
1717
"performance.ts",
1818
"perfLogger.ts",
1919
"semver.ts",

0 commit comments

Comments
 (0)