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
This commit is contained in:
Ron Buckton 2021-02-01 15:33:34 -08:00 committed by GitHub
parent 66ecfcbd04
commit c953969698
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 42 deletions

View File

@ -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;
@ -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>();
@ -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);
}
}
/**
@ -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);
}
}
/**
@ -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;
}
}
}

View File

@ -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;
}
@ -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;
@ -55,6 +58,10 @@ 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
};
@ -62,10 +69,12 @@ namespace ts {
}
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`.
@ -73,26 +82,25 @@ namespace ts {
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
};

View File

@ -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;
@ -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() {

View File

@ -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)) {