From a197beef6d185e3e40574540505882ddf1a9757d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 16 Sep 2020 11:06:55 -0700 Subject: [PATCH 1/6] Migrate 'ts.performance' to use native performance hooks when available --- src/compiler/performance.ts | 43 ++-- src/compiler/performanceCore.ts | 80 ++++++++ src/compiler/performanceTimestamp.ts | 26 --- src/compiler/tsconfig.json | 2 +- src/shims/performanceShims.ts | 203 +++++++++++++++++++ src/shims/tsconfig.json | 3 +- src/testRunner/unittests/tscWatch/helpers.ts | 2 +- 7 files changed, 307 insertions(+), 52 deletions(-) create mode 100644 src/compiler/performanceCore.ts delete mode 100644 src/compiler/performanceTimestamp.ts create mode 100644 src/shims/performanceShims.ts diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index dcee41c2d2c70..e37c2cac50a00 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -2,15 +2,12 @@ /** Performance measurements for the compiler. */ namespace ts.performance { declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; + const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : noop; - // NOTE: cannot use ts.noop as core.ts loads after this - const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : () => { /*empty*/ }; - + let perfHooks: PerformanceHooks | undefined; + let perfObserver: PerformanceObserver | undefined; + let perfEntryList: PerformanceObserverEntryList | undefined; let enabled = false; - let profilerStart = 0; - let counts: ESMap; - let marks: ESMap; - let measures: ESMap; export interface Timer { enter(): void; @@ -53,9 +50,8 @@ namespace ts.performance { * @param markName The name of the mark. */ export function mark(markName: string) { - if (enabled) { - marks.set(markName, timestamp()); - counts.set(markName, (counts.get(markName) || 0) + 1); + if (perfHooks && enabled) { + perfHooks.performance.mark(markName); profilerEvent(markName); } } @@ -70,10 +66,8 @@ namespace ts.performance { * used. */ export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (enabled) { - const end = endMarkName && marks.get(endMarkName) || timestamp(); - const start = startMarkName && marks.get(startMarkName) || profilerStart; - measures.set(measureName, (measures.get(measureName) || 0) + (end - start)); + if (perfHooks && enabled) { + perfHooks.performance.measure(measureName, startMarkName, endMarkName); } } @@ -83,7 +77,7 @@ namespace ts.performance { * @param markName The name of the mark. */ export function getCount(markName: string) { - return counts && counts.get(markName) || 0; + return perfEntryList?.getEntriesByName(markName, "mark").length || 0; } /** @@ -92,7 +86,7 @@ namespace ts.performance { * @param measureName The name of the measure whose durations should be accumulated. */ export function getDuration(measureName: string) { - return measures && measures.get(measureName) || 0; + return perfEntryList?.getEntriesByName(measureName, "measure").reduce((a, entry) => a + entry.duration, 0) || 0; } /** @@ -101,22 +95,25 @@ namespace ts.performance { * @param cb The action to perform for each measure */ export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - measures.forEach((measure, key) => { - cb(key, measure); + perfEntryList?.getEntriesByType("measure").forEach(entry => { + cb(entry.name, entry.duration); }); } /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - counts = new Map(); - marks = new Map(); - measures = new Map(); - enabled = true; - profilerStart = timestamp(); + 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."); + perfObserver ||= new perfHooks.PerformanceObserver(list => perfEntryList = list); + perfObserver.observe({ entryTypes: ["mark", "measure"] }); + enabled = true; + } } /** Disables performance measurements for the compiler. */ export function disable() { + perfObserver?.disconnect(); enabled = false; } } diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts new file mode 100644 index 0000000000000..b1811d2405b44 --- /dev/null +++ b/src/compiler/performanceCore.ts @@ -0,0 +1,80 @@ +/*@internal*/ +namespace ts { + export interface PerformanceHooks { + performance: Performance; + PerformanceObserver: PerformanceObserverConstructor; + } + + export interface Performance { + clearMarks(name?: string): void; + mark(name: string): void; + measure(name: string, startMark?: string, endMark?: string): void; + now(): number; + timeOrigin: number; + } + + export interface PerformanceEntry { + name: string; + entryType: string; + startTime: number; + duration: number; + } + + export interface PerformanceObserverEntryList { + getEntries(): PerformanceEntryList; + getEntriesByName(name: string, type?: string): PerformanceEntryList; + getEntriesByType(type: string): PerformanceEntryList; + } + + export interface PerformanceObserver { + disconnect(): void; + observe(options: { entryTypes: readonly string[] }): void; + } + + export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; + export type PerformanceEntryList = PerformanceEntry[]; + + declare const performance: Performance | undefined; + declare const PerformanceObserver: PerformanceObserverConstructor | undefined; + + function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { + 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 { + performance, + PerformanceObserver + }; + } + } + + function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { + if (typeof module === "object" && typeof require === "function") { + try { + return require("perf_hooks") as typeof import("perf_hooks"); + } + catch { + // ignore errors + } + } + } + + const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); + + 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()); + })(); +} \ No newline at end of file diff --git a/src/compiler/performanceTimestamp.ts b/src/compiler/performanceTimestamp.ts deleted file mode 100644 index 6be3c85721bb5..0000000000000 --- a/src/compiler/performanceTimestamp.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*@internal*/ -namespace ts { - declare const performance: { now?(): number } | undefined; - function tryGlobalPerformanceNow() { // browsers - if (typeof performance !== "undefined") return () => performance.now!(); - } - function tryNodePerformanceNow() { // node - try { - const perf_hooks = require("perf_hooks") as typeof import("perf_hooks"); - if (perf_hooks.performance) return () => perf_hooks.performance.now(); - } - // eslint-disable-next-line no-empty - catch { - } - } - function tryDateNow() { - if (Date.now) return () => Date.now(); - } - /** Gets a timestamp with (at least) ms resolution */ - export const timestamp: () => number = - tryGlobalPerformanceNow() - || tryNodePerformanceNow() - || tryDateNow() - || (() => +(new Date())); - -} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 1edb2bbd63da3..623a3aede4d9d 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -13,7 +13,7 @@ "corePublic.ts", "core.ts", "debug.ts", - "performanceTimestamp.ts", + "performanceCore.ts", "performance.ts", "perfLogger.ts", "semver.ts", diff --git a/src/shims/performanceShims.ts b/src/shims/performanceShims.ts new file mode 100644 index 0000000000000..c53bce6a79c15 --- /dev/null +++ b/src/shims/performanceShims.ts @@ -0,0 +1,203 @@ +/* @internal */ +namespace ts { + interface PerformanceHooksShim { + performance: PerformanceShim; + PerformanceObserver: PerformanceObserverShimConstructor; + } + + interface PerformanceShim { + clearMarks(name?: string): void; + mark(name: string): void; + measure(name: string, startMark?: string, endMark?: string): void; + now(): number; + timeOrigin: number; + } + + interface PerformanceEntryShim { + name: string; + entryType: string; + startTime: number; + duration: number; + } + + interface PerformanceObserverEntryListShim { + getEntries(): PerformanceEntryListShim; + getEntriesByName(name: string, type?: string): PerformanceEntryListShim; + getEntriesByType(type: string): PerformanceEntryListShim; + } + + interface PerformanceObserverShim { + disconnect(): void; + observe(options: { entryTypes: readonly string[] }): void; + } + + type PerformanceObserverShimConstructor = new (callback: (list: PerformanceObserverEntryListShim, observer: PerformanceObserverShim) => void) => PerformanceObserverShim; + + type PerformanceEntryListShim = PerformanceEntryShim[]; + + interface PerformanceObserverList { + readonly head: PerformanceObserverNode; + tail: PerformanceObserverNode; + } + + interface PerformanceObserverNode { + observer: PerformanceObserverData; + entryTypes: readonly string[]; + prev: PerformanceObserverNode | undefined; + next: PerformanceObserverNode | undefined; + } + + interface PerformanceObserverData { + buffer: PerformanceEntryShim[]; + node: PerformanceObserverNode | undefined; + } + + /* @internal */ + export namespace ShimPerformance { + export function createPerformanceHooksShim(now: () => number): PerformanceHooksShim { + const timeOrigin = now(); + const observerList = createObserverList(); + let marks = createDictionary(); + + function clearMarks(name?: string) { + if (name !== undefined) { + delete marks[name]; + } + else { + marks = createDictionary(); + } + } + + function mark(markName: string) { + const timestamp = now(); + marks[markName] = timestamp; + if (observerList.head) { + emit(createPerformanceEntry(markName, "mark", timestamp, 0)); + } + } + + function measure(measureName: string, startMark?: string, endMark?: string) { + if (observerList.head) { + const end = (endMark !== undefined ? marks[endMark] : undefined) ?? now(); + const start = (startMark !== undefined ? marks[startMark] : undefined) ?? timeOrigin; + emit(createPerformanceEntry(measureName, "measure", start, end - start)); + } + } + + function emit(entry: PerformanceEntryShim) { + let node: PerformanceObserverNode | undefined = observerList.head; + while (node) { + node = node.next; + if (node) { + if (node.entryTypes.indexOf(entry.entryType) !== -1) { + node.observer.buffer.push(entry); + } + } + } + } + + function createDictionary(): Record { + // eslint-disable-next-line boolean-trivia, no-null/no-null + const obj = Object.create(null); + obj.__ = undefined; + delete obj.__; + return obj; + } + + function createObserverList(): PerformanceObserverList { + const sentinel = {} as PerformanceObserverNode; + sentinel.prev = sentinel; + return { head: sentinel, tail: sentinel }; + } + + function createObserverNode(observer: PerformanceObserverData, entryTypes: readonly string[]): PerformanceObserverNode { + return { observer, entryTypes, prev: undefined, next: undefined }; + } + + function createObserverData(): PerformanceObserverData { + return { node: undefined, buffer: [] }; + } + + function createPerformanceEntry(name: string, entryType: string, startTime: number, duration: number): PerformanceEntryShim { + return { name, entryType, startTime, duration }; + } + + class PerformanceObserverEntryList implements PerformanceObserverEntryListShim { + private _data: PerformanceObserverData; + + constructor(ref: PerformanceObserverData) { + this._data = ref; + } + + getEntries(): PerformanceEntryShim[] { + return this._data.buffer.slice(); + } + + getEntriesByName(name: string, type?: string): PerformanceEntryShim[] { + return this._data.buffer.filter(event => event.name === name && (type === undefined || event.entryType === type)); + } + + getEntriesByType(type: string): PerformanceEntryShim[] { + return this._data.buffer.filter(event => event.entryType === type); + } + } + + class PerformanceObserver implements PerformanceObserverShim { + private _data = createObserverData(); + + constructor(callback: (list: PerformanceObserverEntryListShim, observer: PerformanceObserver) => void) { + const list = new PerformanceObserverEntryList(this._data); + callback(list, this); + } + + observe(options: { entryTypes: readonly string[] }) { + let entryTypes = options.entryTypes; + entryTypes = entryTypes.filter(entryType => entryType === "mark" || entryType === "measure"); + if (entryTypes.length === 0) return; + if (this._data.node) { + this._data.node.entryTypes = entryTypes; + } + else { + const node = createObserverNode(this._data, entryTypes); + node.prev = observerList.tail; + observerList.tail.next = node; + observerList.tail = node; + this._data.node = node; + } + } + + disconnect() { + const node = this._data.node; + if (node) { + // all nodes in have a 'prev' pointer. + if (node.prev === undefined) throw new Error("Illegal state"); + if (node.next) { + node.next.prev = node.prev; + } + else { + // a node in the list without a 'next' pointer must be the tail + if (observerList.tail !== node) throw new Error("Illegal state"); + observerList.tail = node.prev; + } + node.prev.next = node.next; + node.next = node.prev; + node.prev = undefined; + this._data.node = undefined; + this._data.buffer.length = 0; + } + } + } + + return { + performance: { + clearMarks, + mark, + measure, + now, + timeOrigin + }, + PerformanceObserver + }; + } + } +} \ No newline at end of file diff --git a/src/shims/tsconfig.json b/src/shims/tsconfig.json index 572ac1538a9fe..6b7f853777b3c 100644 --- a/src/shims/tsconfig.json +++ b/src/shims/tsconfig.json @@ -4,6 +4,7 @@ "outFile": "../../built/local/shims.js" }, "files": [ - "collectionShims.ts" + "collectionShims.ts", + "performanceShims.ts", ] } diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index caff32b99afd9..9ea81c4e61e61 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -43,7 +43,7 @@ namespace ts.tscWatch { return createWatchProgram(compilerHost); } - const elapsedRegex = /^Elapsed:: [0-9]+ms/; + const elapsedRegex = /^Elapsed:: [0-9]+(?:\.\d+)?ms/; const buildVerboseLogRegEx = /^.+ \- /; export enum HostOutputKind { Log, From dfa55add5a633e6f12928d19a9474fa7e73d7837 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 16 Sep 2020 16:46:50 -0700 Subject: [PATCH 2/6] 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 cab3de0e1bab0..3eb637173c37e 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 e37c2cac50a00..b58cd7fff6f41 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 b1811d2405b44..9c6d91d36a4a9 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 ce469755daf06..ed501a007d21d 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() { From c52e3b29e7fb18d4324309101aa3d55bb21dfb8d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 16 Sep 2020 17:24:31 -0700 Subject: [PATCH 3/6] Add unit tests for PerformanceHooks shim --- src/testRunner/tsconfig.json | 1 + .../unittests/createPerformanceHooksShim.ts | 251 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 src/testRunner/unittests/createPerformanceHooksShim.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index fc9b2272ca3fa..e6c7c7c55fb2d 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -75,6 +75,7 @@ "unittests/semver.ts", "unittests/createMapShim.ts", "unittests/createSetShim.ts", + "unittests/createPerformanceHooksShim.ts", "unittests/transform.ts", "unittests/config/commandLineParsing.ts", "unittests/config/configurationExtension.ts", diff --git a/src/testRunner/unittests/createPerformanceHooksShim.ts b/src/testRunner/unittests/createPerformanceHooksShim.ts new file mode 100644 index 0000000000000..6fceaf3e78493 --- /dev/null +++ b/src/testRunner/unittests/createPerformanceHooksShim.ts @@ -0,0 +1,251 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ // chai's expect allows you to use dot-property assertions like `.is.empty` +namespace ts { + describe("unittests:: createPerformanceHooksShim", () => { + it("has expected API", () => { + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(Date.now); + expect(performance).to.be.an("object"); + expect(performance.timeOrigin).to.be.a("number"); + expect(performance.clearMarks).to.be.a("function"); + expect(performance.mark).to.be.a("function"); + expect(performance.measure).to.be.a("function"); + expect(performance.now).to.be.a("function"); + expect(PerformanceObserver).to.be.a("function"); + let list!: PerformanceObserverEntryList; + let observer2!: PerformanceObserver; + const observer = new PerformanceObserver((_list, observer) => { list = _list; observer2 = observer; }); + expect(list).to.be.an("object"); + expect(observer2).to.be.an("object"); + expect(observer2).to.equal(observer); + expect(observer2.disconnect).to.be.a("function"); + expect(observer2.observe).to.be.a("function"); + expect(list.getEntries).to.be.a("function"); + expect(list.getEntriesByName).to.be.a("function"); + expect(list.getEntriesByType).to.be.a("function"); + }); + it("only listens for events while connected", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + + performance.mark("a"); + const entries1 = list.getEntries(); + observer.observe({ entryTypes: ["mark"] }); + performance.mark("b"); + const entries2 = list.getEntries(); + observer.disconnect(); + performance.mark("c"); + const entries3 = list.getEntries(); + + expect(entries1).to.be.empty; + expect(entries2).to.not.be.empty; + expect(entries3).to.be.empty; + }); + it("Can get entries by name and type (mark)", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark", "measure"] }); + performance.mark("a"); + performance.measure("b", "a"); + const entries = list.getEntriesByName("a", "mark"); + const entries2 = list.getEntriesByName("b", "mark"); + observer.disconnect(); + expect(entries).to.have.lengthOf(1); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("a"); + expect(entries[0].entryType).to.equal("mark"); + expect(entries2).to.be.empty; + }); + it("Can get entries by name and type (measure)", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark", "measure"] }); + performance.mark("a"); + performance.measure("b", "a"); + const entries = list.getEntriesByName("b", "measure"); + const entries2 = list.getEntriesByName("a", "measure"); + observer.disconnect(); + expect(entries).to.have.lengthOf(1); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("b"); + expect(entries[0].entryType).to.equal("measure"); + expect(entries2).to.be.empty; + }); + it("Can get entries by name", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark", "measure"] }); + performance.mark("a"); + performance.measure("b", "a"); + const entries = list.getEntriesByName("a"); + const entries2 = list.getEntriesByName("b"); + observer.disconnect(); + expect(entries).to.not.be.empty; + expect(entries).to.have.lengthOf(1); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("a"); + expect(entries[0].entryType).to.equal("mark"); + expect(entries2).to.have.lengthOf(1); + expect(entries2[0]).to.be.an("object"); + expect(entries2[0].name).to.equal("b"); + expect(entries2[0].entryType).to.equal("measure"); + }); + it("Can get entries by type", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark", "measure"] }); + performance.mark("a"); + performance.measure("b", "a"); + const entries = list.getEntriesByType("mark"); + const entries2 = list.getEntriesByType("measure"); + observer.disconnect(); + expect(entries).to.have.lengthOf(1); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("a"); + expect(entries[0].entryType).to.equal("mark"); + expect(entries2).to.have.lengthOf(1); + expect(entries2[0]).to.be.an("object"); + expect(entries2[0].name).to.equal("b"); + expect(entries2[0].entryType).to.equal("measure"); + }); + it("Can get entries", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark", "measure"] }); + performance.mark("a"); + performance.measure("b", "a"); + const entries = list.getEntries(); + observer.disconnect(); + expect(entries).to.have.lengthOf(2); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("a"); + expect(entries[0].entryType).to.equal("mark"); + expect(entries[1]).to.be.an("object"); + expect(entries[1].name).to.equal("b"); + expect(entries[1].entryType).to.equal("measure"); + }); + it("Unobserved entries are ignored", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark"] }); + performance.mark("a"); + performance.measure("b", "a"); + const entries = list.getEntries(); + observer.disconnect(); + expect(entries).to.have.lengthOf(1); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("a"); + expect(entries[0].entryType).to.equal("mark"); + }); + it("Changing what's observed only affects new entries", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark"] }); + performance.mark("a"); + performance.measure("b", "a"); + observer.observe({ entryTypes: ["measure"] }); + performance.mark("c"); + performance.measure("d", "c"); + const entries = list.getEntries(); + observer.disconnect(); + expect(entries).to.have.lengthOf(2); + expect(entries[0]).to.be.an("object"); + expect(entries[0].name).to.equal("a"); + expect(entries[0].entryType).to.equal("mark"); + expect(entries[1]).to.be.an("object"); + expect(entries[1].name).to.equal("d"); + expect(entries[1].entryType).to.equal("measure"); + }); + it("mark tracks current time", () => { + let timestamp = 0; + const now = () => timestamp; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark"] }); + const ts1 = timestamp; + performance.mark("a"); + timestamp++; + const ts2 = timestamp; + performance.mark("b"); + const entries = list.getEntries(); + observer.disconnect(); + expect(entries[0].startTime).to.equal(ts1); + expect(entries[0].duration).to.equal(0); + expect(entries[1].startTime).to.equal(ts2); + expect(entries[1].duration).to.equal(0); + }); + it("measure tracks time between marks", () => { + let timestamp = 0; + const now = () => timestamp; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark", "measure"] }); + const ts1 = timestamp; + performance.mark("a"); + timestamp++; + const ts2 = timestamp; + performance.mark("b"); + performance.measure("c", "a", "b"); + const entries = list.getEntriesByType("measure"); + observer.disconnect(); + expect(entries[0].startTime).to.equal(ts1); + expect(entries[0].duration).to.equal(ts2 - ts1); + }); + it("measure tracks time between unobserved marks", () => { + let timestamp = 0; + const now = () => timestamp; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["measure"] }); + const ts1 = timestamp; + performance.mark("a"); + timestamp++; + const ts2 = timestamp; + performance.mark("b"); + performance.measure("c", "a", "b"); + const entries = list.getEntries(); + observer.disconnect(); + expect(entries[0].startTime).to.equal(ts1); + expect(entries[0].duration).to.equal(ts2 - ts1); + }); + it("marks can be counted", () => { + let timestamp = 0; + const now = () => timestamp++; + const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); + let list!: PerformanceObserverEntryList; + const observer = new PerformanceObserver(_list => list = _list); + observer.observe({ entryTypes: ["mark"] }); + performance.mark("a"); + performance.mark("a"); + performance.mark("a"); + const entries = list.getEntries(); + observer.disconnect(); + expect(entries).to.have.lengthOf(3); + }); + }); +} \ No newline at end of file From 68806c67f1519f8674306c5fae434a37c5ccd2fc Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 16 Sep 2020 17:44:56 -0700 Subject: [PATCH 4/6] Simplify tests for enablement in mark/measure, remove onProfilerEvent --- src/compiler/performance.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index b58cd7fff6f41..a6656006ee859 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -1,13 +1,12 @@ /*@internal*/ /** Performance measurements for the compiler. */ namespace ts.performance { - declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; - const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : noop; - let perfHooks: PerformanceHooks | undefined; let perfObserver: PerformanceObserver | undefined; let perfEntryList: PerformanceObserverEntryList | undefined; - let enabled = false; + // 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; export interface Timer { enter(): void; @@ -50,9 +49,8 @@ namespace ts.performance { * @param markName The name of the mark. */ export function mark(markName: string) { - if (perfHooks && enabled) { - perfHooks.performance.mark(markName); - profilerEvent(markName); + if (performanceImpl) { + performanceImpl.mark(markName); } } @@ -66,17 +64,17 @@ namespace ts.performance { * used. */ export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (perfHooks && enabled) { + if (performanceImpl) { // 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); + performanceImpl.measure(measureName); } else if (endMarkName === undefined) { - perfHooks.performance.measure(measureName, startMarkName); + performanceImpl.measure(measureName, startMarkName); } else { - perfHooks.performance.measure(measureName, startMarkName, endMarkName); + performanceImpl.measure(measureName, startMarkName, endMarkName); } } } @@ -114,17 +112,17 @@ namespace ts.performance { * Indicates whether the performance API is enabled. */ export function isEnabled() { - return enabled; + return !!performanceImpl; } /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - if (!enabled) { + if (!performanceImpl) { perfHooks ||= tryGetNativePerformanceHooks() || ShimPerformance?.createPerformanceHooksShim(timestamp); if (!perfHooks) return false; perfObserver ||= new perfHooks.PerformanceObserver(list => perfEntryList = list); perfObserver.observe({ entryTypes: ["mark", "measure"] }); - enabled = true; + performanceImpl = perfHooks.performance; } return true; } @@ -132,6 +130,6 @@ namespace ts.performance { /** Disables performance measurements for the compiler. */ export function disable() { perfObserver?.disconnect(); - enabled = false; + performanceImpl = undefined; } } From c5800d1928e53ccfb019c36d5b2083e1779b0a99 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 22 Oct 2020 13:28:05 -0700 Subject: [PATCH 5/6] Remove shims, workaround for bug in peformance.measure --- src/compiler/performance.ts | 24 +- src/compiler/performanceCore.ts | 57 ++-- src/shims/performanceShims.ts | 203 -------------- src/shims/tsconfig.json | 1 - src/testRunner/tsconfig.json | 1 - .../unittests/createPerformanceHooksShim.ts | 251 ------------------ src/testRunner/unittests/tscWatch/helpers.ts | 2 +- 7 files changed, 47 insertions(+), 492 deletions(-) delete mode 100644 src/shims/performanceShims.ts delete mode 100644 src/testRunner/unittests/createPerformanceHooksShim.ts diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index a6656006ee859..9f7d19c95df75 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -49,9 +49,7 @@ namespace ts.performance { * @param markName The name of the mark. */ export function mark(markName: string) { - if (performanceImpl) { - performanceImpl.mark(markName); - } + performanceImpl?.mark(markName); } /** @@ -64,19 +62,7 @@ namespace ts.performance { * used. */ export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (performanceImpl) { - // 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) { - performanceImpl.measure(measureName); - } - else if (endMarkName === undefined) { - performanceImpl.measure(measureName, startMarkName); - } - else { - performanceImpl.measure(measureName, startMarkName, endMarkName); - } - } + performanceImpl?.measure(measureName, startMarkName, endMarkName); } /** @@ -103,9 +89,7 @@ namespace ts.performance { * @param cb The action to perform for each measure */ export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - perfEntryList?.getEntriesByType("measure").forEach(entry => { - cb(entry.name, entry.duration); - }); + perfEntryList?.getEntriesByType("measure").forEach(({ name, duration }) => { cb(name, duration); }); } /** @@ -118,7 +102,7 @@ namespace ts.performance { /** Enables (and resets) performance measurements for the compiler. */ export function enable() { if (!performanceImpl) { - perfHooks ||= tryGetNativePerformanceHooks() || ShimPerformance?.createPerformanceHooksShim(timestamp); + perfHooks ||= tryGetNativePerformanceHooks(); if (!perfHooks) return false; perfObserver ||= new perfHooks.PerformanceObserver(list => perfEntryList = list); perfObserver.observe({ entryTypes: ["mark", "measure"] }); diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts index 9c6d91d36a4a9..69afa9388137a 100644 --- a/src/compiler/performanceCore.ts +++ b/src/compiler/performanceCore.ts @@ -9,7 +9,6 @@ namespace ts { } export interface Performance { - clearMarks(name?: string): void; mark(name: string): void; measure(name: string, startMark?: string, endMark?: string): void; now(): number; @@ -41,14 +40,20 @@ namespace ts { declare const performance: Performance | undefined; declare const PerformanceObserver: PerformanceObserverConstructor | undefined; - function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { - if (typeof performance === "object" && + // eslint-disable-next-line @typescript-eslint/naming-convention + function hasRequiredAPI(performance: Performance | undefined, PerformanceObserver: PerformanceObserverConstructor | undefined) { + return 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") { + typeof PerformanceObserver === "function"; + } + + function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { + if (typeof performance === "object" && + typeof PerformanceObserver === "function" && + hasRequiredAPI(performance, PerformanceObserver)) { return { performance, PerformanceObserver @@ -59,16 +64,38 @@ namespace ts { function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { if (typeof module === "object" && typeof require === "function") { try { - 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; + const { performance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks"); + if (hasRequiredAPI(performance, PerformanceObserver)) { + // 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 || 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__"); + } + } + }, + PerformanceObserver + }; + } + return { + performance, + PerformanceObserver + }; } } catch { diff --git a/src/shims/performanceShims.ts b/src/shims/performanceShims.ts deleted file mode 100644 index c53bce6a79c15..0000000000000 --- a/src/shims/performanceShims.ts +++ /dev/null @@ -1,203 +0,0 @@ -/* @internal */ -namespace ts { - interface PerformanceHooksShim { - performance: PerformanceShim; - PerformanceObserver: PerformanceObserverShimConstructor; - } - - interface PerformanceShim { - clearMarks(name?: string): void; - mark(name: string): void; - measure(name: string, startMark?: string, endMark?: string): void; - now(): number; - timeOrigin: number; - } - - interface PerformanceEntryShim { - name: string; - entryType: string; - startTime: number; - duration: number; - } - - interface PerformanceObserverEntryListShim { - getEntries(): PerformanceEntryListShim; - getEntriesByName(name: string, type?: string): PerformanceEntryListShim; - getEntriesByType(type: string): PerformanceEntryListShim; - } - - interface PerformanceObserverShim { - disconnect(): void; - observe(options: { entryTypes: readonly string[] }): void; - } - - type PerformanceObserverShimConstructor = new (callback: (list: PerformanceObserverEntryListShim, observer: PerformanceObserverShim) => void) => PerformanceObserverShim; - - type PerformanceEntryListShim = PerformanceEntryShim[]; - - interface PerformanceObserverList { - readonly head: PerformanceObserverNode; - tail: PerformanceObserverNode; - } - - interface PerformanceObserverNode { - observer: PerformanceObserverData; - entryTypes: readonly string[]; - prev: PerformanceObserverNode | undefined; - next: PerformanceObserverNode | undefined; - } - - interface PerformanceObserverData { - buffer: PerformanceEntryShim[]; - node: PerformanceObserverNode | undefined; - } - - /* @internal */ - export namespace ShimPerformance { - export function createPerformanceHooksShim(now: () => number): PerformanceHooksShim { - const timeOrigin = now(); - const observerList = createObserverList(); - let marks = createDictionary(); - - function clearMarks(name?: string) { - if (name !== undefined) { - delete marks[name]; - } - else { - marks = createDictionary(); - } - } - - function mark(markName: string) { - const timestamp = now(); - marks[markName] = timestamp; - if (observerList.head) { - emit(createPerformanceEntry(markName, "mark", timestamp, 0)); - } - } - - function measure(measureName: string, startMark?: string, endMark?: string) { - if (observerList.head) { - const end = (endMark !== undefined ? marks[endMark] : undefined) ?? now(); - const start = (startMark !== undefined ? marks[startMark] : undefined) ?? timeOrigin; - emit(createPerformanceEntry(measureName, "measure", start, end - start)); - } - } - - function emit(entry: PerformanceEntryShim) { - let node: PerformanceObserverNode | undefined = observerList.head; - while (node) { - node = node.next; - if (node) { - if (node.entryTypes.indexOf(entry.entryType) !== -1) { - node.observer.buffer.push(entry); - } - } - } - } - - function createDictionary(): Record { - // eslint-disable-next-line boolean-trivia, no-null/no-null - const obj = Object.create(null); - obj.__ = undefined; - delete obj.__; - return obj; - } - - function createObserverList(): PerformanceObserverList { - const sentinel = {} as PerformanceObserverNode; - sentinel.prev = sentinel; - return { head: sentinel, tail: sentinel }; - } - - function createObserverNode(observer: PerformanceObserverData, entryTypes: readonly string[]): PerformanceObserverNode { - return { observer, entryTypes, prev: undefined, next: undefined }; - } - - function createObserverData(): PerformanceObserverData { - return { node: undefined, buffer: [] }; - } - - function createPerformanceEntry(name: string, entryType: string, startTime: number, duration: number): PerformanceEntryShim { - return { name, entryType, startTime, duration }; - } - - class PerformanceObserverEntryList implements PerformanceObserverEntryListShim { - private _data: PerformanceObserverData; - - constructor(ref: PerformanceObserverData) { - this._data = ref; - } - - getEntries(): PerformanceEntryShim[] { - return this._data.buffer.slice(); - } - - getEntriesByName(name: string, type?: string): PerformanceEntryShim[] { - return this._data.buffer.filter(event => event.name === name && (type === undefined || event.entryType === type)); - } - - getEntriesByType(type: string): PerformanceEntryShim[] { - return this._data.buffer.filter(event => event.entryType === type); - } - } - - class PerformanceObserver implements PerformanceObserverShim { - private _data = createObserverData(); - - constructor(callback: (list: PerformanceObserverEntryListShim, observer: PerformanceObserver) => void) { - const list = new PerformanceObserverEntryList(this._data); - callback(list, this); - } - - observe(options: { entryTypes: readonly string[] }) { - let entryTypes = options.entryTypes; - entryTypes = entryTypes.filter(entryType => entryType === "mark" || entryType === "measure"); - if (entryTypes.length === 0) return; - if (this._data.node) { - this._data.node.entryTypes = entryTypes; - } - else { - const node = createObserverNode(this._data, entryTypes); - node.prev = observerList.tail; - observerList.tail.next = node; - observerList.tail = node; - this._data.node = node; - } - } - - disconnect() { - const node = this._data.node; - if (node) { - // all nodes in have a 'prev' pointer. - if (node.prev === undefined) throw new Error("Illegal state"); - if (node.next) { - node.next.prev = node.prev; - } - else { - // a node in the list without a 'next' pointer must be the tail - if (observerList.tail !== node) throw new Error("Illegal state"); - observerList.tail = node.prev; - } - node.prev.next = node.next; - node.next = node.prev; - node.prev = undefined; - this._data.node = undefined; - this._data.buffer.length = 0; - } - } - } - - return { - performance: { - clearMarks, - mark, - measure, - now, - timeOrigin - }, - PerformanceObserver - }; - } - } -} \ No newline at end of file diff --git a/src/shims/tsconfig.json b/src/shims/tsconfig.json index 6b7f853777b3c..ebea929016ac0 100644 --- a/src/shims/tsconfig.json +++ b/src/shims/tsconfig.json @@ -5,6 +5,5 @@ }, "files": [ "collectionShims.ts", - "performanceShims.ts", ] } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index e6c7c7c55fb2d..fc9b2272ca3fa 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -75,7 +75,6 @@ "unittests/semver.ts", "unittests/createMapShim.ts", "unittests/createSetShim.ts", - "unittests/createPerformanceHooksShim.ts", "unittests/transform.ts", "unittests/config/commandLineParsing.ts", "unittests/config/configurationExtension.ts", diff --git a/src/testRunner/unittests/createPerformanceHooksShim.ts b/src/testRunner/unittests/createPerformanceHooksShim.ts deleted file mode 100644 index 6fceaf3e78493..0000000000000 --- a/src/testRunner/unittests/createPerformanceHooksShim.ts +++ /dev/null @@ -1,251 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions */ // chai's expect allows you to use dot-property assertions like `.is.empty` -namespace ts { - describe("unittests:: createPerformanceHooksShim", () => { - it("has expected API", () => { - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(Date.now); - expect(performance).to.be.an("object"); - expect(performance.timeOrigin).to.be.a("number"); - expect(performance.clearMarks).to.be.a("function"); - expect(performance.mark).to.be.a("function"); - expect(performance.measure).to.be.a("function"); - expect(performance.now).to.be.a("function"); - expect(PerformanceObserver).to.be.a("function"); - let list!: PerformanceObserverEntryList; - let observer2!: PerformanceObserver; - const observer = new PerformanceObserver((_list, observer) => { list = _list; observer2 = observer; }); - expect(list).to.be.an("object"); - expect(observer2).to.be.an("object"); - expect(observer2).to.equal(observer); - expect(observer2.disconnect).to.be.a("function"); - expect(observer2.observe).to.be.a("function"); - expect(list.getEntries).to.be.a("function"); - expect(list.getEntriesByName).to.be.a("function"); - expect(list.getEntriesByType).to.be.a("function"); - }); - it("only listens for events while connected", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - - performance.mark("a"); - const entries1 = list.getEntries(); - observer.observe({ entryTypes: ["mark"] }); - performance.mark("b"); - const entries2 = list.getEntries(); - observer.disconnect(); - performance.mark("c"); - const entries3 = list.getEntries(); - - expect(entries1).to.be.empty; - expect(entries2).to.not.be.empty; - expect(entries3).to.be.empty; - }); - it("Can get entries by name and type (mark)", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark", "measure"] }); - performance.mark("a"); - performance.measure("b", "a"); - const entries = list.getEntriesByName("a", "mark"); - const entries2 = list.getEntriesByName("b", "mark"); - observer.disconnect(); - expect(entries).to.have.lengthOf(1); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("a"); - expect(entries[0].entryType).to.equal("mark"); - expect(entries2).to.be.empty; - }); - it("Can get entries by name and type (measure)", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark", "measure"] }); - performance.mark("a"); - performance.measure("b", "a"); - const entries = list.getEntriesByName("b", "measure"); - const entries2 = list.getEntriesByName("a", "measure"); - observer.disconnect(); - expect(entries).to.have.lengthOf(1); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("b"); - expect(entries[0].entryType).to.equal("measure"); - expect(entries2).to.be.empty; - }); - it("Can get entries by name", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark", "measure"] }); - performance.mark("a"); - performance.measure("b", "a"); - const entries = list.getEntriesByName("a"); - const entries2 = list.getEntriesByName("b"); - observer.disconnect(); - expect(entries).to.not.be.empty; - expect(entries).to.have.lengthOf(1); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("a"); - expect(entries[0].entryType).to.equal("mark"); - expect(entries2).to.have.lengthOf(1); - expect(entries2[0]).to.be.an("object"); - expect(entries2[0].name).to.equal("b"); - expect(entries2[0].entryType).to.equal("measure"); - }); - it("Can get entries by type", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark", "measure"] }); - performance.mark("a"); - performance.measure("b", "a"); - const entries = list.getEntriesByType("mark"); - const entries2 = list.getEntriesByType("measure"); - observer.disconnect(); - expect(entries).to.have.lengthOf(1); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("a"); - expect(entries[0].entryType).to.equal("mark"); - expect(entries2).to.have.lengthOf(1); - expect(entries2[0]).to.be.an("object"); - expect(entries2[0].name).to.equal("b"); - expect(entries2[0].entryType).to.equal("measure"); - }); - it("Can get entries", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark", "measure"] }); - performance.mark("a"); - performance.measure("b", "a"); - const entries = list.getEntries(); - observer.disconnect(); - expect(entries).to.have.lengthOf(2); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("a"); - expect(entries[0].entryType).to.equal("mark"); - expect(entries[1]).to.be.an("object"); - expect(entries[1].name).to.equal("b"); - expect(entries[1].entryType).to.equal("measure"); - }); - it("Unobserved entries are ignored", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark"] }); - performance.mark("a"); - performance.measure("b", "a"); - const entries = list.getEntries(); - observer.disconnect(); - expect(entries).to.have.lengthOf(1); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("a"); - expect(entries[0].entryType).to.equal("mark"); - }); - it("Changing what's observed only affects new entries", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark"] }); - performance.mark("a"); - performance.measure("b", "a"); - observer.observe({ entryTypes: ["measure"] }); - performance.mark("c"); - performance.measure("d", "c"); - const entries = list.getEntries(); - observer.disconnect(); - expect(entries).to.have.lengthOf(2); - expect(entries[0]).to.be.an("object"); - expect(entries[0].name).to.equal("a"); - expect(entries[0].entryType).to.equal("mark"); - expect(entries[1]).to.be.an("object"); - expect(entries[1].name).to.equal("d"); - expect(entries[1].entryType).to.equal("measure"); - }); - it("mark tracks current time", () => { - let timestamp = 0; - const now = () => timestamp; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark"] }); - const ts1 = timestamp; - performance.mark("a"); - timestamp++; - const ts2 = timestamp; - performance.mark("b"); - const entries = list.getEntries(); - observer.disconnect(); - expect(entries[0].startTime).to.equal(ts1); - expect(entries[0].duration).to.equal(0); - expect(entries[1].startTime).to.equal(ts2); - expect(entries[1].duration).to.equal(0); - }); - it("measure tracks time between marks", () => { - let timestamp = 0; - const now = () => timestamp; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark", "measure"] }); - const ts1 = timestamp; - performance.mark("a"); - timestamp++; - const ts2 = timestamp; - performance.mark("b"); - performance.measure("c", "a", "b"); - const entries = list.getEntriesByType("measure"); - observer.disconnect(); - expect(entries[0].startTime).to.equal(ts1); - expect(entries[0].duration).to.equal(ts2 - ts1); - }); - it("measure tracks time between unobserved marks", () => { - let timestamp = 0; - const now = () => timestamp; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["measure"] }); - const ts1 = timestamp; - performance.mark("a"); - timestamp++; - const ts2 = timestamp; - performance.mark("b"); - performance.measure("c", "a", "b"); - const entries = list.getEntries(); - observer.disconnect(); - expect(entries[0].startTime).to.equal(ts1); - expect(entries[0].duration).to.equal(ts2 - ts1); - }); - it("marks can be counted", () => { - let timestamp = 0; - const now = () => timestamp++; - const { performance, PerformanceObserver } = ShimPerformance.createPerformanceHooksShim(now); - let list!: PerformanceObserverEntryList; - const observer = new PerformanceObserver(_list => list = _list); - observer.observe({ entryTypes: ["mark"] }); - performance.mark("a"); - performance.mark("a"); - performance.mark("a"); - const entries = list.getEntries(); - observer.disconnect(); - expect(entries).to.have.lengthOf(3); - }); - }); -} \ No newline at end of file diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index 9ea81c4e61e61..5201cda4f3779 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -43,7 +43,7 @@ namespace ts.tscWatch { return createWatchProgram(compilerHost); } - const elapsedRegex = /^Elapsed:: [0-9]+(?:\.\d+)?ms/; + const elapsedRegex = /^Elapsed:: \d+(?:\.\d+)?ms/; const buildVerboseLogRegEx = /^.+ \- /; export enum HostOutputKind { Log, From 7b0d049b89d674e6221687412016753ec025e24a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 23 Oct 2020 17:08:07 -0700 Subject: [PATCH 6/6] PR feedback --- src/executeCommandLine/executeCommandLine.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index ed501a007d21d..54ed94e375b67 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -648,21 +648,22 @@ namespace ts { reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); } - 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; + const isPerformanceEnabled = performance.isEnabled(); + const programTime = isPerformanceEnabled ? performance.getDuration("Program") : 0; + const bindTime = isPerformanceEnabled ? performance.getDuration("Bind") : 0; + const checkTime = isPerformanceEnabled ? performance.getDuration("Check") : 0; + const emitTime = isPerformanceEnabled ? 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); - if (performance.isEnabled()) { + if (isPerformanceEnabled) { performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); } } - else if (performance.isEnabled()) { + else if (isPerformanceEnabled) { // 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 @@ -674,11 +675,11 @@ namespace ts { reportTimeStatistic("Check time", checkTime); reportTimeStatistic("Emit time", emitTime); } - if (performance.isEnabled()) { + if (isPerformanceEnabled) { reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); } reportStatistics(); - if (!performance.isEnabled()) { + if (!isPerformanceEnabled) { 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 {