Skip to content

Switched 'ts.performance' to a mixed mode only uses native performance APIs when necessary #42586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 37 additions & 22 deletions src/compiler/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/** Performance measurements for the compiler. */
namespace ts.performance {
let perfHooks: PerformanceHooks | undefined;
let perfObserver: PerformanceObserver | undefined;
// when set, indicates the implementation of `Performance` to use for user timing.
// when unset, indicates user timing is unavailable or disabled.
let performanceImpl: Performance | undefined;
Expand Down Expand Up @@ -41,6 +40,10 @@ namespace ts.performance {
}

export const nullTimer: Timer = { enter: noop, exit: noop };

let enabled = false;
let timeorigin = timestamp();
const marks = new Map<string, number>();
const counts = new Map<string, number>();
const durations = new Map<string, number>();

Expand All @@ -50,7 +53,12 @@ namespace ts.performance {
* @param markName The name of the mark.
*/
export function mark(markName: string) {
performanceImpl?.mark(markName);
if (enabled) {
const count = counts.get(markName) ?? 0;
counts.set(markName, count + 1);
marks.set(markName, timestamp());
performanceImpl?.mark(markName);
}
}

/**
Expand All @@ -63,7 +71,13 @@ namespace ts.performance {
* used.
*/
export function measure(measureName: string, startMarkName?: string, endMarkName?: string) {
performanceImpl?.measure(measureName, startMarkName, endMarkName);
if (enabled) {
const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp();
const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin;
const previousDuration = durations.get(measureName) || 0;
durations.set(measureName, previousDuration + (end - start));
performanceImpl?.measure(measureName, startMarkName, endMarkName);
}
}

/**
Expand Down Expand Up @@ -97,35 +111,36 @@ namespace ts.performance {
* Indicates whether the performance API is enabled.
*/
export function isEnabled() {
return !!performanceImpl;
return enabled;
}

/** Enables (and resets) performance measurements for the compiler. */
export function enable() {
if (!performanceImpl) {
export function enable(system: System = sys) {
if (!enabled) {
enabled = true;
perfHooks ||= tryGetNativePerformanceHooks();
if (!perfHooks) return false;
perfObserver ||= new perfHooks.PerformanceObserver(updateStatisticsFromList);
perfObserver.observe({ entryTypes: ["mark", "measure"] });
performanceImpl = perfHooks.performance;
if (perfHooks) {
timeorigin = perfHooks.performance.timeOrigin;
// NodeJS's Web Performance API is currently slower than expected, but we'd still like
// to be able to leverage native trace events when node is run with either `--cpu-prof`
// or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when
// running in debug mode (since its possible to generate a cpu profile while debugging).
if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) {
performanceImpl = perfHooks.performance;
}
}
}
return true;
}

/** Disables performance measurements for the compiler. */
export function disable() {
perfObserver?.disconnect();
performanceImpl = undefined;
counts.clear();
durations.clear();
}

function updateStatisticsFromList(list: PerformanceObserverEntryList) {
for (const mark of list.getEntriesByType("mark")) {
counts.set(mark.name, (counts.get(mark.name) || 0) + 1);
}
for (const measure of list.getEntriesByType("measure")) {
durations.set(measure.name, (durations.get(measure.name) || 0) + measure.duration);
if (enabled) {
marks.clear();
counts.clear();
durations.clear();
performanceImpl = undefined;
enabled = false;
}
}
}
46 changes: 27 additions & 19 deletions src/compiler/performanceCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace ts {
// between browsers and NodeJS:

export interface PerformanceHooks {
/** Indicates whether we should write native performance events */
shouldWriteNativeEvents: boolean;
performance: Performance;
PerformanceObserver: PerformanceObserverConstructor;
}
Expand Down Expand Up @@ -37,6 +39,7 @@ namespace ts {
export type PerformanceEntryList = PerformanceEntry[];

// Browser globals for the Web Performance User Timings API
declare const process: any;
declare const performance: Performance | undefined;
declare const PerformanceObserver: PerformanceObserverConstructor | undefined;

Expand All @@ -55,44 +58,49 @@ namespace ts {
typeof PerformanceObserver === "function" &&
hasRequiredAPI(performance, PerformanceObserver)) {
return {
// For now we always write native performance events when running in the browser. We may
// make this conditional in the future if we find that native web performance hooks
// in the browser also slow down compilation.
shouldWriteNativeEvents: true,
performance,
PerformanceObserver
};
}
}

function tryGetNodePerformanceHooks(): PerformanceHooks | undefined {
if (typeof module === "object" && typeof require === "function") {
if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof module === "object" && typeof require === "function") {
try {
const { performance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks");
if (hasRequiredAPI(performance, PerformanceObserver)) {
let performance: Performance;
const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks");
if (hasRequiredAPI(nodePerformance, PerformanceObserver)) {
performance = nodePerformance;
// There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not
// match the Web Performance API specification. Node's implementation did not allow
// optional `start` and `end` arguments for `performance.measure`.
// See https://github.com/nodejs/node/pull/32651 for more information.
const version = new Version(process.versions.node);
const range = new VersionRange("<12.16.3 || 13 <13.13");
if (range.test(version)) {
return {
performance: {
get timeOrigin() { return performance.timeOrigin; },
now() { return performance.now(); },
mark(name) { return performance.mark(name); },
measure(name, start = "nodeStart", end?) {
if (end === undefined) {
end = "__performance.measure-fix__";
performance.mark(end);
}
performance.measure(name, start, end);
if (end === "__performance.measure-fix__") {
performance.clearMarks("__performance.measure-fix__");
}
performance = {
get timeOrigin() { return nodePerformance.timeOrigin; },
now() { return nodePerformance.now(); },
mark(name) { return nodePerformance.mark(name); },
measure(name, start = "nodeStart", end?) {
if (end === undefined) {
end = "__performance.measure-fix__";
nodePerformance.mark(end);
}
},
PerformanceObserver
nodePerformance.measure(name, start, end);
if (end === "__performance.measure-fix__") {
nodePerformance.clearMarks("__performance.measure-fix__");
}
}
};
}
return {
// By default, only write native events when generating a cpu profile or using the v8 profiler.
shouldWriteNativeEvents: false,
performance,
PerformanceObserver
};
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ namespace ts {
exit(exitCode?: number): void;
/*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean;
/*@internal*/ disableCPUProfiler?(continuation: () => void): boolean;
/*@internal*/ cpuProfilingEnabled?(): boolean;
realpath?(path: string): string;
/*@internal*/ getEnvironmentVariable(name: string): string;
/*@internal*/ tryEnableSourceMapsForHost?(): void;
Expand Down Expand Up @@ -1286,6 +1287,7 @@ namespace ts {
},
enableCPUProfiler,
disableCPUProfiler,
cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"),
realpath,
debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
tryEnableSourceMapsForHost() {
Expand Down
2 changes: 1 addition & 1 deletion src/executeCommandLine/executeCommandLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ namespace ts {

function enableStatisticsAndTracing(system: System, compilerOptions: CompilerOptions, isBuildMode: boolean) {
if (canReportDiagnostics(system, compilerOptions)) {
performance.enable();
performance.enable(system);
}

if (canTrace(system, compilerOptions)) {
Expand Down