Skip to content

Commit c953969

Browse files
authored
Switched 'ts.performance' to a mixed mode only uses native performance APIs when necessary (#42586)
* Partially revert native performance * Fix bug in measure * Conditionally enable native perf events
1 parent 66ecfcb commit c953969

File tree

4 files changed

+67
-42
lines changed

4 files changed

+67
-42
lines changed

src/compiler/performance.ts

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
/** Performance measurements for the compiler. */
33
namespace ts.performance {
44
let perfHooks: PerformanceHooks | undefined;
5-
let perfObserver: PerformanceObserver | undefined;
65
// when set, indicates the implementation of `Performance` to use for user timing.
76
// when unset, indicates user timing is unavailable or disabled.
87
let performanceImpl: Performance | undefined;
@@ -41,6 +40,10 @@ namespace ts.performance {
4140
}
4241

4342
export const nullTimer: Timer = { enter: noop, exit: noop };
43+
44+
let enabled = false;
45+
let timeorigin = timestamp();
46+
const marks = new Map<string, number>();
4447
const counts = new Map<string, number>();
4548
const durations = new Map<string, number>();
4649

@@ -50,7 +53,12 @@ namespace ts.performance {
5053
* @param markName The name of the mark.
5154
*/
5255
export function mark(markName: string) {
53-
performanceImpl?.mark(markName);
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+
}
5462
}
5563

5664
/**
@@ -63,7 +71,13 @@ namespace ts.performance {
6371
* used.
6472
*/
6573
export function measure(measureName: string, startMarkName?: string, endMarkName?: string) {
66-
performanceImpl?.measure(measureName, startMarkName, endMarkName);
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+
}
6781
}
6882

6983
/**
@@ -97,35 +111,36 @@ namespace ts.performance {
97111
* Indicates whether the performance API is enabled.
98112
*/
99113
export function isEnabled() {
100-
return !!performanceImpl;
114+
return enabled;
101115
}
102116

103117
/** Enables (and resets) performance measurements for the compiler. */
104-
export function enable() {
105-
if (!performanceImpl) {
118+
export function enable(system: System = sys) {
119+
if (!enabled) {
120+
enabled = true;
106121
perfHooks ||= tryGetNativePerformanceHooks();
107-
if (!perfHooks) return false;
108-
perfObserver ||= new perfHooks.PerformanceObserver(updateStatisticsFromList);
109-
perfObserver.observe({ entryTypes: ["mark", "measure"] });
110-
performanceImpl = perfHooks.performance;
122+
if (perfHooks) {
123+
timeorigin = perfHooks.performance.timeOrigin;
124+
// NodeJS's Web Performance API is currently slower than expected, but we'd still like
125+
// to be able to leverage native trace events when node is run with either `--cpu-prof`
126+
// or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when
127+
// running in debug mode (since its possible to generate a cpu profile while debugging).
128+
if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) {
129+
performanceImpl = perfHooks.performance;
130+
}
131+
}
111132
}
112133
return true;
113134
}
114135

115136
/** Disables performance measurements for the compiler. */
116137
export function disable() {
117-
perfObserver?.disconnect();
118-
performanceImpl = undefined;
119-
counts.clear();
120-
durations.clear();
121-
}
122-
123-
function updateStatisticsFromList(list: PerformanceObserverEntryList) {
124-
for (const mark of list.getEntriesByType("mark")) {
125-
counts.set(mark.name, (counts.get(mark.name) || 0) + 1);
126-
}
127-
for (const measure of list.getEntriesByType("measure")) {
128-
durations.set(measure.name, (durations.get(measure.name) || 0) + measure.duration);
138+
if (enabled) {
139+
marks.clear();
140+
counts.clear();
141+
durations.clear();
142+
performanceImpl = undefined;
143+
enabled = false;
129144
}
130145
}
131146
}

src/compiler/performanceCore.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace ts {
44
// between browsers and NodeJS:
55

66
export interface PerformanceHooks {
7+
/** Indicates whether we should write native performance events */
8+
shouldWriteNativeEvents: boolean;
79
performance: Performance;
810
PerformanceObserver: PerformanceObserverConstructor;
911
}
@@ -37,6 +39,7 @@ namespace ts {
3739
export type PerformanceEntryList = PerformanceEntry[];
3840

3941
// Browser globals for the Web Performance User Timings API
42+
declare const process: any;
4043
declare const performance: Performance | undefined;
4144
declare const PerformanceObserver: PerformanceObserverConstructor | undefined;
4245

@@ -55,44 +58,49 @@ namespace ts {
5558
typeof PerformanceObserver === "function" &&
5659
hasRequiredAPI(performance, PerformanceObserver)) {
5760
return {
61+
// For now we always write native performance events when running in the browser. We may
62+
// make this conditional in the future if we find that native web performance hooks
63+
// in the browser also slow down compilation.
64+
shouldWriteNativeEvents: true,
5865
performance,
5966
PerformanceObserver
6067
};
6168
}
6269
}
6370

6471
function tryGetNodePerformanceHooks(): PerformanceHooks | undefined {
65-
if (typeof module === "object" && typeof require === "function") {
72+
if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof module === "object" && typeof require === "function") {
6673
try {
67-
const { performance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks");
68-
if (hasRequiredAPI(performance, PerformanceObserver)) {
74+
let performance: Performance;
75+
const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks");
76+
if (hasRequiredAPI(nodePerformance, PerformanceObserver)) {
77+
performance = nodePerformance;
6978
// There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not
7079
// match the Web Performance API specification. Node's implementation did not allow
7180
// optional `start` and `end` arguments for `performance.measure`.
7281
// See https://github.com/nodejs/node/pull/32651 for more information.
7382
const version = new Version(process.versions.node);
7483
const range = new VersionRange("<12.16.3 || 13 <13.13");
7584
if (range.test(version)) {
76-
return {
77-
performance: {
78-
get timeOrigin() { return performance.timeOrigin; },
79-
now() { return performance.now(); },
80-
mark(name) { return performance.mark(name); },
81-
measure(name, start = "nodeStart", end?) {
82-
if (end === undefined) {
83-
end = "__performance.measure-fix__";
84-
performance.mark(end);
85-
}
86-
performance.measure(name, start, end);
87-
if (end === "__performance.measure-fix__") {
88-
performance.clearMarks("__performance.measure-fix__");
89-
}
85+
performance = {
86+
get timeOrigin() { return nodePerformance.timeOrigin; },
87+
now() { return nodePerformance.now(); },
88+
mark(name) { return nodePerformance.mark(name); },
89+
measure(name, start = "nodeStart", end?) {
90+
if (end === undefined) {
91+
end = "__performance.measure-fix__";
92+
nodePerformance.mark(end);
9093
}
91-
},
92-
PerformanceObserver
94+
nodePerformance.measure(name, start, end);
95+
if (end === "__performance.measure-fix__") {
96+
nodePerformance.clearMarks("__performance.measure-fix__");
97+
}
98+
}
9399
};
94100
}
95101
return {
102+
// By default, only write native events when generating a cpu profile or using the v8 profiler.
103+
shouldWriteNativeEvents: false,
96104
performance,
97105
PerformanceObserver
98106
};

src/compiler/sys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ namespace ts {
11161116
exit(exitCode?: number): void;
11171117
/*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean;
11181118
/*@internal*/ disableCPUProfiler?(continuation: () => void): boolean;
1119+
/*@internal*/ cpuProfilingEnabled?(): boolean;
11191120
realpath?(path: string): string;
11201121
/*@internal*/ getEnvironmentVariable(name: string): string;
11211122
/*@internal*/ tryEnableSourceMapsForHost?(): void;
@@ -1286,6 +1287,7 @@ namespace ts {
12861287
},
12871288
enableCPUProfiler,
12881289
disableCPUProfiler,
1290+
cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"),
12891291
realpath,
12901292
debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
12911293
tryEnableSourceMapsForHost() {

src/executeCommandLine/executeCommandLine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ namespace ts {
662662

663663
function enableStatisticsAndTracing(system: System, compilerOptions: CompilerOptions, isBuildMode: boolean) {
664664
if (canReportDiagnostics(system, compilerOptions)) {
665-
performance.enable();
665+
performance.enable(system);
666666
}
667667

668668
if (canTrace(system, compilerOptions)) {

0 commit comments

Comments
 (0)