mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Merge pull request #40593 from microsoft/nativePerformanceHooks
Migrate 'ts.performance' to use native performance hooks when available
This commit is contained in:
commit
db6f66cc4e
@ -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",
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
/*@internal*/
|
||||
/** Performance measurements for the compiler. */
|
||||
namespace ts.performance {
|
||||
declare const onProfilerEvent: { (markName: string): void; profiler: boolean; };
|
||||
|
||||
// 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 enabled = false;
|
||||
let profilerStart = 0;
|
||||
let counts: ESMap<string, number>;
|
||||
let marks: ESMap<string, number>;
|
||||
let measures: ESMap<string, number>;
|
||||
let perfHooks: PerformanceHooks | undefined;
|
||||
let perfObserver: PerformanceObserver | undefined;
|
||||
let perfEntryList: PerformanceObserverEntryList | 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;
|
||||
|
||||
export interface Timer {
|
||||
enter(): void;
|
||||
@ -53,11 +49,7 @@ 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);
|
||||
profilerEvent(markName);
|
||||
}
|
||||
performanceImpl?.mark(markName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,11 +62,7 @@ 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));
|
||||
}
|
||||
performanceImpl?.measure(measureName, startMarkName, endMarkName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +71,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 +80,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 +89,31 @@ 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(({ name, duration }) => { cb(name, duration); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the performance API is enabled.
|
||||
*/
|
||||
export function isEnabled() {
|
||||
return !!performanceImpl;
|
||||
}
|
||||
|
||||
/** 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 (!performanceImpl) {
|
||||
perfHooks ||= tryGetNativePerformanceHooks();
|
||||
if (!perfHooks) return false;
|
||||
perfObserver ||= new perfHooks.PerformanceObserver(list => perfEntryList = list);
|
||||
perfObserver.observe({ entryTypes: ["mark", "measure"] });
|
||||
performanceImpl = perfHooks.performance;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Disables performance measurements for the compiler. */
|
||||
export function disable() {
|
||||
enabled = false;
|
||||
perfObserver?.disconnect();
|
||||
performanceImpl = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
121
src/compiler/performanceCore.ts
Normal file
121
src/compiler/performanceCore.ts
Normal file
@ -0,0 +1,121 @@
|
||||
/*@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;
|
||||
}
|
||||
|
||||
export interface Performance {
|
||||
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[];
|
||||
|
||||
// Browser globals for the Web Performance User Timings API
|
||||
declare const performance: Performance | undefined;
|
||||
declare const PerformanceObserver: PerformanceObserverConstructor | undefined;
|
||||
|
||||
// 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.mark === "function" &&
|
||||
typeof performance.measure === "function" &&
|
||||
typeof performance.now === "function" &&
|
||||
typeof PerformanceObserver === "function";
|
||||
}
|
||||
|
||||
function tryGetWebPerformanceHooks(): PerformanceHooks | undefined {
|
||||
if (typeof performance === "object" &&
|
||||
typeof PerformanceObserver === "function" &&
|
||||
hasRequiredAPI(performance, PerformanceObserver)) {
|
||||
return {
|
||||
performance,
|
||||
PerformanceObserver
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetNodePerformanceHooks(): PerformanceHooks | undefined {
|
||||
if (typeof module === "object" && typeof require === "function") {
|
||||
try {
|
||||
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 {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 =
|
||||
nativePerformance ? () => nativePerformance.now() :
|
||||
Date.now ? Date.now :
|
||||
() => +(new Date());
|
||||
}
|
||||
@ -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()));
|
||||
|
||||
}
|
||||
@ -13,7 +13,7 @@
|
||||
"corePublic.ts",
|
||||
"core.ts",
|
||||
"debug.ts",
|
||||
"performanceTimestamp.ts",
|
||||
"performanceCore.ts",
|
||||
"performance.ts",
|
||||
"perfLogger.ts",
|
||||
"semver.ts",
|
||||
|
||||
@ -648,19 +648,22 @@ 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 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);
|
||||
performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration));
|
||||
if (isPerformanceEnabled) {
|
||||
performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration));
|
||||
}
|
||||
}
|
||||
else {
|
||||
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
|
||||
@ -672,10 +675,16 @@ namespace ts {
|
||||
reportTimeStatistic("Check time", checkTime);
|
||||
reportTimeStatistic("Emit time", emitTime);
|
||||
}
|
||||
reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime);
|
||||
if (isPerformanceEnabled) {
|
||||
reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime);
|
||||
}
|
||||
reportStatistics();
|
||||
|
||||
performance.disable();
|
||||
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 {
|
||||
performance.disable();
|
||||
}
|
||||
}
|
||||
|
||||
function reportStatistics() {
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
"outFile": "../../built/local/shims.js"
|
||||
},
|
||||
"files": [
|
||||
"collectionShims.ts"
|
||||
"collectionShims.ts",
|
||||
]
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ namespace ts.tscWatch {
|
||||
return createWatchProgram(compilerHost);
|
||||
}
|
||||
|
||||
const elapsedRegex = /^Elapsed:: [0-9]+ms/;
|
||||
const elapsedRegex = /^Elapsed:: \d+(?:\.\d+)?ms/;
|
||||
const buildVerboseLogRegEx = /^.+ \- /;
|
||||
export enum HostOutputKind {
|
||||
Log,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user