Skip to content
Closed
9 changes: 8 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,7 +1331,14 @@ namespace ts {
description: Diagnostics.Delete_the_outputs_of_all_projects,
type: "boolean",
defaultValueDescription: false,
}
},
{
name: "solutionDiagnostics",
type: "boolean",
category: Diagnostics.Compiler_Diagnostics,
description: Diagnostics.Output_more_detailed_solution_performance_information_after_building,
defaultValueDescription: false,
},
];

/* @internal */
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5261,7 +5261,7 @@
"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.": {
"Performance timings for '--diagnostics' or '--extendedDiagnostics' or '--solutionDiagnostics' are not available in this session. A native implementation of the Web Performance API could not be found.": {
"category": "Message",
"code": 6386
},
Expand Down Expand Up @@ -5799,6 +5799,10 @@
"category": "Message",
"code": 6718
},
"Output more detailed solution performance information after building.": {
"category": "Message",
"code": 6719
},
"Default catch clause variables as 'unknown' instead of 'any'.": {
"category": "Message",
"code": 6803
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ namespace ts {
sourceMap: compilerOptions.sourceMap,
inlineSourceMap: compilerOptions.inlineSourceMap,
inlineSources: compilerOptions.inlineSources,
extendedDiagnostics: compilerOptions.extendedDiagnostics,
extendedDiagnostics: compilerOptions.extendedDiagnostics || compilerOptions.solutionDiagnostics,
writeBundleFileInfo: !!bundleBuildInfo,
relativeToBuildInfo
};
Expand Down Expand Up @@ -453,7 +453,7 @@ namespace ts {
target: compilerOptions.target,
sourceMap: compilerOptions.sourceMap,
inlineSourceMap: compilerOptions.inlineSourceMap,
extendedDiagnostics: compilerOptions.extendedDiagnostics,
extendedDiagnostics: compilerOptions.extendedDiagnostics || compilerOptions.solutionDiagnostics,
onlyPrintJsDocStyle: true,
writeBundleFileInfo: !!bundleBuildInfo,
recordInternalSection: !!bundleBuildInfo,
Expand Down Expand Up @@ -482,7 +482,7 @@ namespace ts {
sourceMap: !forceDtsEmit && compilerOptions.declarationMap,
sourceRoot: compilerOptions.sourceRoot,
mapRoot: compilerOptions.mapRoot,
extendedDiagnostics: compilerOptions.extendedDiagnostics,
extendedDiagnostics: compilerOptions.extendedDiagnostics || compilerOptions.solutionDiagnostics,
// Explicitly do not passthru either `inline` option
}
);
Expand Down
258 changes: 143 additions & 115 deletions src/compiler/performance.ts
Original file line number Diff line number Diff line change
@@ -1,146 +1,174 @@
/*@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;
}
const nullTimer: Timer = { enter: noop, exit: noop };
export const performance = createPerformanceTracker();
export const solutionPerformance = createPerformanceTracker();

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>();

export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer {
let enterCount = 0;
return {
enter,
exit
createTimerIf,
createTimer,
mark,
measure,
getCount,
getDuration,
forEachMeasure,
forEachCount,
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 exit() {
if (--enterCount === 0) {
mark(endMarkName);
measure(measureName, startMarkName, endMarkName);
function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer {
let enterCount = 0;
return {
enter,
exit
};

function enter() {
if (++enterCount === 1) {
mark(startMarkName);
}
}
else if (enterCount < 0) {
Debug.fail("enter/exit count does not match.");

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

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);
/**
* 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?

}
}

/**
* 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);
}
}
}

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

/**
* 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));
}
/**
* Iterate over each count which is not duration mark, performing some action
*
* @param cb The action to perform for each measure
*/
function forEachCount(cb: (countName: string, count: number) => void) {
counts.forEach((count, countName) => !durationMarks.has(countName) && cb(countName, count));
}

/**
* Indicates whether the performance API is enabled.
*/
export function isEnabled() {
return enabled;
}
/**
* Indicates whether the performance API is enabled.
*/
function isEnabled() {
return enabled;
}

/** 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;
/** 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();
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