Skip to content

Report aggregate statistics for solution as well as some solution perf numbers #49285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
289 changes: 176 additions & 113 deletions src/compiler/performance.ts
Original file line number Diff line number Diff line change
@@ -1,146 +1,209 @@
/*@internal*/
/** Performance measurements for the compiler. */
namespace ts.performance {
let perfHooks: PerformanceHooks | 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 {
namespace ts {
interface Timer {
enter(): void;
exit(): void;
}

export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) {
return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer;
export interface Statistic {
name: string;
value: number;
type: StatisticType
}

export enum StatisticType {
time,
count,
memory,
Comment on lines +9 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear on what this is for, given that we already have a concept of "count" (i.e., "marks"), and "time" (i.e., "measure"). Memory usage isn't generally tracked by the native User Timings API but can be accomplished by providing additional metadata to a mark. We could probably extend our version of mark to support this behavior.

}

export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer {
let enterCount = 0;
const nullTimer: Timer = { enter: noop, exit: noop };
export const performance = createPerformanceTracker();
export const buildPerformance = createPerformanceTracker();
Comment on lines +22 to +23
Copy link
Contributor

@rbuckton rbuckton Jun 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we need two different performance instances? Do these two overlap WRT mark and measure names?

The original ts.performance API was designed to mirror the native User Timings API and essentially act as an adapter between the DOM and NodeJS implementations, but the user timings API doesn't support multiple instances.


function createPerformanceTracker() {
let perfHooks: PerformanceHooks | 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;
let enabled = false;
let timeorigin = timestamp();
const marks = new Map<string, number>();
const counts = new Map<string, number>();
const durations = new Map<string, number>();
const durationMarks = new Set<string>();
let statistics: ESMap<string, Statistic> | undefined;

return {
enter,
exit
createTimerIf,
createTimer,
mark,
measure,
addStatistics,
getCount,
getDuration,
forEachMeasure,
forEachCount,
forEachStatistics,
isEnabled,
enable,
disable,
};

function enter() {
if (++enterCount === 1) {
mark(startMarkName);
function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) {
return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer;
}

function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer {
let enterCount = 0;
return {
enter,
exit
};

function enter() {
if (++enterCount === 1) {
mark(startMarkName);
}
}

function exit() {
if (--enterCount === 0) {
mark(endMarkName);
measure(measureName, startMarkName, endMarkName);
}
else if (enterCount < 0) {
Debug.fail("enter/exit count does not match.");
}
}
}

function exit() {
if (--enterCount === 0) {
mark(endMarkName);
measure(measureName, startMarkName, endMarkName);
/**
* Marks a performance event.
*
* @param markName The name of the mark.
*/
function mark(markName: string) {
if (enabled) {
const count = counts.get(markName) ?? 0;
counts.set(markName, count + 1);
marks.set(markName, timestamp());
performanceImpl?.mark(markName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calls out to the native PerformanceHooks API for use with CPU profiles and a few other things in ts-perf. Is it intentional that both performance and buildPerformance call into this?

}
else if (enterCount < 0) {
Debug.fail("enter/exit count does not match.");
}

/**
* Adds a performance measurement with the specified name.
*
* @param measureName The name of the performance measurement.
* @param startMarkName The name of the starting mark. If not supplied, the point at which the
* profiler was enabled is used.
* @param endMarkName The name of the ending mark. If not supplied, the current timestamp is
* used.
*/
function measure(measureName: string, startMarkName: string, endMarkName: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signature no longer aligns with the native User Timings API. Neither a startMarkName nor an endMarkName are required.

if (enabled) {
durationMarks.add(startMarkName).add(endMarkName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these excluded here? They wouldn't be excluded by the native User Timings API.

const end = marks.get(endMarkName) ?? timestamp();
const start = marks.get(startMarkName) ?? timeorigin;
const previousDuration = durations.get(measureName) || 0;
durations.set(measureName, previousDuration + (end - start));
performanceImpl?.measure(measureName, startMarkName, endMarkName);
}
}

function addStatistics(s: Statistic) {
if (enabled) {
const existing = statistics?.get(s.name);
if (existing) {
if (existing.type === StatisticType.memory) existing.value = Math.max(existing.value, s.value);
else existing.value += s.value;
}
else {
(statistics ??= new Map()).set(s.name, s);
}
}
}
}

export const nullTimer: Timer = { enter: noop, exit: noop };

let enabled = false;
let timeorigin = timestamp();
const marks = new Map<string, number>();
const counts = new Map<string, number>();
const durations = new Map<string, number>();

/**
* Marks a performance event.
*
* @param markName The name of the mark.
*/
export function mark(markName: string) {
if (enabled) {
const count = counts.get(markName) ?? 0;
counts.set(markName, count + 1);
marks.set(markName, timestamp());
performanceImpl?.mark(markName);
/**
* Gets the number of times a marker was encountered.
*
* @param markName The name of the mark.
*/
function getCount(markName: string) {
return counts.get(markName) || 0;
}
}

/**
* Adds a performance measurement with the specified name.
*
* @param measureName The name of the performance measurement.
* @param startMarkName The name of the starting mark. If not supplied, the point at which the
* profiler was enabled is used.
* @param endMarkName The name of the ending mark. If not supplied, the current timestamp is
* used.
*/
export function measure(measureName: string, startMarkName?: string, endMarkName?: string) {
if (enabled) {
const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp();
const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin;
const previousDuration = durations.get(measureName) || 0;
durations.set(measureName, previousDuration + (end - start));
performanceImpl?.measure(measureName, startMarkName, endMarkName);
/**
* Gets the total duration of all measurements with the supplied name.
*
* @param measureName The name of the measure whose durations should be accumulated.
*/
function getDuration(measureName: string) {
return durations.get(measureName) || 0;
}
}

/**
* Gets the number of times a marker was encountered.
*
* @param markName The name of the mark.
*/
export function getCount(markName: string) {
return counts.get(markName) || 0;
}
/**
* Iterate over each measure, performing some action
*
* @param cb The action to perform for each measure
*/
function forEachMeasure(cb: (duration: number, measureName: string) => void) {
durations.forEach(cb);
}

/**
* Gets the total duration of all measurements with the supplied name.
*
* @param measureName The name of the measure whose durations should be accumulated.
*/
export function getDuration(measureName: string) {
return durations.get(measureName) || 0;
}
/**
* Iterate over each count which is not duration mark, performing some action
*
* @param cb The action to perform for each measure
*/
function forEachCount(cb: (count: number, countName: string) => void) {
counts.forEach((count, countName) => !durationMarks.has(countName) && cb(count, countName));
}

/**
* Iterate over each measure, performing some action
*
* @param cb The action to perform for each measure
*/
export function forEachMeasure(cb: (measureName: string, duration: number) => void) {
durations.forEach((duration, measureName) => cb(measureName, duration));
}

/**
* Indicates whether the performance API is enabled.
*/
export function isEnabled() {
return enabled;
}
function forEachStatistics(cb: (statistic: Statistic, name: string) => void) {
statistics?.forEach(cb);
}

/** Enables (and resets) performance measurements for the compiler. */
export function enable(system: System = sys) {
if (!enabled) {
enabled = true;
perfHooks ||= tryGetNativePerformanceHooks();
if (perfHooks) {
timeorigin = perfHooks.performance.timeOrigin;
// NodeJS's Web Performance API is currently slower than expected, but we'd still like
// to be able to leverage native trace events when node is run with either `--cpu-prof`
// or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when
// running in debug mode (since its possible to generate a cpu profile while debugging).
if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) {
performanceImpl = perfHooks.performance;
/**
* Indicates whether the performance API is enabled.
*/
function isEnabled() {
return enabled;
}

/** Enables (and resets) performance measurements for the compiler. */
function enable(system: System = sys) {
if (!enabled) {
enabled = true;
perfHooks ||= tryGetNativePerformanceHooks();
if (perfHooks) {
timeorigin = perfHooks.performance.timeOrigin;
// NodeJS's Web Performance API is currently slower than expected, but we'd still like
// to be able to leverage native trace events when node is run with either `--cpu-prof`
// or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when
// running in debug mode (since its possible to generate a cpu profile while debugging).
if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) {
performanceImpl = perfHooks.performance;
}
}
}
return true;
}
return true;
}

/** Disables performance measurements for the compiler. */
export function disable() {
if (enabled) {
marks.clear();
counts.clear();
durations.clear();
performanceImpl = undefined;
enabled = false;
/** Disables performance measurements for the compiler. */
function disable() {
if (enabled) {
marks.clear();
counts.clear();
durations.clear();
durationMarks.clear();
statistics?.clear();
performanceImpl = undefined;
enabled = false;
}
}
}
}
4 changes: 1 addition & 3 deletions src/compiler/sourcemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ namespace ts {
}

export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator {
const { enter, exit } = generatorOptions.extendedDiagnostics
? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap")
: performance.nullTimer;
const { enter, exit } = performance.createTimerIf(!!generatorOptions.extendedDiagnostics, "Source Map", "beforeSourcemap", "afterSourcemap");

// Current source map file and its index in the sources list
const rawSources: string[] = [];
Expand Down
Loading