Skip to content

Commit

Permalink
Migrate 'ts.performance' to use native performance hooks when available
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Sep 16, 2020
1 parent 23cb2d8 commit 4b5662b
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 33 deletions.
43 changes: 20 additions & 23 deletions src/compiler/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, number>;
let marks: ESMap<string, number>;
let measures: ESMap<string, number>;

export interface Timer {
enter(): void;
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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);
}
}

Expand All @@ -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;
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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<string, number>();
marks = new Map<string, number>();
measures = new Map<string, number>();
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;
}
}
80 changes: 80 additions & 0 deletions src/compiler/performanceCore.ts
Original file line number Diff line number Diff line change
@@ -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());
})();
}
6 changes: 0 additions & 6 deletions src/compiler/performanceTimestamp.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/compiler/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"corePublic.ts",
"core.ts",
"debug.ts",
"performanceTimestamp.ts",
"performanceCore.ts",
"performance.ts",
"perfLogger.ts",
"semver.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/harness/virtualFileSystemWithWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ interface Array<T> { length: number; [n: number]: T; }`

function baselineOutputs(baseline: string[], output: readonly string[], start: number, end = output.length) {
for (let i = start; i < end; i++) {
baseline.push(output[i].replace(/Elapsed::\s[0-9]+ms/g, "Elapsed:: *ms"));
baseline.push(output[i].replace(/Elapsed::\s[0-9]+(?:\.\d+)?ms/g, "Elapsed:: *ms"));
}
}

Expand Down
Loading

0 comments on commit 4b5662b

Please sign in to comment.