From dfa55add5a633e6f12928d19a9474fa7e73d7837 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 16 Sep 2020 16:46:50 -0700 Subject: [PATCH] Write message instead of crashing when native perf API not found. --- src/compiler/diagnosticMessages.json | 4 +++ src/compiler/performance.ts | 22 ++++++++++++-- src/compiler/performanceCore.ts | 30 ++++++++++++++------ src/executeCommandLine/executeCommandLine.ts | 26 +++++++++++------ 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index cab3de0e1ba..3eb637173c3 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4719,6 +4719,10 @@ "code": 6385, "reportsDeprecated": true }, + "Performance timings for '--diagnostics' or '--extendedDiagnostics' are not available in this session. A native implementation of the Web Performance API could not be found.": { + "category": "Message", + "code": 6386 + }, "The expected type comes from property '{0}' which is declared here on type '{1}'": { "category": "Message", diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index e37c2cac50a..b58cd7fff6f 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -67,7 +67,17 @@ namespace ts.performance { */ export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { if (perfHooks && enabled) { - perfHooks.performance.measure(measureName, startMarkName, endMarkName); + // NodeJS perf_hooks depends on call arity, not 'undefined' checks, so we + // need to be sure we call 'measure' with the correct number of arguments. + if (startMarkName === undefined) { + perfHooks.performance.measure(measureName); + } + else if (endMarkName === undefined) { + perfHooks.performance.measure(measureName, startMarkName); + } + else { + perfHooks.performance.measure(measureName, startMarkName, endMarkName); + } } } @@ -100,15 +110,23 @@ namespace ts.performance { }); } + /** + * Indicates whether the performance API is enabled. + */ + export function isEnabled() { + return enabled; + } + /** Enables (and resets) performance measurements for the compiler. */ export function enable() { if (!enabled) { perfHooks ||= tryGetNativePerformanceHooks() || ShimPerformance?.createPerformanceHooksShim(timestamp); - if (!perfHooks) throw new Error("TypeScript requires an environment that provides a compatible native Web Performance API implementation."); + if (!perfHooks) return false; perfObserver ||= new perfHooks.PerformanceObserver(list => perfEntryList = list); perfObserver.observe({ entryTypes: ["mark", "measure"] }); enabled = true; } + return true; } /** Disables performance measurements for the compiler. */ diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts index b1811d2405b..9c6d91d36a4 100644 --- a/src/compiler/performanceCore.ts +++ b/src/compiler/performanceCore.ts @@ -1,5 +1,8 @@ /*@internal*/ namespace ts { + // The following definitions provide the minimum compatible support for the Web Performance User Timings API + // between browsers and NodeJS: + export interface PerformanceHooks { performance: Performance; PerformanceObserver: PerformanceObserverConstructor; @@ -34,6 +37,7 @@ namespace ts { export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; export type PerformanceEntryList = PerformanceEntry[]; + // Browser globals for the Web Performance User Timings API declare const performance: Performance | undefined; declare const PerformanceObserver: PerformanceObserverConstructor | undefined; @@ -55,7 +59,17 @@ namespace ts { function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { if (typeof module === "object" && typeof require === "function") { try { - return require("perf_hooks") as typeof import("perf_hooks"); + const perfHooks = require("perf_hooks") as typeof import("perf_hooks"); + const { performance, PerformanceObserver } = perfHooks; + if (typeof performance === "object" && + typeof performance.timeOrigin === "number" && + typeof performance.clearMarks === "function" && + typeof performance.mark === "function" && + typeof performance.measure === "function" && + typeof performance.now === "function" && + typeof PerformanceObserver === "function") { + return perfHooks; + } } catch { // ignore errors @@ -63,18 +77,18 @@ namespace ts { } } + // Unlike with the native Map/Set 'tryGet' functions in corePublic.ts, we eagerly evaluate these + // since we will need them for `timestamp`, below. const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); + const nativePerformance = nativePerformanceHooks?.performance; export function tryGetNativePerformanceHooks() { return nativePerformanceHooks; } /** Gets a timestamp with (at least) ms resolution */ - export const timestamp = (() => { - if (nativePerformanceHooks) { - const performance = nativePerformanceHooks.performance; - return () => performance.now(); - } - return Date.now ? Date.now : () => +(new Date()); - })(); + export const timestamp = + nativePerformance ? () => nativePerformance.now() : + Date.now ? Date.now : + () => +(new Date()); } \ No newline at end of file diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index ce469755daf..ed501a007d2 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -648,19 +648,21 @@ namespace ts { reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); } - const programTime = performance.getDuration("Program"); - const bindTime = performance.getDuration("Bind"); - const checkTime = performance.getDuration("Check"); - const emitTime = performance.getDuration("Emit"); + const programTime = performance.isEnabled() ? performance.getDuration("Program") : 0; + const bindTime = performance.isEnabled() ? performance.getDuration("Bind") : 0; + const checkTime = performance.isEnabled() ? performance.getDuration("Check") : 0; + const emitTime = performance.isEnabled() ? performance.getDuration("Emit") : 0; if (compilerOptions.extendedDiagnostics) { const caches = program.getRelationCacheSizes(); reportCountStatistic("Assignability cache size", caches.assignable); reportCountStatistic("Identity cache size", caches.identity); reportCountStatistic("Subtype cache size", caches.subtype); reportCountStatistic("Strict subtype cache size", caches.strictSubtype); - performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); + if (performance.isEnabled()) { + performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); + } } - else { + else if (performance.isEnabled()) { // Individual component times. // Note: To match the behavior of previous versions of the compiler, the reported parse time includes // I/O read time and processing time for triple-slash references and module imports, and the reported @@ -672,10 +674,16 @@ namespace ts { reportTimeStatistic("Check time", checkTime); reportTimeStatistic("Emit time", emitTime); } - reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + if (performance.isEnabled()) { + reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + } reportStatistics(); - - performance.disable(); + if (!performance.isEnabled()) { + sys.write(Diagnostics.Performance_timings_for_diagnostics_or_extendedDiagnostics_are_not_available_in_this_session_A_native_implementation_of_the_Web_Performance_API_could_not_be_found.message + "\n"); + } + else { + performance.disable(); + } } function reportStatistics() {