diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 3f6c23f2b0735..73493289f96fa 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -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, } - 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(); + + 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(); + const counts = new Map(); + const durations = new Map(); + const durationMarks = new Set(); + let statistics: ESMap | 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); } - 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) { + if (enabled) { + durationMarks.add(startMarkName).add(endMarkName); + 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(); - const counts = new Map(); - const durations = new Map(); - - /** - * 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; + } } } } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index e3aebc849b6a3..22bb9e92424e7 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -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[] = []; diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index e7d4d8196e13e..d07998d48ef9f 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -401,7 +401,7 @@ namespace ts { if (value) { return isParsedCommandLine(value) ? value : undefined; } - + buildPerformance.mark("beforeConfigFileParsing"); let diagnostic: Diagnostic | undefined; const { parseConfigFileHost, baseCompilerOptions, baseWatchOptions, extendedConfigCache, host } = state; let parsed: ParsedCommandLine | undefined; @@ -415,6 +415,8 @@ namespace ts { parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; } configFileCache.set(configFilePath, parsed || diagnostic!); + buildPerformance.mark("afterConfigFileParsing"); + buildPerformance.measure("Config file parsing", "beforeConfigFileParsing", "afterConfigFileParsing"); return parsed; } @@ -732,6 +734,7 @@ namespace ts { if (updateOutputFileStampsPending) { updateOutputTimestamps(state, config, projectPath); } + buildPerformance.mark("Timestamps only updates"); return doneInvalidatedProject(state, projectPath); } }; @@ -845,6 +848,8 @@ namespace ts { function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { executeSteps(BuildStep.Done, cancellationToken, writeFile, customTransformers); + if (kind === InvalidatedProjectKind.Build) buildPerformance.mark("Projects built"); + else buildPerformance.mark("Bundles updated"); return doneInvalidatedProject(state, projectPath); } @@ -1342,8 +1347,7 @@ namespace ts { reportQueue: boolean ): InvalidatedProject | undefined { const info = getNextInvalidatedProjectCreateInfo(state, buildOrder, reportQueue); - if (!info) return info; - return createInvalidatedProjectWithInfo(state, info, buildOrder); + return info && createInvalidatedProjectWithInfo(state, info, buildOrder); } function listEmittedFile({ write }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { @@ -1799,7 +1803,10 @@ namespace ts { return prior; } + buildPerformance.mark("beforeUpToDateCheckTime"); const actual = getUpToDateStatusWorker(state, project, resolvedPath); + buildPerformance.mark("afterUpToDateCheckTime"); + buildPerformance.measure("Up-to-date check time", "beforeUpToDateCheckTime", "afterUpToDateCheckTime"); state.projectStatus.set(resolvedPath, actual); return actual; } @@ -1917,7 +1924,7 @@ namespace ts { } break; } - // falls through + // falls through case UpToDateStatusType.UpToDateWithInputFileText: case UpToDateStatusType.UpToDateWithUpstreamTypes: @@ -1945,6 +1952,14 @@ namespace ts { } function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers, onlyReferences?: boolean): ExitStatus { + buildPerformance.mark("beforeBuild"); + const result = buildWorker(state, project, cancellationToken, writeFile, getCustomTransformers, onlyReferences); + buildPerformance.mark("afterBuild"); + buildPerformance.measure("Build", "beforeBuild", "afterBuild"); + return result; + } + + function buildWorker(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers, onlyReferences?: boolean): ExitStatus { const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; @@ -1973,7 +1988,15 @@ namespace ts { : ExitStatus.DiagnosticsPresent_OutputsSkipped; } - function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { + function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean): ExitStatus { + buildPerformance.mark("beforeClean"); + const result = cleanWorker(state, project, onlyReferences); + buildPerformance.mark("afterClean"); + buildPerformance.measure("Clean", "beforeClean", "afterClean"); + return result; + } + + function cleanWorker(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; @@ -2050,6 +2073,14 @@ namespace ts { } function buildNextInvalidatedProject(state: SolutionBuilderState, changeDetected: boolean) { + buildPerformance.mark("beforeBuild"); + const buildOrder = buildNextInvalidatedProjectWorker(state, changeDetected); + buildPerformance.mark("afterBuild"); + buildPerformance.measure("Build", "beforeBuild", "afterBuild"); + if (buildOrder) reportErrorSummary(state, buildOrder); + } + + function buildNextInvalidatedProjectWorker(state: SolutionBuilderState, changeDetected: boolean) { state.timerToBuildInvalidatedProject = undefined; if (state.reportFileChangeDetected) { state.reportFileChangeDetected = false; @@ -2079,7 +2110,7 @@ namespace ts { } } disableCache(state); - reportErrorSummary(state, buildOrder); + return buildOrder; } function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { @@ -2186,6 +2217,7 @@ namespace ts { function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { if (!state.watchAllProjectsPending) return; + buildPerformance.mark("beforeWatcherCreation"); state.watchAllProjectsPending = false; for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) { const resolvedPath = toResolvedConfigFilePath(state, resolved); @@ -2204,6 +2236,8 @@ namespace ts { watchPackageJsonFiles(state, resolved, resolvedPath, cfg); } } + buildPerformance.mark("afterWatcherCreation"); + buildPerformance.measure("Watcher creation", "beforeWatcherCreation", "afterWatcherCreation"); } function stopWatching(state: SolutionBuilderState) { diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 68d2e51e9d4eb..1034ebe0bfa98 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -1,9 +1,4 @@ namespace ts { - interface Statistic { - name: string; - value: string; - } - function countLines(program: Program): Map { const counts = getCountsMap(); forEach(program.getSourceFiles(), file => { @@ -14,15 +9,6 @@ namespace ts { return counts; } - function countNodes(program: Program): Map { - const counts = getCountsMap(); - forEach(program.getSourceFiles(), file => { - const key = getCountKey(program, file); - counts.set(key, counts.get(key)! + file.nodeCount); - }); - return counts; - } - function getCountsMap() { const counts = new Map(); counts.set("Library", 0); @@ -751,9 +737,22 @@ namespace ts { createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)), createWatchStatusReporter(sys, buildOptions) ); + const onWatchStatusChange = buildHost.onWatchStatusChange; + let reportWatchStatistics = false; + buildHost.onWatchStatusChange = (d, newLine, options, errorCount) => { + onWatchStatusChange?.(d, newLine, options, errorCount); + if (!reportWatchStatistics) return; + if (d.code === Diagnostics.Found_0_errors_Watching_for_file_changes.code || + d.code === Diagnostics.Found_1_error_Watching_for_file_changes.code) { + reportSolutionBuilderTimes(sys, buildOptions, builder); + } + }; updateSolutionBuilderHost(sys, cb, buildHost); + enableSolutionPerformance(sys, buildOptions); const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions, watchOptions); builder.build(); + reportSolutionBuilderTimes(sys, buildOptions, builder); + reportWatchStatistics = true; return builder; } @@ -765,12 +764,38 @@ namespace ts { createReportErrorSummary(sys, buildOptions) ); updateSolutionBuilderHost(sys, cb, buildHost); + enableSolutionPerformance(sys, buildOptions); const builder = createSolutionBuilder(buildHost, projects, buildOptions); const exitStatus = buildOptions.clean ? builder.clean() : builder.build(); + reportSolutionBuilderTimes(sys, buildOptions, builder); dumpTracingLegend(); // Will no-op if there hasn't been any tracing return sys.exit(exitStatus); } + function enableSolutionPerformance(system: System, options: BuildOptions) { + if (system === sys && options.extendedDiagnostics) buildPerformance.enable(); + } + + function reportSolutionBuilderTimes(system: System, buildOptions: BuildOptions, builder: SolutionBuilder) { + if (system !== sys || !buildOptions.extendedDiagnostics) return; + + if (!buildPerformance.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"); + return; + } + + const statistics: Statistic[] = []; + statistics.push( + { name: "Projects in scope", value: getBuildOrderFromAnyBuildOrder(builder.getBuildOrder()).length, type: StatisticType.count }, + ); + buildPerformance.forEachCount((count, name) => statistics.push({ name, value: count, type: StatisticType.count })); + buildPerformance.forEachStatistics(s => statistics.push(s)); + buildPerformance.forEachMeasure((duration, name) => statistics.push({ name: `${name} time`, value: duration, type: StatisticType.time })); + buildPerformance.disable(); + buildPerformance.enable(); + reportAllStatistics(system, statistics); + } + function createReportErrorSummary(sys: System, options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined { return shouldBePretty(sys, options) ? (errorCount, filesInError) => sys.write(getErrorSummaryText(errorCount, filesInError, sys.newLine, sys)) : @@ -840,7 +865,7 @@ namespace ts { cb: ExecuteCommandLineCallbacks, buildHost: SolutionBuilderHostBase ) { - updateCreateProgram(sys, buildHost); + updateCreateProgram(sys, buildHost, /*isBuildMode*/ true); buildHost.afterProgramEmitAndDiagnostics = program => { reportStatistics(sys, program.getProgram()); cb(program); @@ -848,12 +873,12 @@ namespace ts { buildHost.afterEmitBundle = cb; } - function updateCreateProgram(sys: System, host: { createProgram: CreateProgram; }) { + function updateCreateProgram(sys: System, host: { createProgram: CreateProgram; }, isBuildMode: boolean) { const compileUsingBuilder = host.createProgram; host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => { Debug.assert(rootNames !== undefined || (options === undefined && !!oldProgram)); if (options !== undefined) { - enableStatisticsAndTracing(sys, options, /*isBuildMode*/ true); + enableStatisticsAndTracing(sys, options, isBuildMode); } return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); }; @@ -864,7 +889,7 @@ namespace ts { cb: ExecuteCommandLineCallbacks, watchCompilerHost: WatchCompilerHost, ) { - updateCreateProgram(sys, watchCompilerHost); + updateCreateProgram(sys, watchCompilerHost, /*isBuildMode*/ false); const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217 watchCompilerHost.afterProgramCreate = builderProgram => { emitFilesUsingBuilder(builderProgram); @@ -953,18 +978,13 @@ namespace ts { reportCountStatistic("Files", program.getSourceFiles().length); const lineCounts = countLines(program); - const nodeCounts = countNodes(program); if (compilerOptions.extendedDiagnostics) { for (const key of arrayFrom(lineCounts.keys())) { reportCountStatistic("Lines of " + key, lineCounts.get(key)!); } - for (const key of arrayFrom(nodeCounts.keys())) { - reportCountStatistic("Nodes of " + key, nodeCounts.get(key)!); - } } else { reportCountStatistic("Lines", reduceLeftIterator(lineCounts.values(), (sum, count) => sum + count, 0)); - reportCountStatistic("Nodes", reduceLeftIterator(nodeCounts.values(), (sum, count) => sum + count, 0)); } reportCountStatistic("Identifiers", program.getIdentifierCount()); @@ -973,7 +993,7 @@ namespace ts { reportCountStatistic("Instantiations", program.getInstantiationCount()); if (memoryUsed >= 0) { - reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); + reportMemoryStatistic("Memory used", memoryUsed); } const isPerformanceEnabled = performance.isEnabled(); @@ -988,7 +1008,7 @@ namespace ts { reportCountStatistic("Subtype cache size", caches.subtype); reportCountStatistic("Strict subtype cache size", caches.strictSubtype); if (isPerformanceEnabled) { - performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); + performance.forEachMeasure((duration, name) => reportTimeStatistic(`${name} time`, duration, /*aggregate*/ true)); } } else if (isPerformanceEnabled) { @@ -996,17 +1016,17 @@ namespace ts { // 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 // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. - reportTimeStatistic("I/O read", performance.getDuration("I/O Read")); - reportTimeStatistic("I/O write", performance.getDuration("I/O Write")); - reportTimeStatistic("Parse time", programTime); - reportTimeStatistic("Bind time", bindTime); - reportTimeStatistic("Check time", checkTime); - reportTimeStatistic("Emit time", emitTime); + reportTimeStatistic("I/O read", performance.getDuration("I/O Read"), /*aggregate*/ true); + reportTimeStatistic("I/O write", performance.getDuration("I/O Write"), /*aggregate*/ true); + reportTimeStatistic("Parse time", programTime, /*aggregate*/ true); + reportTimeStatistic("Bind time", bindTime, /*aggregate*/ true); + reportTimeStatistic("Check time", checkTime, /*aggregate*/ true); + reportTimeStatistic("Emit time", emitTime, /*aggregate*/ true); } if (isPerformanceEnabled) { - reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime, /*aggregate*/ false); } - reportStatistics(); + reportAllStatistics(sys, statistics); 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"); } @@ -1015,34 +1035,53 @@ namespace ts { } } - function reportStatistics() { - let nameSize = 0; - let valueSize = 0; - for (const { name, value } of statistics) { - if (name.length > nameSize) { - nameSize = name.length; - } + function reportStatisticalValue(s: Statistic, aggregate: boolean) { + statistics.push(s); + if (aggregate) buildPerformance.addStatistics({ ...s, name: `Aggregate ${s.name}` }); + } - if (value.length > valueSize) { - valueSize = value.length; - } - } + function reportMemoryStatistic(name: string, memoryUsed: number) { + reportStatisticalValue({ name, value: memoryUsed, type: StatisticType.memory }, /*aggregate*/ true); + } - for (const { name, value } of statistics) { - sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); - } + function reportCountStatistic(name: string, count: number) { + reportStatisticalValue({ name, value: count, type: StatisticType.count }, /*aggregate*/ true); } - function reportStatisticalValue(name: string, value: string) { - statistics.push({ name, value }); + function reportTimeStatistic(name: string, time: number, aggregate: boolean) { + reportStatisticalValue({ name, value: time, type: StatisticType.time }, aggregate); } + } - function reportCountStatistic(name: string, count: number) { - reportStatisticalValue(name, "" + count); + function reportAllStatistics(sys: System, statistics: Statistic[]) { + let nameSize = 0; + let valueSize = 0; + for (const s of statistics) { + if (s.name.length > nameSize) { + nameSize = s.name.length; + } + + const valueString = statisticValue(s); + if (valueString.length > valueSize) { + valueSize = valueString.length; + } + } + + for (const s of statistics) { + sys.write(padRight(s.name + ":", nameSize + 2) + padLeft(statisticValue(s).toString(), valueSize) + sys.newLine); } + } - function reportTimeStatistic(name: string, time: number) { - reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); + function statisticValue(s: Statistic) { + switch (s.type) { + case StatisticType.count: + return "" + s.value; + case StatisticType.time: + return (s.value / 1000).toFixed(2) + "s"; + case StatisticType.memory: + return Math.round(s.value / 1000) + "K"; + default: + Debug.assertNever(s.type); } }