diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f0c58c66bef..1619fc0fc8c 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3,8 +3,6 @@ /* @internal */ namespace ts { - export let bindTime = 0; - export const enum ModuleInstanceState { NonInstantiated = 0, Instantiated = 1, @@ -96,9 +94,10 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - const start = new Date().getTime(); + Performance.mark("bindStart"); binder(file, options); - bindTime += new Date().getTime() - start; + Performance.mark("bindEnd"); + Performance.measure("bindTime", "bindStart", "bindEnd"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 344dd35a5c3..725ed2c1bad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14,8 +14,6 @@ namespace ts { return node.id; } - export let checkTime = 0; - export function getSymbolId(symbol: Symbol): number { if (!symbol.id) { symbol.id = nextSymbolId; @@ -16066,11 +16064,12 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - const start = new Date().getTime(); + Performance.mark("checkStart"); checkSourceFileWorker(node); - checkTime += new Date().getTime() - start; + Performance.mark("checkEnd"); + Performance.measure("checkTime", "checkStart", "checkEnd"); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/core.ts b/src/compiler/core.ts index bae17c642d2..0a070844903 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1134,4 +1134,139 @@ namespace ts { : ((fileName) => fileName.toLowerCase()); } + /*@internal*/ + export namespace Performance { + interface MarkData { + markName: string; + timestamp: number; + } + + interface MeasureData { + measureName: string; + startMarkName: string; + endMarkName: string; + timestamp: number; + marksOffset: number; + } + + export interface Measure { + name: string; + startTime: number; + duration: number; + } + + const marks: MarkData[] = []; + const measures: MeasureData[] = []; + + let start = now(); + + /** Gets the current timer for performance measurements. */ + export function now() { + // TODO(rbuckton): Determine if there is a higher-resolution timer we can use. + return Date.now(); + } + + /** + * Adds a performance mark with the specified name. + * + * @param markName The name of the performance mark. + */ + export function mark(markName: string) { + marks.push({ markName, timestamp: now() }); + } + + /** + * 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 provided, the most recent time value of the start mark is used. + * If not specified, the value is the time that the performance service was + * initialized or the last time it was reset. + * @param endMarkName The name of the ending mark. + * If provided, the most recent time value of the end mark is used. + * If not specified, the current time is used. + */ + export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + measures.push({ + measureName, + startMarkName, + endMarkName, + timestamp: now(), + marksOffset: marks.length + }); + } + + /** + * Gets an array of performance measures. + * + * @param measureName The name of the measure. + * If provided, only measures with the provided name are returned. + * If not specified, all measures are returned since the last time the + * performance service was reset. + */ + export function getMeasures(measureName?: string) { + const result: Measure[] = []; + for (const measure of measures) { + if (measureName !== undefined && measureName !== measure.measureName) { + continue; + } + + let startOffset = 0; + let startTime = start; + if (measure.startMarkName) { + const startMarkIndex = getMarkOffset(measure.startMarkName, 0, measure.marksOffset); + if (startMarkIndex >= 0) { + startOffset = startMarkIndex; + startTime = marks[startMarkIndex].timestamp; + } + } + + let endTime = measure.timestamp; + if (measure.endMarkName) { + const endMarkIndex = getMarkOffset(measure.endMarkName, startOffset, measure.marksOffset); + if (endMarkIndex >= 0) { + endTime = marks[endMarkIndex].timestamp; + } + } + + const duration = endTime - startTime; + result.push({ + name: measure.measureName, + startTime, + duration + }); + } + + return result; + } + + function getMarkOffset(markName: string, markStart: number, markEnd: number) { + if (markName === undefined) { + return -1; + } + + if (markStart < 0) { + markStart = 0; + } + + for (let i = markEnd - 1; i >= markStart; i--) { + const mark = marks[i]; + if (mark.markName === markName) { + return i; + } + } + + return -1; + } + + /** + * Resets all marks and measurements in the performance service. + */ + export function reset() { + marks.length = 0; + measures.length = 0; + start = now(); + } + } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bf5b7cb5727..1e7d4118e6c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -4,11 +4,6 @@ /// namespace ts { - /* @internal */ export let programTime = 0; - /* @internal */ export let emitTime = 0; - /* @internal */ export let ioReadTime = 0; - /* @internal */ export let ioWriteTime = 0; - /** The version of the TypeScript compiler release */ const emptyArray: any[] = []; @@ -781,9 +776,10 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - const start = new Date().getTime(); + Performance.mark("ioReadStart"); text = sys.readFile(fileName, options.charset); - ioReadTime += new Date().getTime() - start; + Performance.mark("ioReadEnd"); + Performance.measure("ioReadTime", "ioReadStart", "ioReadEnd"); } catch (e) { if (onError) { @@ -850,7 +846,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - const start = new Date().getTime(); + Performance.mark("ioWriteStart"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -860,7 +856,8 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - ioWriteTime += new Date().getTime() - start; + Performance.mark("ioWriteEnd"); + Performance.measure("ioWriteTime", "ioWriteStart", "ioWriteEnd"); } catch (e) { if (onError) { @@ -962,7 +959,7 @@ namespace ts { let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); - const start = new Date().getTime(); + Performance.mark("programStart"); host = host || createCompilerHost(options); @@ -1055,7 +1052,8 @@ namespace ts { verifyCompilerOptions(); - programTime += new Date().getTime() - start; + Performance.mark("programEnd"); + Performance.measure("programTime", "programStart", "programEnd"); return program; @@ -1288,7 +1286,7 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - const start = new Date().getTime(); + Performance.mark("emitStart"); // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. let useLegacyEmitter = options.useLegacyEmitter; @@ -1302,7 +1300,9 @@ namespace ts { getEmitHost(writeFileCallback), sourceFile); - emitTime += new Date().getTime() - start; + Performance.mark("emitEnd"); + Performance.measure("emitTime", "emitStart", "emitEnd"); + return emitResult; } @@ -2076,7 +2076,7 @@ namespace ts { } // Cannot specify module gen that isn't amd or system with --out - // Report this error if user specified --module moduleKind + // Report this error if user specified --module moduleKind // or if there is external module in compilation which defaults to commonjs const emitModuleKind = getEmitModuleKind(options); if (outFile && (options.module || firstExternalModuleSourceFile) && !(emitModuleKind === ModuleKind.AMD || emitModuleKind === ModuleKind.System)) { diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 7c9fae6b632..2325f75a665 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -544,12 +544,7 @@ namespace ts { } function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { - ioReadTime = 0; - ioWriteTime = 0; - programTime = 0; - bindTime = 0; - checkTime = 0; - emitTime = 0; + Performance.reset(); const program = createProgram(fileNames, compilerOptions, compilerHost); const exitStatus = compileProgram(); @@ -561,6 +556,13 @@ namespace ts { } if (compilerOptions.diagnostics) { + const ioReadTime = reduceLeft(Performance.getMeasures("ioReadTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const ioWriteTime = reduceLeft(Performance.getMeasures("ioWriteTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const programTime = reduceLeft(Performance.getMeasures("programTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const bindTime = reduceLeft(Performance.getMeasures("bindTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const checkTime = reduceLeft(Performance.getMeasures("checkTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const emitTime = reduceLeft(Performance.getMeasures("emitTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program));