perf(compiler-cli): refactor the performance tracing infrastructure (#41125)

ngtsc has an internal performance tracing package, which previously has not
really seen much use. It used to track performance statistics on a very
granular basis (microseconds per actual class analysis, for example). This
had two problems:

* it produced voluminous amounts of data, complicating the analysis of such
  results and providing dubious value.
* it added nontrivial overhead to compilation when used (which also affected
  the very performance of the operations being measured).

This commit replaces the old system with a streamlined performance tracing
setup which is lightweight and designed to be always-on. The new system
tracks 3 metrics:

* time taken by various phases and operations within the compiler
* events (counters) which measure the shape and size of the compilation
* memory usage measured at various points of the compilation process

If the compiler option `tracePerformance` is set, the compiler will
serialize these metrics to a JSON file at that location after compilation is
complete.

PR Close #41125
This commit is contained in:
Alex Rickabaugh 2021-03-15 15:23:03 -07:00
parent dd82c8e9f4
commit 48fec08c95
44 changed files with 1201 additions and 651 deletions

View File

@ -16,6 +16,7 @@ ts_library(
], ],
deps = [ deps = [
"//packages/compiler-cli", "//packages/compiler-cli",
"//packages/compiler-cli/src/ngtsc/perf",
"@npm//@bazel/typescript", "@npm//@bazel/typescript",
"@npm//@types/node", "@npm//@types/node",
"@npm//tsickle", "@npm//tsickle",

View File

@ -7,6 +7,7 @@
*/ */
import * as ng from '@angular/compiler-cli'; import * as ng from '@angular/compiler-cli';
import {PerfPhase} from '@angular/compiler-cli/src/ngtsc/perf';
import {BazelOptions, CachedFileLoader, CompilerHost, constructManifest, debug, FileCache, FileLoader, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop, UncachedFileLoader} from '@bazel/typescript'; import {BazelOptions, CachedFileLoader, CompilerHost, constructManifest, debug, FileCache, FileLoader, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop, UncachedFileLoader} from '@bazel/typescript';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
@ -515,6 +516,12 @@ function gatherDiagnosticsForInputsOnly(
options: ng.CompilerOptions, bazelOpts: BazelOptions, options: ng.CompilerOptions, bazelOpts: BazelOptions,
ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] { ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] {
const tsProgram = ngProgram.getTsProgram(); const tsProgram = ngProgram.getTsProgram();
// For the Ivy compiler, track the amount of time spent fetching TypeScript diagnostics.
let previousPhase = PerfPhase.Unaccounted;
if (ngProgram instanceof ng.NgtscProgram) {
previousPhase = ngProgram.compiler.perfRecorder.phase(PerfPhase.TypeScriptDiagnostics);
}
const diagnostics: (ng.Diagnostic|ts.Diagnostic)[] = []; const diagnostics: (ng.Diagnostic|ts.Diagnostic)[] = [];
// These checks mirror ts.getPreEmitDiagnostics, with the important // These checks mirror ts.getPreEmitDiagnostics, with the important
// exception of avoiding b/30708240, which is that if you call // exception of avoiding b/30708240, which is that if you call
@ -529,6 +536,11 @@ function gatherDiagnosticsForInputsOnly(
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf)); diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
diagnostics.push(...tsProgram.getSemanticDiagnostics(sf)); diagnostics.push(...tsProgram.getSemanticDiagnostics(sf));
} }
if (ngProgram instanceof ng.NgtscProgram) {
ngProgram.compiler.perfRecorder.phase(previousPhase);
}
if (!diagnostics.length) { if (!diagnostics.length) {
// only gather the angular diagnostics if we have no diagnostics // only gather the angular diagnostics if we have no diagnostics
// in any other files. // in any other files.

View File

@ -22,5 +22,6 @@ export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'
export {ngToTsDiagnostic} from './src/transformers/util'; export {ngToTsDiagnostic} from './src/transformers/util';
export {NgTscPlugin} from './src/ngtsc/tsc_plugin'; export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
export {NgtscProgram} from './src/ngtsc/program';
setFileSystem(new NodeJSFileSystem()); setFileSystem(new NodeJSFileSystem());

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ConstantPool} from '@angular/compiler'; import {ConstantPool} from '@angular/compiler';
import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ParsedConfiguration} from '../../..'; import {ParsedConfiguration} from '../../..';
@ -89,7 +90,7 @@ export class DecorationAnalyzer {
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]); fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
evaluator = evaluator =
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null); new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
importGraph = new ImportGraph(this.typeChecker); importGraph = new ImportGraph(this.typeChecker, NOOP_PERF_RECORDER);
cycleAnalyzer = new CycleAnalyzer(this.importGraph); cycleAnalyzer = new CycleAnalyzer(this.importGraph);
injectableRegistry = new InjectableClassRegistry(this.reflectionHost); injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader); typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader);
@ -104,7 +105,8 @@ export class DecorationAnalyzer {
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer, /* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER, CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
NOOP_DEPENDENCY_TRACKER, this.injectableRegistry, NOOP_DEPENDENCY_TRACKER, this.injectableRegistry,
/* semanticDepGraphUpdater */ null, !!this.compilerOptions.annotateForClosureCompiler), /* semanticDepGraphUpdater */ null, !!this.compilerOptions.annotateForClosureCompiler,
NOOP_PERF_RECORDER),
// See the note in ngtsc about why this cast is needed. // See the note in ngtsc about why this cast is needed.
// clang-format off // clang-format off
@ -117,23 +119,26 @@ export class DecorationAnalyzer {
// version 10, undecorated classes that use Angular features are no longer handled // version 10, undecorated classes that use Angular features are no longer handled
// in ngtsc, but we want to ensure compatibility in ngcc for outdated libraries that // in ngtsc, but we want to ensure compatibility in ngcc for outdated libraries that
// have not migrated to explicit decorators. See: https://hackmd.io/@alx/ryfYYuvzH. // have not migrated to explicit decorators. See: https://hackmd.io/@alx/ryfYYuvzH.
/* compileUndecoratedClassesWithAngularFeatures */ true /* compileUndecoratedClassesWithAngularFeatures */ true,
NOOP_PERF_RECORDER
) as DecoratorHandler<unknown, unknown, SemanticSymbol|null,unknown>, ) as DecoratorHandler<unknown, unknown, SemanticSymbol|null,unknown>,
// clang-format on // clang-format on
// Pipe handler must be before injectable handler in list so pipe factories are printed // Pipe handler must be before injectable handler in list so pipe factories are printed
// before injectable factories (so injectable factories can delegate to them) // before injectable factories (so injectable factories can delegate to them)
new PipeDecoratorHandler( new PipeDecoratorHandler(
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry, this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore), NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore, NOOP_PERF_RECORDER),
new InjectableDecoratorHandler( new InjectableDecoratorHandler(
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore, this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
/* strictCtorDeps */ false, this.injectableRegistry, /* errorOnDuplicateProv */ false), /* strictCtorDeps */ false, this.injectableRegistry, NOOP_PERF_RECORDER,
/* errorOnDuplicateProv */ false),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry, this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null, this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
this.refEmitter, this.refEmitter,
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER, /* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
!!this.compilerOptions.annotateForClosureCompiler, this.injectableRegistry), !!this.compilerOptions.annotateForClosureCompiler, this.injectableRegistry,
NOOP_PERF_RECORDER),
]; ];
compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost); compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost);
migrations: Migration[] = [ migrations: Migration[] = [

View File

@ -18,6 +18,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/indexer", "//packages/compiler-cli/src/ngtsc/indexer",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/routing", "//packages/compiler-cli/src/ngtsc/routing",
"//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/scope",

View File

@ -18,6 +18,7 @@ import {extractSemanticTypeParameters, isArrayEqual, isReferenceEqual, SemanticD
import {IndexingContext} from '../../indexer'; import {IndexingContext} from '../../indexer';
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata'; import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator'; import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope'; import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
@ -208,7 +209,7 @@ export class ComponentDecoratorHandler implements
private depTracker: DependencyTracker|null, private depTracker: DependencyTracker|null,
private injectableRegistry: InjectableClassRegistry, private injectableRegistry: InjectableClassRegistry,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null, private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean) {} private annotateForClosureCompiler: boolean, private perf: PerfRecorder) {}
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>(); private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
private elementSchemaRegistry = new DomElementSchemaRegistry(); private elementSchemaRegistry = new DomElementSchemaRegistry();
@ -309,6 +310,7 @@ export class ComponentDecoratorHandler implements
analyze( analyze(
node: ClassDeclaration, decorator: Readonly<Decorator>, node: ClassDeclaration, decorator: Readonly<Decorator>,
flags: HandlerFlags = HandlerFlags.NONE): AnalysisOutput<ComponentAnalysisData> { flags: HandlerFlags = HandlerFlags.NONE): AnalysisOutput<ComponentAnalysisData> {
this.perf.eventCount(PerfEvent.AnalyzeComponent);
const containingFile = node.getSourceFile().fileName; const containingFile = node.getSourceFile().fileName;
this.literalCache.delete(decorator); this.literalCache.delete(decorator);

View File

@ -16,6 +16,7 @@ import {areTypeParametersEqual, extractSemanticTypeParameters, isArrayEqual, isS
import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateGuardMeta} from '../../metadata'; import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateGuardMeta} from '../../metadata';
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util'; import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator'; import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope'; import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
@ -180,7 +181,7 @@ export class DirectiveDecoratorHandler implements
private injectableRegistry: InjectableClassRegistry, private isCore: boolean, private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null, private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean, private annotateForClosureCompiler: boolean,
private compileUndecoratedClassesWithAngularFeatures: boolean) {} private compileUndecoratedClassesWithAngularFeatures: boolean, private perf: PerfRecorder) {}
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = DirectiveDecoratorHandler.name; readonly name = DirectiveDecoratorHandler.name;
@ -211,6 +212,8 @@ export class DirectiveDecoratorHandler implements
return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]}; return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]};
} }
this.perf.eventCount(PerfEvent.AnalyzeDirective);
const directiveResult = extractDirectiveMetadata( const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore, node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
flags, this.annotateForClosureCompiler); flags, this.annotateForClosureCompiler);

View File

@ -12,6 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder} from '../../imports'; import {DefaultImportRecorder} from '../../imports';
import {InjectableClassRegistry} from '../../metadata'; import {InjectableClassRegistry} from '../../metadata';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
@ -34,7 +35,7 @@ export class InjectableDecoratorHandler implements
constructor( constructor(
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder, private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
private isCore: boolean, private strictCtorDeps: boolean, private isCore: boolean, private strictCtorDeps: boolean,
private injectableRegistry: InjectableClassRegistry, private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
/** /**
* What to do if the injectable already contains a ɵprov property. * What to do if the injectable already contains a ɵprov property.
* *
@ -64,6 +65,8 @@ export class InjectableDecoratorHandler implements
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>): analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
AnalysisOutput<InjectableHandlerData> { AnalysisOutput<InjectableHandlerData> {
this.perf.eventCount(PerfEvent.AnalyzeInjectable);
const meta = extractInjectableMetadata(node, decorator, this.reflector); const meta = extractInjectableMetadata(node, decorator, this.reflector);
const decorators = this.reflector.getDecoratorsOfDeclaration(node); const decorators = this.reflector.getDecoratorsOfDeclaration(node);

View File

@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports'
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph'; import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph';
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata'; import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator'; import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection'; import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing'; import {NgModuleRouteAnalyzer} from '../../routing';
import {LocalModuleScopeRegistry, ScopeData} from '../../scope'; import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
@ -131,7 +132,8 @@ export class NgModuleDecoratorHandler implements
private factoryTracker: FactoryTracker|null, private factoryTracker: FactoryTracker|null,
private defaultImportRecorder: DefaultImportRecorder, private defaultImportRecorder: DefaultImportRecorder,
private annotateForClosureCompiler: boolean, private annotateForClosureCompiler: boolean,
private injectableRegistry: InjectableClassRegistry, private localeId?: string) {} private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
private localeId?: string) {}
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = NgModuleDecoratorHandler.name; readonly name = NgModuleDecoratorHandler.name;
@ -154,6 +156,8 @@ export class NgModuleDecoratorHandler implements
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>): analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
AnalysisOutput<NgModuleAnalysis> { AnalysisOutput<NgModuleAnalysis> {
this.perf.eventCount(PerfEvent.AnalyzeNgModule);
const name = node.name.text; const name = node.name.text;
if (decorator.args === null || decorator.args.length > 1) { if (decorator.args === null || decorator.args.length > 1) {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(

View File

@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference} from '../../imports';
import {SemanticSymbol} from '../../incremental/semantic_graph'; import {SemanticSymbol} from '../../incremental/semantic_graph';
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata'; import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope'; import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
@ -55,7 +56,8 @@ export class PipeDecoratorHandler implements
private reflector: ReflectionHost, private evaluator: PartialEvaluator, private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry, private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private defaultImportRecorder: DefaultImportRecorder, private defaultImportRecorder: DefaultImportRecorder,
private injectableRegistry: InjectableClassRegistry, private isCore: boolean) {} private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
private perf: PerfRecorder) {}
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = PipeDecoratorHandler.name; readonly name = PipeDecoratorHandler.name;
@ -78,6 +80,8 @@ export class PipeDecoratorHandler implements
analyze(clazz: ClassDeclaration, decorator: Readonly<Decorator>): analyze(clazz: ClassDeclaration, decorator: Readonly<Decorator>):
AnalysisOutput<PipeHandlerData> { AnalysisOutput<PipeHandlerData> {
this.perf.eventCount(PerfEvent.AnalyzePipe);
const name = clazz.name.text; const name = clazz.name.text;
const type = wrapTypeReference(this.reflector, clazz); const type = wrapTypeReference(this.reflector, clazz);
const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(clazz)); const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(clazz));

View File

@ -20,6 +20,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/incremental:api", "//packages/compiler-cli/src/ngtsc/incremental:api",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/testing", "//packages/compiler-cli/src/ngtsc/testing",

View File

@ -16,6 +16,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports'; import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata'; import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
@ -41,7 +42,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null); const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null);
const moduleResolver = const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null); new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
const importGraph = new ImportGraph(checker); const importGraph = new ImportGraph(checker, NOOP_PERF_RECORDER);
const cycleAnalyzer = new CycleAnalyzer(importGraph); const cycleAnalyzer = new CycleAnalyzer(importGraph);
const metaRegistry = new LocalMetadataRegistry(); const metaRegistry = new LocalMetadataRegistry();
const dtsReader = new DtsMetadataReader(checker, reflectionHost); const dtsReader = new DtsMetadataReader(checker, reflectionHost);
@ -80,6 +81,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
injectableRegistry, injectableRegistry,
/* semanticDepGraphUpdater */ null, /* semanticDepGraphUpdater */ null,
/* annotateForClosureCompiler */ false, /* annotateForClosureCompiler */ false,
NOOP_PERF_RECORDER,
); );
return {reflectionHost, handler}; return {reflectionHost, handler};
} }

View File

@ -13,6 +13,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports'; import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata'; import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
@ -171,7 +172,7 @@ runInEachFileSystem(() => {
NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false, NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false,
/*semanticDepGraphUpdater*/ null, /*semanticDepGraphUpdater*/ null,
/*annotateForClosureCompiler*/ false, /*annotateForClosureCompiler*/ false,
/*detectUndecoratedClassesWithAngularFeatures*/ false); /*detectUndecoratedClassesWithAngularFeatures*/ false, NOOP_PERF_RECORDER);
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration); const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);

View File

@ -10,6 +10,7 @@ import {absoluteFrom} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing'; import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports'; import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
import {InjectableClassRegistry} from '../../metadata'; import {InjectableClassRegistry} from '../../metadata';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
import {InjectableDecoratorHandler} from '../src/injectable'; import {InjectableDecoratorHandler} from '../src/injectable';
@ -70,7 +71,7 @@ function setupHandler(errorOnDuplicateProv: boolean) {
const injectableRegistry = new InjectableClassRegistry(reflectionHost); const injectableRegistry = new InjectableClassRegistry(reflectionHost);
const handler = new InjectableDecoratorHandler( const handler = new InjectableDecoratorHandler(
reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false, reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false,
/* strictCtorDeps */ false, injectableRegistry, errorOnDuplicateProv); /* strictCtorDeps */ false, injectableRegistry, NOOP_PERF_RECORDER, errorOnDuplicateProv);
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration); const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov'); const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
if (ɵprov === undefined) { if (ɵprov === undefined) {

View File

@ -14,6 +14,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports'; import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata'; import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
@ -71,7 +72,8 @@ runInEachFileSystem(() => {
const handler = new NgModuleDecoratorHandler( const handler = new NgModuleDecoratorHandler(
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
/* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null, /* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null,
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false, injectableRegistry); NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false, injectableRegistry,
NOOP_PERF_RECORDER);
const TestModule = const TestModule =
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration); getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
const detected = const detected =

View File

@ -28,9 +28,8 @@ export interface TestOnlyOptions {
/** /**
* An option to enable ngtsc's internal performance tracing. * An option to enable ngtsc's internal performance tracing.
* *
* This should be a path to a JSON file where trace information will be written. An optional 'ts:' * This should be a path to a JSON file where trace information will be written. This is sensitive
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem * to the compiler's working directory, and should likely be an absolute path.
* (not all hosts support this mode of operation).
* *
* This is currently not exposed to users as the trace format is still unstable. * This is currently not exposed to users as the trace format is still unstable.
*/ */

View File

@ -21,7 +21,9 @@ import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata'; import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata';
import {ModuleWithProvidersScanner} from '../../modulewithproviders'; import {ModuleWithProvidersScanner} from '../../modulewithproviders';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf'; import {ActivePerfRecorder} from '../../perf';
import {PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf/src/api';
import {DelegatingPerfRecorder} from '../../perf/src/recorder';
import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {AdapterResourceLoader} from '../../resource'; import {AdapterResourceLoader} from '../../resource';
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing'; import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
@ -80,6 +82,7 @@ export interface FreshCompilationTicket {
enableTemplateTypeChecker: boolean; enableTemplateTypeChecker: boolean;
usePoisonedData: boolean; usePoisonedData: boolean;
tsProgram: ts.Program; tsProgram: ts.Program;
perfRecorder: ActivePerfRecorder;
} }
/** /**
@ -95,12 +98,14 @@ export interface IncrementalTypeScriptCompilationTicket {
newDriver: IncrementalDriver; newDriver: IncrementalDriver;
enableTemplateTypeChecker: boolean; enableTemplateTypeChecker: boolean;
usePoisonedData: boolean; usePoisonedData: boolean;
perfRecorder: ActivePerfRecorder;
} }
export interface IncrementalResourceCompilationTicket { export interface IncrementalResourceCompilationTicket {
kind: CompilationTicketKind.IncrementalResource; kind: CompilationTicketKind.IncrementalResource;
compiler: NgCompiler; compiler: NgCompiler;
modifiedResourceFiles: Set<string>; modifiedResourceFiles: Set<string>;
perfRecorder: ActivePerfRecorder;
} }
/** /**
@ -119,8 +124,8 @@ export type CompilationTicket = FreshCompilationTicket|IncrementalTypeScriptComp
export function freshCompilationTicket( export function freshCompilationTicket(
tsProgram: ts.Program, options: NgCompilerOptions, tsProgram: ts.Program, options: NgCompilerOptions,
incrementalBuildStrategy: IncrementalBuildStrategy, incrementalBuildStrategy: IncrementalBuildStrategy,
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, enableTemplateTypeChecker: boolean, typeCheckingProgramStrategy: TypeCheckingProgramStrategy, perfRecorder: ActivePerfRecorder|null,
usePoisonedData: boolean): CompilationTicket { enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket {
return { return {
kind: CompilationTicketKind.Fresh, kind: CompilationTicketKind.Fresh,
tsProgram, tsProgram,
@ -129,6 +134,7 @@ export function freshCompilationTicket(
typeCheckingProgramStrategy, typeCheckingProgramStrategy,
enableTemplateTypeChecker, enableTemplateTypeChecker,
usePoisonedData, usePoisonedData,
perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow(),
}; };
} }
@ -139,8 +145,8 @@ export function freshCompilationTicket(
export function incrementalFromCompilerTicket( export function incrementalFromCompilerTicket(
oldCompiler: NgCompiler, newProgram: ts.Program, oldCompiler: NgCompiler, newProgram: ts.Program,
incrementalBuildStrategy: IncrementalBuildStrategy, incrementalBuildStrategy: IncrementalBuildStrategy,
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
modifiedResourceFiles: Set<string>): CompilationTicket { perfRecorder: ActivePerfRecorder|null): CompilationTicket {
const oldProgram = oldCompiler.getNextProgram(); const oldProgram = oldCompiler.getNextProgram();
const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram); const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram);
if (oldDriver === null) { if (oldDriver === null) {
@ -148,11 +154,15 @@ export function incrementalFromCompilerTicket(
// program. // program.
return freshCompilationTicket( return freshCompilationTicket(
newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy, newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy,
oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData); perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
} }
const newDriver = if (perfRecorder === null) {
IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles); perfRecorder = ActivePerfRecorder.zeroedToNow();
}
const newDriver = IncrementalDriver.reconcile(
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
return { return {
kind: CompilationTicketKind.IncrementalTypeScript, kind: CompilationTicketKind.IncrementalTypeScript,
@ -164,6 +174,7 @@ export function incrementalFromCompilerTicket(
newDriver, newDriver,
oldProgram, oldProgram,
newProgram, newProgram,
perfRecorder,
}; };
} }
@ -175,9 +186,14 @@ export function incrementalFromDriverTicket(
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program, oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy, options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy,
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>, typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket { perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean,
const newDriver = usePoisonedData: boolean): CompilationTicket {
IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles); if (perfRecorder === null) {
perfRecorder = ActivePerfRecorder.zeroedToNow();
}
const newDriver = IncrementalDriver.reconcile(
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
return { return {
kind: CompilationTicketKind.IncrementalTypeScript, kind: CompilationTicketKind.IncrementalTypeScript,
oldProgram, oldProgram,
@ -188,6 +204,7 @@ export function incrementalFromDriverTicket(
typeCheckingProgramStrategy, typeCheckingProgramStrategy,
enableTemplateTypeChecker, enableTemplateTypeChecker,
usePoisonedData, usePoisonedData,
perfRecorder,
}; };
} }
@ -197,6 +214,7 @@ export function resourceChangeTicket(compiler: NgCompiler, modifiedResourceFiles
kind: CompilationTicketKind.IncrementalResource, kind: CompilationTicketKind.IncrementalResource,
compiler, compiler,
modifiedResourceFiles, modifiedResourceFiles,
perfRecorder: ActivePerfRecorder.zeroedToNow(),
}; };
} }
@ -245,16 +263,23 @@ export class NgCompiler {
readonly ignoreForDiagnostics: Set<ts.SourceFile>; readonly ignoreForDiagnostics: Set<ts.SourceFile>;
readonly ignoreForEmit: Set<ts.SourceFile>; readonly ignoreForEmit: Set<ts.SourceFile>;
/**
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
* new compilation uses a fresh `PerfRecorder`. Thus, classes created with a lifespan of the
* `NgCompiler` use a `DelegatingPerfRecorder` so the `PerfRecorder` they write to can be updated
* with each fresh compilation.
*/
private delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
/** /**
* Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation. * Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation.
* *
* Depending on the nature of the compilation request, the `NgCompiler` instance may be reused * Depending on the nature of the compilation request, the `NgCompiler` instance may be reused
* from a previous compilation and updated with any changes, it may be a new instance which * from a previous compilation and updated with any changes, it may be a new instance which
* incrementally reuses state from a previous compilation, or it may represent a fresh compilation * incrementally reuses state from a previous compilation, or it may represent a fresh
* entirely. * compilation entirely.
*/ */
static fromTicket( static fromTicket(ticket: CompilationTicket, adapter: NgCompilerAdapter) {
ticket: CompilationTicket, adapter: NgCompilerAdapter, perfRecorder?: PerfRecorder) {
switch (ticket.kind) { switch (ticket.kind) {
case CompilationTicketKind.Fresh: case CompilationTicketKind.Fresh:
return new NgCompiler( return new NgCompiler(
@ -266,7 +291,7 @@ export class NgCompiler {
IncrementalDriver.fresh(ticket.tsProgram), IncrementalDriver.fresh(ticket.tsProgram),
ticket.enableTemplateTypeChecker, ticket.enableTemplateTypeChecker,
ticket.usePoisonedData, ticket.usePoisonedData,
perfRecorder, ticket.perfRecorder,
); );
case CompilationTicketKind.IncrementalTypeScript: case CompilationTicketKind.IncrementalTypeScript:
return new NgCompiler( return new NgCompiler(
@ -278,11 +303,11 @@ export class NgCompiler {
ticket.newDriver, ticket.newDriver,
ticket.enableTemplateTypeChecker, ticket.enableTemplateTypeChecker,
ticket.usePoisonedData, ticket.usePoisonedData,
perfRecorder, ticket.perfRecorder,
); );
case CompilationTicketKind.IncrementalResource: case CompilationTicketKind.IncrementalResource:
const compiler = ticket.compiler; const compiler = ticket.compiler;
compiler.updateWithChangedResources(ticket.modifiedResourceFiles); compiler.updateWithChangedResources(ticket.modifiedResourceFiles, ticket.perfRecorder);
return compiler; return compiler;
} }
} }
@ -296,7 +321,7 @@ export class NgCompiler {
readonly incrementalDriver: IncrementalDriver, readonly incrementalDriver: IncrementalDriver,
readonly enableTemplateTypeChecker: boolean, readonly enableTemplateTypeChecker: boolean,
readonly usePoisonedData: boolean, readonly usePoisonedData: boolean,
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER, private livePerfRecorder: ActivePerfRecorder,
) { ) {
this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics); this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options); const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
@ -312,9 +337,7 @@ export class NgCompiler {
const moduleResolutionCache = ts.createModuleResolutionCache( const moduleResolutionCache = ts.createModuleResolutionCache(
this.adapter.getCurrentDirectory(), this.adapter.getCurrentDirectory(),
// Note: this used to be an arrow-function closure. However, JS engines like v8 have some // doen't retain a reference to `this`, if other closures in the constructor here reference
// strange behaviors with retaining the lexical scope of the closure. Even if this function
// doesn't retain a reference to `this`, if other closures in the constructor here reference
// `this` internally then a closure created here would retain them. This can cause major // `this` internally then a closure created here would retain them. This can cause major
// memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its // memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
// way into all kinds of places inside TS internal objects. // way into all kinds of places inside TS internal objects.
@ -322,18 +345,41 @@ export class NgCompiler {
this.moduleResolver = this.moduleResolver =
new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache); new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
this.resourceManager = new AdapterResourceLoader(adapter, this.options); this.resourceManager = new AdapterResourceLoader(adapter, this.options);
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(tsProgram.getTypeChecker())); this.cycleAnalyzer =
new CycleAnalyzer(new ImportGraph(tsProgram.getTypeChecker(), this.delegatingPerfRecorder));
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram); this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram);
this.ignoreForDiagnostics = this.ignoreForDiagnostics =
new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf))); new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
this.ignoreForEmit = this.adapter.ignoreForEmit; this.ignoreForEmit = this.adapter.ignoreForEmit;
let dtsFileCount = 0;
let nonDtsFileCount = 0;
for (const sf of tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile) {
dtsFileCount++;
} else {
nonDtsFileCount++;
}
} }
private updateWithChangedResources(changedResources: Set<string>): void { livePerfRecorder.eventCount(PerfEvent.InputDtsFile, dtsFileCount);
livePerfRecorder.eventCount(PerfEvent.InputTsFile, nonDtsFileCount);
}
get perfRecorder(): ActivePerfRecorder {
return this.livePerfRecorder;
}
private updateWithChangedResources(
changedResources: Set<string>, perfRecorder: ActivePerfRecorder): void {
this.livePerfRecorder = perfRecorder;
this.delegatingPerfRecorder.target = perfRecorder;
perfRecorder.inPhase(PerfPhase.ResourceUpdate, () => {
if (this.compilation === null) { if (this.compilation === null) {
// Analysis hasn't happened yet, so no update is necessary - any changes to resources will be // Analysis hasn't happened yet, so no update is necessary - any changes to resources will
// captured by the inital analysis pass itself. // be captured by the inital analysis pass itself.
return; return;
} }
@ -358,6 +404,7 @@ export class NgCompiler {
this.compilation.templateTypeChecker.invalidateClass(clazz); this.compilation.templateTypeChecker.invalidateClass(clazz);
} }
});
} }
/** /**
@ -481,23 +528,18 @@ export class NgCompiler {
if (this.compilation !== null) { if (this.compilation !== null) {
return; return;
} }
await this.perfRecorder.inPhase(PerfPhase.Analysis, async () => {
this.compilation = this.makeCompilation(); this.compilation = this.makeCompilation();
const analyzeSpan = this.perfRecorder.start('analyze');
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
for (const sf of this.tsProgram.getSourceFiles()) { for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile) { if (sf.isDeclarationFile) {
continue; continue;
} }
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf); let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
this.scanForMwp(sf); this.scanForMwp(sf);
if (analysisPromise === undefined) {
this.perfRecorder.stop(analyzeFileSpan);
} else if (this.perfRecorder.enabled) {
analysisPromise = analysisPromise.then(() => this.perfRecorder.stop(analyzeFileSpan));
}
if (analysisPromise !== undefined) { if (analysisPromise !== undefined) {
promises.push(analysisPromise); promises.push(analysisPromise);
} }
@ -505,9 +547,9 @@ export class NgCompiler {
await Promise.all(promises); await Promise.all(promises);
this.perfRecorder.stop(analyzeSpan); this.perfRecorder.memory(PerfCheckpoint.Analysis);
this.resolveCompilation(this.compilation.traitCompiler); this.resolveCompilation(this.compilation.traitCompiler);
});
} }
/** /**
@ -517,9 +559,7 @@ export class NgCompiler {
*/ */
listLazyRoutes(entryRoute?: string): LazyRoute[] { listLazyRoutes(entryRoute?: string): LazyRoute[] {
if (entryRoute) { if (entryRoute) {
// Note: // htts://github.com/angular/angular/blob/50732e156/packages/compiler-cli/src/transformers/compiler_host.ts#L175-L188).
// This resolution step is here to match the implementation of the old `AotCompilerHost` (see
// https://github.com/angular/angular/blob/50732e156/packages/compiler-cli/src/transformers/compiler_host.ts#L175-L188).
// //
// `@angular/cli` will always call this API with an absolute path, so the resolution step is // `@angular/cli` will always call this API with an absolute path, so the resolution step is
// not necessary, but keeping it backwards compatible in case someone else is using the API. // not necessary, but keeping it backwards compatible in case someone else is using the API.
@ -573,7 +613,8 @@ export class NgCompiler {
const before = [ const before = [
ivyTransformFactory( ivyTransformFactory(
compilation.traitCompiler, compilation.reflector, importRewriter, compilation.traitCompiler, compilation.reflector, importRewriter,
compilation.defaultImportTracker, compilation.isCore, this.closureCompilerEnabled), compilation.defaultImportTracker, this.delegatingPerfRecorder, compilation.isCore,
this.closureCompilerEnabled),
aliasTransformFactory(compilation.traitCompiler.exportStatements), aliasTransformFactory(compilation.traitCompiler.exportStatements),
compilation.defaultImportTracker.importPreservingTransformer(), compilation.defaultImportTracker.importPreservingTransformer(),
]; ];
@ -618,28 +659,32 @@ export class NgCompiler {
} }
private analyzeSync(): void { private analyzeSync(): void {
const analyzeSpan = this.perfRecorder.start('analyze'); this.perfRecorder.inPhase(PerfPhase.Analysis, () => {
this.compilation = this.makeCompilation(); this.compilation = this.makeCompilation();
for (const sf of this.tsProgram.getSourceFiles()) { for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile) { if (sf.isDeclarationFile) {
continue; continue;
} }
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
this.compilation.traitCompiler.analyzeSync(sf); this.compilation.traitCompiler.analyzeSync(sf);
this.scanForMwp(sf); this.scanForMwp(sf);
this.perfRecorder.stop(analyzeFileSpan);
} }
this.perfRecorder.stop(analyzeSpan);
this.perfRecorder.memory(PerfCheckpoint.Analysis);
this.resolveCompilation(this.compilation.traitCompiler); this.resolveCompilation(this.compilation.traitCompiler);
});
} }
private resolveCompilation(traitCompiler: TraitCompiler): void { private resolveCompilation(traitCompiler: TraitCompiler): void {
this.perfRecorder.inPhase(PerfPhase.Resolve, () => {
traitCompiler.resolve(); traitCompiler.resolve();
// At this point, analysis is complete and the compiler can now calculate which files need to // At this point, analysis is complete and the compiler can now calculate which files need to
// be emitted, so do that. // be emitted, so do that.
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler); this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
this.perfRecorder.memory(PerfCheckpoint.Resolve);
});
} }
private get fullTemplateTypeCheck(): boolean { private get fullTemplateTypeCheck(): boolean {
@ -770,7 +815,6 @@ export class NgCompiler {
const compilation = this.ensureAnalyzed(); const compilation = this.ensureAnalyzed();
// Get the diagnostics. // Get the diagnostics.
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
const diagnostics: ts.Diagnostic[] = []; const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) { for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile || this.adapter.isShim(sf)) { if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
@ -782,7 +826,6 @@ export class NgCompiler {
} }
const program = this.typeCheckingProgramStrategy.getProgram(); const program = this.typeCheckingProgramStrategy.getProgram();
this.perfRecorder.stop(typeCheckSpan);
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program); this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
this.nextProgram = program; this.nextProgram = program;
@ -794,14 +837,12 @@ export class NgCompiler {
const compilation = this.ensureAnalyzed(); const compilation = this.ensureAnalyzed();
// Get the diagnostics. // Get the diagnostics.
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
const diagnostics: ts.Diagnostic[] = []; const diagnostics: ts.Diagnostic[] = [];
if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) { if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor)); diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
} }
const program = this.typeCheckingProgramStrategy.getProgram(); const program = this.typeCheckingProgramStrategy.getProgram();
this.perfRecorder.stop(typeCheckSpan);
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program); this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
this.nextProgram = program; this.nextProgram = program;
@ -950,7 +991,8 @@ export class NgCompiler {
this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData, this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData,
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer, this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
cycleHandlingStrategy, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph, cycleHandlingStrategy, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph,
injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled), injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled,
this.delegatingPerfRecorder),
// TODO(alxhub): understand why the cast here is necessary (something to do with `null` // TODO(alxhub): understand why the cast here is necessary (something to do with `null`
// not being assignable to `unknown` when wrapped in `Readonly`). // not being assignable to `unknown` when wrapped in `Readonly`).
@ -959,31 +1001,33 @@ export class NgCompiler {
reflector, evaluator, metaRegistry, scopeRegistry, metaReader, reflector, evaluator, metaRegistry, scopeRegistry, metaReader,
defaultImportTracker, injectableRegistry, isCore, semanticDepGraphUpdater, defaultImportTracker, injectableRegistry, isCore, semanticDepGraphUpdater,
this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures, this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures,
this.delegatingPerfRecorder,
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>, ) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
// clang-format on // clang-format on
// Pipe handler must be before injectable handler in list so pipe factories are printed // Pipe handler must be before injectable handler in list so pipe factories are printed
// before injectable factories (so injectable factories can delegate to them) // before injectable factories (so injectable factories can delegate to them)
new PipeDecoratorHandler( new PipeDecoratorHandler(
reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker, reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker,
injectableRegistry, isCore), injectableRegistry, isCore, this.delegatingPerfRecorder),
new InjectableDecoratorHandler( new InjectableDecoratorHandler(
reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false, reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false,
injectableRegistry), injectableRegistry, this.delegatingPerfRecorder),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore, reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker, routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale), this.closureCompilerEnabled, injectableRegistry, this.delegatingPerfRecorder,
this.options.i18nInLocale),
]; ];
const traitCompiler = new TraitCompiler( const traitCompiler = new TraitCompiler(
handlers, reflector, this.perfRecorder, this.incrementalDriver, handlers, reflector, this.delegatingPerfRecorder, this.incrementalDriver,
this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms, this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms,
semanticDepGraphUpdater); semanticDepGraphUpdater);
const templateTypeChecker = new TemplateTypeCheckerImpl( const templateTypeChecker = new TemplateTypeCheckerImpl(
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler, this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver, this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver,
scopeRegistry, typeCheckScopeRegistry); scopeRegistry, typeCheckScopeRegistry, this.delegatingPerfRecorder);
return { return {
isCore, isCore,

View File

@ -25,8 +25,8 @@ function makeFreshCompiler(
programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy, programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy,
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler { enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler {
const ticket = freshCompilationTicket( const ticket = freshCompilationTicket(
program, options, incrementalStrategy, programStrategy, enableTemplateTypeChecker, program, options, incrementalStrategy, programStrategy, /* perfRecorder */ null,
usePoisonedData); enableTemplateTypeChecker, usePoisonedData);
return NgCompiler.fromTicket(ticket, host); return NgCompiler.fromTicket(ticket, host);
} }

View File

@ -9,6 +9,7 @@ ts_library(
]), ]),
module_name = "@angular/compiler-cli/src/ngtsc/cycles", module_name = "@angular/compiler-cli/src/ngtsc/cycles",
deps = [ deps = [
"//packages/compiler-cli/src/ngtsc/perf",
"@npm//typescript", "@npm//typescript",
], ],
) )

View File

@ -8,6 +8,8 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {PerfPhase, PerfRecorder} from '../../perf';
/** /**
* A cached graph of imports in the `ts.Program`. * A cached graph of imports in the `ts.Program`.
* *
@ -17,7 +19,7 @@ import * as ts from 'typescript';
export class ImportGraph { export class ImportGraph {
private map = new Map<ts.SourceFile, Set<ts.SourceFile>>(); private map = new Map<ts.SourceFile, Set<ts.SourceFile>>();
constructor(private checker: ts.TypeChecker) {} constructor(private checker: ts.TypeChecker, private perf: PerfRecorder) {}
/** /**
* List the direct (not transitive) imports of a given `ts.SourceFile`. * List the direct (not transitive) imports of a given `ts.SourceFile`.
@ -99,6 +101,7 @@ export class ImportGraph {
} }
private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> { private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> {
return this.perf.inPhase(PerfPhase.CycleDetection, () => {
const imports = new Set<ts.SourceFile>(); const imports = new Set<ts.SourceFile>();
// Look through the source file for import and export statements. // Look through the source file for import and export statements.
for (const stmt of sf.statements) { for (const stmt of sf.statements) {
@ -119,6 +122,7 @@ export class ImportGraph {
} }
} }
return imports; return imports;
});
} }
} }

View File

@ -13,6 +13,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/cycles", "//packages/compiler-cli/src/ngtsc/cycles",
"//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/file_system/testing", "//packages/compiler-cli/src/ngtsc/file_system/testing",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/testing", "//packages/compiler-cli/src/ngtsc/testing",
"@npm//typescript", "@npm//typescript",
], ],

View File

@ -8,6 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system'; import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing'; import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_PERF_RECORDER} from '../../perf';
import {Cycle, CycleAnalyzer} from '../src/analyzer'; import {Cycle, CycleAnalyzer} from '../src/analyzer';
import {ImportGraph} from '../src/imports'; import {ImportGraph} from '../src/imports';
import {importPath, makeProgramFromGraph} from './util'; import {importPath, makeProgramFromGraph} from './util';
@ -75,7 +76,7 @@ runInEachFileSystem(() => {
const {program} = makeProgramFromGraph(getFileSystem(), graph); const {program} = makeProgramFromGraph(getFileSystem(), graph);
return { return {
program, program,
analyzer: new CycleAnalyzer(new ImportGraph(program.getTypeChecker())), analyzer: new CycleAnalyzer(new ImportGraph(program.getTypeChecker(), NOOP_PERF_RECORDER)),
}; };
} }
}); });

View File

@ -8,6 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system'; import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing'; import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_PERF_RECORDER} from '../../perf';
import {ImportGraph} from '../src/imports'; import {ImportGraph} from '../src/imports';
import {importPath, makeProgramFromGraph} from './util'; import {importPath, makeProgramFromGraph} from './util';
@ -86,7 +87,7 @@ runInEachFileSystem(() => {
const {program} = makeProgramFromGraph(getFileSystem(), graph); const {program} = makeProgramFromGraph(getFileSystem(), graph);
return { return {
program, program,
graph: new ImportGraph(program.getTypeChecker()), graph: new ImportGraph(program.getTypeChecker(), NOOP_PERF_RECORDER),
}; };
} }

View File

@ -14,6 +14,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/incremental/semantic_graph", "//packages/compiler-cli/src/ngtsc/incremental/semantic_graph",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/transform", "//packages/compiler-cli/src/ngtsc/transform",

View File

@ -9,6 +9,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system'; import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
import {PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
import {ClassDeclaration} from '../../reflection'; import {ClassDeclaration} from '../../reflection';
import {ClassRecord, TraitCompiler} from '../../transform'; import {ClassRecord, TraitCompiler} from '../../transform';
import {FileTypeCheckingData} from '../../typecheck/src/checker'; import {FileTypeCheckingData} from '../../typecheck/src/checker';
@ -43,12 +44,13 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
*/ */
static reconcile( static reconcile(
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program, oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
modifiedResourceFiles: Set<string>|null): IncrementalDriver { modifiedResourceFiles: Set<string>|null, perf: PerfRecorder): IncrementalDriver {
return perf.inPhase(PerfPhase.Reconciliation, () => {
// Initialize the state of the current build based on the previous one. // Initialize the state of the current build based on the previous one.
let state: PendingBuildState; let state: PendingBuildState;
if (oldDriver.state.kind === BuildStateKind.Pending) { if (oldDriver.state.kind === BuildStateKind.Pending) {
// The previous build never made it past the pending state. Reuse it as the starting state for // The previous build never made it past the pending state. Reuse it as the starting state
// this build. // for this build.
state = oldDriver.state; state = oldDriver.state;
} else { } else {
let priorGraph: SemanticDepGraph|null = null; let priorGraph: SemanticDepGraph|null = null;
@ -79,8 +81,8 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
// Next, process the files in the new program, with a couple of goals: // Next, process the files in the new program, with a couple of goals:
// 1) Determine which TS files have changed, if any, and merge them into `changedTsFiles`. // 1) Determine which TS files have changed, if any, and merge them into `changedTsFiles`.
// 2) Produce a list of TS files which no longer exist in the program (they've been deleted // 2) Produce a list of TS files which no longer exist in the program (they've been deleted
// since the previous compilation). These need to be removed from the state tracking to avoid // since the previous compilation). These need to be removed from the state tracking to
// leaking memory. // avoid leaking memory.
// All files in the old program, for easy detection of changes. // All files in the old program, for easy detection of changes.
const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles()); const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles());
@ -106,8 +108,8 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
state.changedTsPaths.add(newFile.fileName); state.changedTsPaths.add(newFile.fileName);
} else { } else {
// It's a .d.ts file. Currently the compiler does not do a great job of tracking // It's a .d.ts file. Currently the compiler does not do a great job of tracking
// dependencies on .d.ts files, so bail out of incremental builds here and do a full build. // dependencies on .d.ts files, so bail out of incremental builds here and do a full
// This usually only happens if something in node_modules changes. // build. This usually only happens if something in node_modules changes.
return IncrementalDriver.fresh(newProgram); return IncrementalDriver.fresh(newProgram);
} }
} }
@ -117,25 +119,28 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
state.pendingEmit.delete(filePath); state.pendingEmit.delete(filePath);
state.pendingTypeCheckEmit.delete(filePath); state.pendingTypeCheckEmit.delete(filePath);
// Even if the file doesn't exist in the current compilation, it still might have been changed // Even if the file doesn't exist in the current compilation, it still might have been
// in a previous one, so delete it from the set of changed TS files, just in case. // changed in a previous one, so delete it from the set of changed TS files, just in case.
state.changedTsPaths.delete(filePath); state.changedTsPaths.delete(filePath);
} }
// Now, changedTsPaths contains physically changed TS paths. Use the previous program's logical perf.eventCount(PerfEvent.SourceFilePhysicalChange, state.changedTsPaths.size);
// dependency graph to determine logically changed files.
// Now, changedTsPaths contains physically changed TS paths. Use the previous program's
// logical dependency graph to determine logically changed files.
const depGraph = new FileDependencyGraph(); const depGraph = new FileDependencyGraph();
// If a previous compilation exists, use its dependency graph to determine the set of logically // If a previous compilation exists, use its dependency graph to determine the set of
// changed files. // logically changed files.
let logicalChanges: Set<string>|null = null; let logicalChanges: Set<string>|null = null;
if (state.lastGood !== null) { if (state.lastGood !== null) {
// Extract the set of logically changed files. At the same time, this operation populates the // Extract the set of logically changed files. At the same time, this operation populates
// current (fresh) dependency graph with information about those files which have not // the current (fresh) dependency graph with information about those files which have not
// logically changed. // logically changed.
logicalChanges = depGraph.updateWithPhysicalChanges( logicalChanges = depGraph.updateWithPhysicalChanges(
state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths, state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths,
state.changedResourcePaths); state.changedResourcePaths);
perf.eventCount(PerfEvent.SourceFileLogicalChange, logicalChanges.size);
for (const fileName of state.changedTsPaths) { for (const fileName of state.changedTsPaths) {
logicalChanges.add(fileName); logicalChanges.add(fileName);
} }
@ -153,8 +158,8 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
} }
// `state` now reflects the initial pending state of the current compilation. // `state` now reflects the initial pending state of the current compilation.
return new IncrementalDriver(state, depGraph, logicalChanges); return new IncrementalDriver(state, depGraph, logicalChanges);
});
} }
static fresh(program: ts.Program): IncrementalDriver { static fresh(program: ts.Program): IncrementalDriver {

View File

@ -9,8 +9,6 @@ ts_library(
]), ]),
deps = [ deps = [
"//packages:types", "//packages:types",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/reflection",
"@npm//@types/node", "@npm//@types/node",
"@npm//typescript", "@npm//typescript",
], ],

View File

@ -6,6 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export {PerfRecorder} from './src/api'; export * from './src/api';
export {NOOP_PERF_RECORDER} from './src/noop'; export {NOOP_PERF_RECORDER} from './src/noop';
export {PerfTracker} from './src/tracking'; export {ActivePerfRecorder, DelegatingPerfRecorder} from './src/recorder';

View File

@ -6,12 +6,328 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {DeclarationNode} from '../../reflection'; /**
* A phase of compilation for which time is tracked in a distinct bucket.
*/
export enum PerfPhase {
/**
* The "default" phase which tracks time not spent in any other phase.
*/
Unaccounted,
export interface PerfRecorder { /**
readonly enabled: boolean; * Time spent setting up the compiler, before a TypeScript program is created.
*
* This includes operations like configuring the `ts.CompilerHost` and any wrappers.
*/
Setup,
mark(name: string, node?: DeclarationNode, category?: string, detail?: string): void; /**
start(name: string, node?: DeclarationNode, category?: string, detail?: string): number; * Time spent in `ts.createProgram`, including reading and parsing `ts.SourceFile`s in the
stop(span: number): void; * `ts.CompilerHost`.
*
* This might be an incremental program creation operation.
*/
TypeScriptProgramCreate,
/**
* Time spent reconciling the contents of an old `ts.Program` with the new incremental one.
*
* Only present in incremental compilations.
*/
Reconciliation,
/**
* Time spent updating an `NgCompiler` instance with a resource-only change.
*
* Only present in incremental compilations where the change was resource-only.
*/
ResourceUpdate,
/**
* Time spent calculating the plain TypeScript diagnostics (structural and semantic).
*/
TypeScriptDiagnostics,
/**
* Time spent in Angular analysis of individual classes in the program.
*/
Analysis,
/**
* Time spent in Angular global analysis (synthesis of analysis information into a complete
* understanding of the program).
*/
Resolve,
/**
* Time spent building the import graph of the program in order to perform cycle detection.
*/
CycleDetection,
/**
* Time spent generating the text of Type Check Blocks in order to perform template type checking.
*/
TcbGeneration,
/**
* Time spent updating the `ts.Program` with new Type Check Block code.
*/
TcbUpdateProgram,
/**
* Time spent by TypeScript performing its emit operations, including downleveling and writing
* output files.
*/
TypeScriptEmit,
/**
* Time spent by Angular performing code transformations of ASTs as they're about to be emitted.
*
* This includes the actual code generation step for templates, and occurs during the emit phase
* (but is tracked separately from `TypeScriptEmit` time).
*/
Compile,
/**
* Time spent performing a `TemplateTypeChecker` autocompletion operation.
*/
TtcAutocompletion,
/**
* Time spent computing template type-checking diagnostics.
*/
TtcDiagnostics,
/**
* Time spent getting a `Symbol` from the `TemplateTypeChecker`.
*/
TtcSymbol,
/**
* Time spent by the Angular Language Service calculating a "get references" or a renaming
* operation.
*/
LsReferencesAndRenames,
/**
* Tracks the number of `PerfPhase`s, and must appear at the end of the list.
*/
LAST,
}
/**
* Represents some occurrence during compilation, and is tracked with a counter.
*/
export enum PerfEvent {
/**
* Counts the number of `.d.ts` files in the program.
*/
InputDtsFile,
/**
* Counts the number of non-`.d.ts` files in the program.
*/
InputTsFile,
/**
* An `@Component` class was analyzed.
*/
AnalyzeComponent,
/**
* An `@Directive` class was analyzed.
*/
AnalyzeDirective,
/**
* An `@Injectable` class was analyzed.
*/
AnalyzeInjectable,
/**
* An `@NgModule` class was analyzed.
*/
AnalyzeNgModule,
/**
* An `@Pipe` class was analyzed.
*/
AnalyzePipe,
/**
* A trait was analyzed.
*
* In theory, this should be the sum of the `Analyze` counters for each decorator type.
*/
TraitAnalyze,
/**
* A trait had a prior analysis available from an incremental program, and did not need to be
* re-analyzed.
*/
TraitReuseAnalysis,
/**
* A `ts.SourceFile` directly changed between the prior program and a new incremental compilation.
*/
SourceFilePhysicalChange,
/**
* A `ts.SourceFile` did not physically changed, but according to the file dependency graph, has
* logically changed between the prior program and a new incremental compilation.
*/
SourceFileLogicalChange,
/**
* A `ts.SourceFile` has not logically changed and all of its analysis results were thus available
* for reuse.
*/
SourceFileReuseAnalysis,
/**
* A Type Check Block (TCB) was generated.
*/
GenerateTcb,
/**
* A Type Check Block (TCB) could not be generated because inlining was disabled, and the block
* would've required inlining.
*/
SkipGenerateTcbNoInline,
/**
* A `.ngtypecheck.ts` file could be reused from the previous program and did not need to be
* regenerated.
*/
ReuseTypeCheckFile,
/**
* The template type-checking program required changes and had to be updated in an incremental
* step.
*/
UpdateTypeCheckProgram,
/**
* The compiler was able to prove that a `ts.SourceFile` did not need to be re-emitted.
*/
EmitSkipSourceFile,
/**
* A `ts.SourceFile` was emitted.
*/
EmitSourceFile,
/**
* Tracks the number of `PrefEvent`s, and must appear at the end of the list.
*/
LAST,
}
/**
* Represents a checkpoint during compilation at which the memory usage of the compiler should be
* recorded.
*/
export enum PerfCheckpoint {
/**
* The point at which the `PerfRecorder` was created, and ideally tracks memory used before any
* compilation structures are created.
*/
Initial,
/**
* The point just after the `ts.Program` has been created.
*/
TypeScriptProgramCreate,
/**
* The point just before Angular analysis starts.
*
* In the main usage pattern for the compiler, TypeScript diagnostics have been calculated at this
* point, so the `ts.TypeChecker` has fully ingested the current program, all `ts.Type` structures
* and `ts.Symbol`s have been created.
*/
PreAnalysis,
/**
* The point just after Angular analysis completes.
*/
Analysis,
/**
* The point just after Angular resolution is complete.
*/
Resolve,
/**
* The point just after Type Check Blocks (TCBs) have been generated.
*/
TtcGeneration,
/**
* The point just after the template type-checking program has been updated with any new TCBs.
*/
TtcUpdateProgram,
/**
* The point just before emit begins.
*
* In the main usage pattern for the compiler, all template type-checking diagnostics have been
* requested at this point.
*/
PreEmit,
/**
* The point just after the program has been fully emitted.
*/
Emit,
/**
* Tracks the number of `PerfCheckpoint`s, and must appear at the end of the list.
*/
LAST,
}
/**
* Records timing, memory, or counts at specific points in the compiler's operation.
*/
export interface PerfRecorder {
/**
* Set the current phase of compilation.
*
* Time spent in the previous phase will be accounted to that phase. The caller is responsible for
* exiting the phase when work that should be tracked within it is completed, and either returning
* to the previous phase or transitioning to the next one directly.
*
* In general, prefer using `inPhase()` to instrument a section of code, as it automatically
* handles entering and exiting the phase. `phase()` should only be used when the former API
* cannot be cleanly applied to a particular operation.
*
* @returns the previous phase
*/
phase(phase: PerfPhase): PerfPhase;
/**
* Run `fn` in the given `PerfPhase` and return the result.
*
* Enters `phase` before executing the given `fn`, then exits the phase and returns the result.
* Prefer this API to `phase()` where possible.
*/
inPhase<T>(phase: PerfPhase, fn: () => T): T;
/**
* Record the memory usage of the compiler at the given checkpoint.
*/
memory(after: PerfCheckpoint): void;
/**
* Record that a specific event has occurred, possibly more than once.
*/
eventCount(event: PerfEvent, incrementBy?: number): void;
/**
* Return the `PerfRecorder` to an empty state (clear all tracked statistics) and reset the zero
* point to the current time.
*/
reset(): void;
} }

View File

@ -5,15 +5,23 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {DeclarationNode} from '../../reflection'; import {PerfPhase, PerfRecorder} from './api';
import {PerfRecorder} from './api'; class NoopPerfRecorder implements PerfRecorder {
eventCount(): void {}
export const NOOP_PERF_RECORDER: PerfRecorder = { memory(): void {}
enabled: false,
mark: (name: string, node: DeclarationNode, category?: string, detail?: string): void => {}, phase(): PerfPhase {
start: (name: string, node: DeclarationNode, category?: string, detail?: string): number => { return PerfPhase.Unaccounted;
return 0; }
},
stop: (span: number|false): void => {}, inPhase<T>(phase: PerfPhase, fn: () => T): T {
}; return fn();
}
reset(): void {}
}
export const NOOP_PERF_RECORDER: PerfRecorder = new NoopPerfRecorder();

View File

@ -0,0 +1,154 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/// <reference types="node" />
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from './api';
import {HrTime, mark, timeSinceInMicros} from './clock';
/**
* Serializable performance data for the compilation, using string names.
*/
export interface PerfResults {
events: Record<string, number>;
phases: Record<string, number>;
memory: Record<string, number>;
}
/**
* A `PerfRecorder` that actively tracks performance statistics.
*/
export class ActivePerfRecorder implements PerfRecorder {
private counters: number[];
private phaseTime: number[];
private bytes: number[];
private currentPhase = PerfPhase.Unaccounted;
private currentPhaseEntered = this.zeroTime;
/**
* Creates an `ActivePerfRecoder` with its zero point set to the current time.
*/
static zeroedToNow(): ActivePerfRecorder {
return new ActivePerfRecorder(mark());
}
private constructor(private zeroTime: HrTime) {
this.counters = Array(PerfEvent.LAST).fill(0);
this.phaseTime = Array(PerfPhase.LAST).fill(0);
this.bytes = Array(PerfCheckpoint.LAST).fill(0);
// Take an initial memory snapshot before any other compilation work begins.
this.memory(PerfCheckpoint.Initial);
}
reset(): void {
this.counters = Array(PerfEvent.LAST).fill(0);
this.phaseTime = Array(PerfPhase.LAST).fill(0);
this.bytes = Array(PerfCheckpoint.LAST).fill(0);
this.zeroTime = mark();
this.currentPhase = PerfPhase.Unaccounted;
this.currentPhaseEntered = this.zeroTime;
}
memory(after: PerfCheckpoint): void {
this.bytes[after] = process.memoryUsage().heapUsed;
}
phase(phase: PerfPhase): PerfPhase {
const previous = this.currentPhase;
this.phaseTime[this.currentPhase] += timeSinceInMicros(this.currentPhaseEntered);
this.currentPhase = phase;
this.currentPhaseEntered = mark();
return previous;
}
inPhase<T>(phase: PerfPhase, fn: () => T): T {
const previousPhase = this.phase(phase);
try {
return fn();
} finally {
this.phase(previousPhase);
}
}
eventCount(counter: PerfEvent, incrementBy: number = 1): void {
this.counters[counter] += incrementBy;
}
/**
* Return the current performance metrics as a serializable object.
*/
finalize(): PerfResults {
// Track the last segment of time spent in `this.currentPhase` in the time array.
this.phase(PerfPhase.Unaccounted);
const results: PerfResults = {
events: {},
phases: {},
memory: {},
};
for (let i = 0; i < this.phaseTime.length; i++) {
if (this.phaseTime[i] > 0) {
results.phases[PerfPhase[i]] = this.phaseTime[i];
}
}
for (let i = 0; i < this.phaseTime.length; i++) {
if (this.counters[i] > 0) {
results.events[PerfEvent[i]] = this.counters[i];
}
}
for (let i = 0; i < this.bytes.length; i++) {
if (this.bytes[i] > 0) {
results.memory[PerfCheckpoint[i]] = this.bytes[i];
}
}
return results;
}
}
/**
* A `PerfRecorder` that delegates to a target `PerfRecorder` which can be updated later.
*
* `DelegatingPerfRecorder` is useful when a compiler class that needs a `PerfRecorder` can outlive
* the current compilation. This is true for most compiler classes as resource-only changes reuse
* the same `NgCompiler` for a new compilation.
*/
export class DelegatingPerfRecorder implements PerfRecorder {
constructor(public target: PerfRecorder) {}
eventCount(counter: PerfEvent, incrementBy?: number): void {
this.target.eventCount(counter, incrementBy);
}
phase(phase: PerfPhase): PerfPhase {
return this.target.phase(phase);
}
inPhase<T>(phase: PerfPhase, fn: () => T): T {
// Note: this doesn't delegate to `this.target.inPhase` but instead is implemented manually here
// to avoid adding an additional frame of noise to the stack when debugging.
const previousPhase = this.target.phase(phase);
try {
return fn();
} finally {
this.target.phase(previousPhase);
}
}
memory(after: PerfCheckpoint): void {
this.target.memory(after);
}
reset(): void {
this.target.reset();
}
}

View File

@ -1,110 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/// <reference types="node" />
import * as fs from 'fs';
import * as ts from 'typescript';
import {resolve} from '../../file_system';
import {DeclarationNode} from '../../reflection';
import {PerfRecorder} from './api';
import {HrTime, mark, timeSinceInMicros} from './clock';
export class PerfTracker implements PerfRecorder {
private nextSpanId = 1;
private log: PerfLogEvent[] = [];
readonly enabled = true;
private constructor(private zeroTime: HrTime) {}
static zeroedToNow(): PerfTracker {
return new PerfTracker(mark());
}
mark(name: string, node?: DeclarationNode, category?: string, detail?: string): void {
const msg = this.makeLogMessage(PerfLogEventType.MARK, name, node, category, detail, undefined);
this.log.push(msg);
}
start(name: string, node?: DeclarationNode, category?: string, detail?: string): number {
const span = this.nextSpanId++;
const msg = this.makeLogMessage(PerfLogEventType.SPAN_OPEN, name, node, category, detail, span);
this.log.push(msg);
return span;
}
stop(span: number): void {
this.log.push({
type: PerfLogEventType.SPAN_CLOSE,
span,
stamp: timeSinceInMicros(this.zeroTime),
});
}
private makeLogMessage(
type: PerfLogEventType, name: string, node: DeclarationNode|undefined,
category: string|undefined, detail: string|undefined, span: number|undefined): PerfLogEvent {
const msg: PerfLogEvent = {
type,
name,
stamp: timeSinceInMicros(this.zeroTime),
};
if (category !== undefined) {
msg.category = category;
}
if (detail !== undefined) {
msg.detail = detail;
}
if (span !== undefined) {
msg.span = span;
}
if (node !== undefined) {
msg.file = node.getSourceFile().fileName;
if (!ts.isSourceFile(node)) {
const name = ts.getNameOfDeclaration(node);
if (name !== undefined && ts.isIdentifier(name)) {
msg.declaration = name.text;
}
}
}
return msg;
}
asJson(): unknown {
return this.log;
}
serializeToFile(target: string, host: ts.CompilerHost): void {
const json = JSON.stringify(this.log, null, 2);
if (target.startsWith('ts:')) {
target = target.substr('ts:'.length);
const outFile = resolve(host.getCurrentDirectory(), target);
host.writeFile(outFile, json, false);
} else {
const outFile = resolve(host.getCurrentDirectory(), target);
fs.writeFileSync(outFile, json);
}
}
}
export interface PerfLogEvent {
name?: string;
span?: number;
file?: string;
declaration?: string;
type: PerfLogEventType;
category?: string;
detail?: string;
stamp: number;
}
export enum PerfLogEventType {
SPAN_OPEN,
SPAN_CLOSE,
MARK,
}

View File

@ -14,10 +14,10 @@ import {verifySupportedTypeScriptVersion} from '../typescript_support';
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, NgCompilerHost} from './core'; import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, NgCompilerHost} from './core';
import {NgCompilerOptions} from './core/api'; import {NgCompilerOptions} from './core/api';
import {absoluteFrom, AbsoluteFsPath} from './file_system'; import {absoluteFrom, AbsoluteFsPath, getFileSystem} from './file_system';
import {TrackedIncrementalBuildStrategy} from './incremental'; import {TrackedIncrementalBuildStrategy} from './incremental';
import {IndexedComponent} from './indexer'; import {IndexedComponent} from './indexer';
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf'; import {ActivePerfRecorder, PerfCheckpoint as PerfCheckpoint, PerfEvent, PerfPhase} from './perf';
import {DeclarationNode} from './reflection'; import {DeclarationNode} from './reflection';
import {retagAllTsFiles, untagAllTsFiles} from './shims'; import {retagAllTsFiles, untagAllTsFiles} from './shims';
import {ReusedProgramStrategy} from './typecheck'; import {ReusedProgramStrategy} from './typecheck';
@ -54,22 +54,20 @@ export class NgtscProgram implements api.Program {
private reuseTsProgram: ts.Program; private reuseTsProgram: ts.Program;
private closureCompilerEnabled: boolean; private closureCompilerEnabled: boolean;
private host: NgCompilerHost; private host: NgCompilerHost;
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER;
private perfTracker: PerfTracker|null = null;
private incrementalStrategy: TrackedIncrementalBuildStrategy; private incrementalStrategy: TrackedIncrementalBuildStrategy;
constructor( constructor(
rootNames: ReadonlyArray<string>, private options: NgCompilerOptions, rootNames: ReadonlyArray<string>, private options: NgCompilerOptions,
delegateHost: api.CompilerHost, oldProgram?: NgtscProgram) { delegateHost: api.CompilerHost, oldProgram?: NgtscProgram) {
const perfRecorder = ActivePerfRecorder.zeroedToNow();
perfRecorder.phase(PerfPhase.Setup);
// First, check whether the current TS version is supported. // First, check whether the current TS version is supported.
if (!options.disableTypeScriptVersionCheck) { if (!options.disableTypeScriptVersionCheck) {
verifySupportedTypeScriptVersion(); verifySupportedTypeScriptVersion();
} }
if (options.tracePerformance !== undefined) {
this.perfTracker = PerfTracker.zeroedToNow();
this.perfRecorder = this.perfTracker;
}
this.closureCompilerEnabled = !!options.annotateForClosureCompiler; this.closureCompilerEnabled = !!options.annotateForClosureCompiler;
const reuseProgram = oldProgram?.reuseTsProgram; const reuseProgram = oldProgram?.reuseTsProgram;
@ -83,9 +81,14 @@ export class NgtscProgram implements api.Program {
retagAllTsFiles(reuseProgram); retagAllTsFiles(reuseProgram);
} }
this.tsProgram = ts.createProgram(this.host.inputFiles, options, this.host, reuseProgram); this.tsProgram = perfRecorder.inPhase(
PerfPhase.TypeScriptProgramCreate,
() => ts.createProgram(this.host.inputFiles, options, this.host, reuseProgram));
this.reuseTsProgram = this.tsProgram; this.reuseTsProgram = this.tsProgram;
perfRecorder.phase(PerfPhase.Unaccounted);
perfRecorder.memory(PerfCheckpoint.TypeScriptProgramCreate);
this.host.postProgramCreationCleanup(); this.host.postProgramCreationCleanup();
// Shim tagging has served its purpose, and tags can now be removed from all `ts.SourceFile`s in // Shim tagging has served its purpose, and tags can now be removed from all `ts.SourceFile`s in
@ -111,7 +114,7 @@ export class NgtscProgram implements api.Program {
let ticket: CompilationTicket; let ticket: CompilationTicket;
if (oldProgram === undefined) { if (oldProgram === undefined) {
ticket = freshCompilationTicket( ticket = freshCompilationTicket(
this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy, this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy, perfRecorder,
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false); /* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
} else { } else {
ticket = incrementalFromCompilerTicket( ticket = incrementalFromCompilerTicket(
@ -120,12 +123,13 @@ export class NgtscProgram implements api.Program {
this.incrementalStrategy, this.incrementalStrategy,
reusedProgramStrategy, reusedProgramStrategy,
modifiedResourceFiles, modifiedResourceFiles,
perfRecorder,
); );
} }
// Create the NgCompiler which will drive the rest of the compilation. // Create the NgCompiler which will drive the rest of the compilation.
this.compiler = NgCompiler.fromTicket(ticket, this.host, this.perfRecorder); this.compiler = NgCompiler.fromTicket(ticket, this.host);
} }
getTsProgram(): ts.Program { getTsProgram(): ts.Program {
@ -138,19 +142,23 @@ export class NgtscProgram implements api.Program {
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken| getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
undefined): readonly ts.Diagnostic[] { undefined): readonly ts.Diagnostic[] {
return this.tsProgram.getOptionsDiagnostics(cancellationToken); return this.compiler.perfRecorder.inPhase(
PerfPhase.TypeScriptDiagnostics,
() => this.tsProgram.getOptionsDiagnostics(cancellationToken));
} }
getTsSyntacticDiagnostics( getTsSyntacticDiagnostics(
sourceFile?: ts.SourceFile|undefined, sourceFile?: ts.SourceFile|undefined,
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] { cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
const ignoredFiles = this.compiler.ignoreForDiagnostics; const ignoredFiles = this.compiler.ignoreForDiagnostics;
let res: readonly ts.Diagnostic[];
if (sourceFile !== undefined) { if (sourceFile !== undefined) {
if (ignoredFiles.has(sourceFile)) { if (ignoredFiles.has(sourceFile)) {
return []; return [];
} }
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken); res = this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
} else { } else {
const diagnostics: ts.Diagnostic[] = []; const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) { for (const sf of this.tsProgram.getSourceFiles()) {
@ -158,20 +166,24 @@ export class NgtscProgram implements api.Program {
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken)); diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
} }
} }
return diagnostics; res = diagnostics;
} }
return res;
});
} }
getTsSemanticDiagnostics( getTsSemanticDiagnostics(
sourceFile?: ts.SourceFile|undefined, sourceFile?: ts.SourceFile|undefined,
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] { cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
const ignoredFiles = this.compiler.ignoreForDiagnostics; const ignoredFiles = this.compiler.ignoreForDiagnostics;
let res: readonly ts.Diagnostic[];
if (sourceFile !== undefined) { if (sourceFile !== undefined) {
if (ignoredFiles.has(sourceFile)) { if (ignoredFiles.has(sourceFile)) {
return []; return [];
} }
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken); res = this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
} else { } else {
const diagnostics: ts.Diagnostic[] = []; const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) { for (const sf of this.tsProgram.getSourceFiles()) {
@ -179,8 +191,10 @@ export class NgtscProgram implements api.Program {
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken)); diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
} }
} }
return diagnostics; res = diagnostics;
} }
return res;
});
} }
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken| getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
@ -235,6 +249,9 @@ export class NgtscProgram implements api.Program {
emitCallback?: api.TsEmitCallback | undefined; emitCallback?: api.TsEmitCallback | undefined;
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback | undefined; mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback | undefined;
}|undefined): ts.EmitResult { }|undefined): ts.EmitResult {
this.compiler.perfRecorder.memory(PerfCheckpoint.PreEmit);
const res = this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptEmit, () => {
const {transformers} = this.compiler.prepareEmit(); const {transformers} = this.compiler.prepareEmit();
const ignoreFiles = this.compiler.ignoreForEmit; const ignoreFiles = this.compiler.ignoreForEmit;
const emitCallback = opts && opts.emitCallback || defaultEmitCallback; const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
@ -265,7 +282,6 @@ export class NgtscProgram implements api.Program {
beforeTransforms.push(...customTransforms.beforeTs); beforeTransforms.push(...customTransforms.beforeTs);
} }
const emitSpan = this.perfRecorder.start('emit');
const emitResults: ts.EmitResult[] = []; const emitResults: ts.EmitResult[] = [];
for (const targetSourceFile of this.tsProgram.getSourceFiles()) { for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
@ -274,10 +290,12 @@ export class NgtscProgram implements api.Program {
} }
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) { if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) {
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSkipSourceFile);
continue; continue;
} }
const fileEmitSpan = this.perfRecorder.start('emitFile', targetSourceFile); this.compiler.perfRecorder.eventCount(PerfEvent.EmitSourceFile);
emitResults.push(emitCallback({ emitResults.push(emitCallback({
targetSourceFile, targetSourceFile,
program: this.tsProgram, program: this.tsProgram,
@ -291,17 +309,22 @@ export class NgtscProgram implements api.Program {
afterDeclarations: afterDeclarationsTransforms, afterDeclarations: afterDeclarationsTransforms,
} as any, } as any,
})); }));
this.perfRecorder.stop(fileEmitSpan);
} }
this.perfRecorder.stop(emitSpan); this.compiler.perfRecorder.memory(PerfCheckpoint.Emit);
if (this.perfTracker !== null && this.options.tracePerformance !== undefined) { // Run the emit, including a custom transformer that will downlevel the Ivy decorators in
this.perfTracker.serializeToFile(this.options.tracePerformance, this.host); // code.
}
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults); return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
});
// Record performance analysis information to disk if we've been asked to do so.
if (this.options.tracePerformance !== undefined) {
const perf = this.compiler.perfRecorder.finalize();
getFileSystem().writeFile(
getFileSystem().resolve(this.options.tracePerformance), JSON.stringify(perf, null, 2));
}
return res;
} }
getIndexedComponents(): Map<DeclarationNode, IndexedComponent> { getIndexedComponents(): Map<DeclarationNode, IndexedComponent> {

View File

@ -13,7 +13,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {IncrementalBuild} from '../../incremental/api'; import {IncrementalBuild} from '../../incremental/api';
import {SemanticDepGraphUpdater, SemanticSymbol} from '../../incremental/semantic_graph'; import {SemanticDepGraphUpdater, SemanticSymbol} from '../../incremental/semantic_graph';
import {IndexingContext} from '../../indexer'; import {IndexingContext} from '../../indexer';
import {PerfRecorder} from '../../perf'; import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost} from '../../reflection'; import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost} from '../../reflection';
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api'; import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api';
import {getSourceFile, isExported} from '../../util/src/typescript'; import {getSourceFile, isExported} from '../../util/src/typescript';
@ -124,6 +124,9 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
this.adopt(priorRecord); this.adopt(priorRecord);
} }
this.perf.eventCount(PerfEvent.SourceFileReuseAnalysis);
this.perf.eventCount(PerfEvent.TraitReuseAnalysis, priorWork.length);
// Skip the rest of analysis, as this file's prior traits are being reused. // Skip the rest of analysis, as this file's prior traits are being reused.
return; return;
} }
@ -359,6 +362,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
TraitState[trait.state]} (expected DETECTED)`); TraitState[trait.state]} (expected DETECTED)`);
} }
this.perf.eventCount(PerfEvent.TraitAnalyze);
// Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does. // Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does.
let result: AnalysisOutput<unknown>; let result: AnalysisOutput<unknown>;
try { try {
@ -509,9 +514,6 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
continue; continue;
} }
const compileSpan = this.perf.start('compileClass', original);
// `trait.resolution` is non-null asserted here because TypeScript does not recognize that // `trait.resolution` is non-null asserted here because TypeScript does not recognize that
// `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that // `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that
// `Readonly` works. // `Readonly` works.
@ -526,7 +528,6 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
} }
const compileMatchRes = compileRes; const compileMatchRes = compileRes;
this.perf.stop(compileSpan);
if (Array.isArray(compileMatchRes)) { if (Array.isArray(compileMatchRes)) {
for (const result of compileMatchRes) { for (const result of compileMatchRes) {
if (!res.some(r => r.name === result.name)) { if (!res.some(r => r.name === result.name)) {

View File

@ -10,6 +10,7 @@ import {ConstantPool} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {DefaultImportRecorder, ImportRewriter} from '../../imports'; import {DefaultImportRecorder, ImportRewriter} from '../../imports';
import {PerfPhase, PerfRecorder} from '../../perf';
import {Decorator, ReflectionHost} from '../../reflection'; import {Decorator, ReflectionHost} from '../../reflection';
import {ImportManager, RecordWrappedNodeExprFn, translateExpression, translateStatement, TranslatorOptions} from '../../translator'; import {ImportManager, RecordWrappedNodeExprFn, translateExpression, translateStatement, TranslatorOptions} from '../../translator';
import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor'; import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor';
@ -33,14 +34,16 @@ interface FileOverviewMeta {
export function ivyTransformFactory( export function ivyTransformFactory(
compilation: TraitCompiler, reflector: ReflectionHost, importRewriter: ImportRewriter, compilation: TraitCompiler, reflector: ReflectionHost, importRewriter: ImportRewriter,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean, defaultImportRecorder: DefaultImportRecorder, perf: PerfRecorder, isCore: boolean,
isClosureCompilerEnabled: boolean): ts.TransformerFactory<ts.SourceFile> { isClosureCompilerEnabled: boolean): ts.TransformerFactory<ts.SourceFile> {
const recordWrappedNodeExpr = createRecorderFn(defaultImportRecorder); const recordWrappedNodeExpr = createRecorderFn(defaultImportRecorder);
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => { return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
return (file: ts.SourceFile): ts.SourceFile => { return (file: ts.SourceFile): ts.SourceFile => {
return transformIvySourceFile( return perf.inPhase(
compilation, context, reflector, importRewriter, file, isCore, isClosureCompilerEnabled, PerfPhase.Compile,
recordWrappedNodeExpr); () => transformIvySourceFile(
compilation, context, reflector, importRewriter, file, isCore,
isClosureCompilerEnabled, recordWrappedNodeExpr));
}; };
}; };
} }

View File

@ -12,7 +12,7 @@ import {CompilationTicket, freshCompilationTicket, incrementalFromDriverTicket,
import {NgCompilerOptions, UnifiedModulesHost} from './core/api'; import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
import {NodeJSFileSystem, setFileSystem} from './file_system'; import {NodeJSFileSystem, setFileSystem} from './file_system';
import {PatchedProgramIncrementalBuildStrategy} from './incremental'; import {PatchedProgramIncrementalBuildStrategy} from './incremental';
import {NOOP_PERF_RECORDER} from './perf'; import {ActivePerfRecorder, NOOP_PERF_RECORDER, PerfPhase} from './perf';
import {untagAllTsFiles} from './shims'; import {untagAllTsFiles} from './shims';
import {OptimizeFor} from './typecheck/api'; import {OptimizeFor} from './typecheck/api';
import {ReusedProgramStrategy} from './typecheck/src/augmented_program'; import {ReusedProgramStrategy} from './typecheck/src/augmented_program';
@ -94,6 +94,13 @@ export class NgTscPlugin implements TscPlugin {
ignoreForDiagnostics: Set<ts.SourceFile>, ignoreForDiagnostics: Set<ts.SourceFile>,
ignoreForEmit: Set<ts.SourceFile>, ignoreForEmit: Set<ts.SourceFile>,
} { } {
// TODO(alxhub): we provide a `PerfRecorder` to the compiler, but because we're not driving the
// compilation, the information captured within it is incomplete, and may not include timings
// for phases such as emit.
//
// Additionally, nothing actually captures the perf results here, so recording stats at all is
// somewhat moot for now :)
const perfRecorder = ActivePerfRecorder.zeroedToNow();
if (this.host === null || this.options === null) { if (this.host === null || this.options === null) {
throw new Error('Lifecycle error: setupCompilation() before wrapHost().'); throw new Error('Lifecycle error: setupCompilation() before wrapHost().');
} }
@ -115,15 +122,15 @@ export class NgTscPlugin implements TscPlugin {
if (oldProgram === undefined || oldDriver === null) { if (oldProgram === undefined || oldDriver === null) {
ticket = freshCompilationTicket( ticket = freshCompilationTicket(
program, this.options, strategy, typeCheckStrategy, program, this.options, strategy, typeCheckStrategy, perfRecorder,
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false); /* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
} else { } else {
strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram); strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram);
ticket = incrementalFromDriverTicket( ticket = incrementalFromDriverTicket(
oldProgram, oldDriver, program, this.options, strategy, typeCheckStrategy, oldProgram, oldDriver, program, this.options, strategy, typeCheckStrategy,
modifiedResourceFiles, false, false); modifiedResourceFiles, perfRecorder, false, false);
} }
this._compiler = NgCompiler.fromTicket(ticket, this.host, NOOP_PERF_RECORDER); this._compiler = NgCompiler.fromTicket(ticket, this.host);
return { return {
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics, ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
ignoreForEmit: this._compiler.ignoreForEmit, ignoreForEmit: this._compiler.ignoreForEmit,
@ -146,6 +153,9 @@ export class NgTscPlugin implements TscPlugin {
} }
createTransformers(): ts.CustomTransformers { createTransformers(): ts.CustomTransformers {
// The plugin consumer doesn't know about our perf tracing system, so we consider the emit phase
// as beginning now.
this.compiler.perfRecorder.phase(PerfPhase.TypeScriptEmit);
return this.compiler.prepareEmit().transformers; return this.compiler.prepareEmit().transformers;
} }
} }

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/incremental:api", "//packages/compiler-cli/src/ngtsc/incremental:api",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/shims",

View File

@ -12,6 +12,7 @@ import * as ts from 'typescript';
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system'; import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
import {Reference, ReferenceEmitter} from '../../imports'; import {Reference, ReferenceEmitter} from '../../imports';
import {IncrementalBuild} from '../../incremental/api'; import {IncrementalBuild} from '../../incremental/api';
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection'; import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope'; import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
import {isShim} from '../../shims'; import {isShim} from '../../shims';
@ -82,7 +83,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>, private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>, private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>,
private readonly componentScopeReader: ComponentScopeReader, private readonly componentScopeReader: ComponentScopeReader,
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry) {} private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry,
private readonly perf: PerfRecorder) {}
getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null { getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null {
const {data} = this.getLatestComponentState(component); const {data} = this.getLatestComponentState(component);
@ -181,6 +183,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
break; break;
} }
return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
const sfPath = absoluteFromSourceFile(sf); const sfPath = absoluteFromSourceFile(sf);
const fileRecord = this.state.get(sfPath)!; const fileRecord = this.state.get(sfPath)!;
@ -205,11 +208,13 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
} }
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null); return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
});
} }
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] { getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] {
this.ensureShimForComponent(component); this.ensureShimForComponent(component);
return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
const sf = component.getSourceFile(); const sf = component.getSourceFile();
const sfPath = absoluteFromSourceFile(sf); const sfPath = absoluteFromSourceFile(sf);
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component); const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
@ -244,6 +249,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return diagnostics.filter( return diagnostics.filter(
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic => (diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
diag !== null && diag.templateId === templateId); diag !== null && diag.templateId === templateId);
});
} }
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null { getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null {
@ -256,7 +262,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
if (engine === null) { if (engine === null) {
return null; return null;
} }
return engine.getGlobalCompletions(context); return this.perf.inPhase(
PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context));
} }
getExpressionCompletionLocation( getExpressionCompletionLocation(
@ -266,7 +273,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
if (engine === null) { if (engine === null) {
return null; return null;
} }
return engine.getExpressionCompletionLocation(ast); return this.perf.inPhase(
PerfPhase.TtcAutocompletion, () => engine.getExpressionCompletionLocation(ast));
} }
invalidateClass(clazz: ts.ClassDeclaration): void { invalidateClass(clazz: ts.ClassDeclaration): void {
@ -318,6 +326,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return; return;
} }
this.perf.eventCount(PerfEvent.ReuseTypeCheckFile);
this.state.set(sfPath, previousResults); this.state.set(sfPath, previousResults);
} }
@ -326,6 +335,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return; return;
} }
this.perf.inPhase(PerfPhase.TcbGeneration, () => {
const host = new WholeProgramTypeCheckingHost(this); const host = new WholeProgramTypeCheckingHost(this);
const ctx = this.newContext(host); const ctx = this.newContext(host);
@ -349,9 +359,11 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
this.updateFromContext(ctx); this.updateFromContext(ctx);
this.isComplete = true; this.isComplete = true;
});
} }
private ensureAllShimsForOneFile(sf: ts.SourceFile): void { private ensureAllShimsForOneFile(sf: ts.SourceFile): void {
this.perf.inPhase(PerfPhase.TcbGeneration, () => {
this.maybeAdoptPriorResultsForFile(sf); this.maybeAdoptPriorResultsForFile(sf);
const sfPath = absoluteFromSourceFile(sf); const sfPath = absoluteFromSourceFile(sf);
@ -362,7 +374,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return; return;
} }
const host = new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this); const host =
new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this);
const ctx = this.newContext(host); const ctx = this.newContext(host);
this.typeCheckAdapter.typeCheck(sf, ctx); this.typeCheckAdapter.typeCheck(sf, ctx);
@ -370,6 +383,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
fileData.isComplete = true; fileData.isComplete = true;
this.updateFromContext(ctx); this.updateFromContext(ctx);
});
} }
private ensureShimForComponent(component: ts.ClassDeclaration): void { private ensureShimForComponent(component: ts.ClassDeclaration): void {
@ -399,7 +413,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
InliningMode.Error; InliningMode.Error;
return new TypeCheckContextImpl( return new TypeCheckContextImpl(
this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector, this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector,
host, inlining); host, inlining, this.perf);
} }
/** /**
@ -428,8 +442,14 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
private updateFromContext(ctx: TypeCheckContextImpl): void { private updateFromContext(ctx: TypeCheckContextImpl): void {
const updates = ctx.finalize(); const updates = ctx.finalize();
return this.perf.inPhase(PerfPhase.TcbUpdateProgram, () => {
if (updates.size > 0) {
this.perf.eventCount(PerfEvent.UpdateTypeCheckProgram);
}
this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental); this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental);
this.priorBuild.recordSuccessfulTypeCheck(this.state); this.priorBuild.recordSuccessfulTypeCheck(this.state);
this.perf.memory(PerfCheckpoint.TtcUpdateProgram);
});
} }
getFileData(path: AbsoluteFsPath): FileTypeCheckingData { getFileData(path: AbsoluteFsPath): FileTypeCheckingData {
@ -450,7 +470,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
if (builder === null) { if (builder === null) {
return null; return null;
} }
return builder.getSymbol(node); return this.perf.inPhase(PerfPhase.TtcSymbol, () => builder.getSymbol(node));
} }
private getOrCreateSymbolBuilder(component: ts.ClassDeclaration): SymbolBuilder|null { private getOrCreateSymbolBuilder(component: ts.ClassDeclaration): SymbolBuilder|null {

View File

@ -12,6 +12,7 @@ import * as ts from 'typescript';
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system'; import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports'; import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, ReflectionHost} from '../../reflection'; import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ImportManager} from '../../translator'; import {ImportManager} from '../../translator';
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api'; import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
@ -179,7 +180,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>, private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
private componentMappingStrategy: ComponentToShimMappingStrategy, private componentMappingStrategy: ComponentToShimMappingStrategy,
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, private refEmitter: ReferenceEmitter, private reflector: ReflectionHost,
private host: TypeCheckingHost, private inlining: InliningMode) { private host: TypeCheckingHost, private inlining: InliningMode, private perf: PerfRecorder) {
if (inlining === InliningMode.Error && config.useInlineTypeConstructors) { if (inlining === InliningMode.Error && config.useInlineTypeConstructors) {
// We cannot use inlining for type checking since this environment does not support it. // We cannot use inlining for type checking since this environment does not support it.
throw new Error(`AssertionError: invalid inlining configuration.`); throw new Error(`AssertionError: invalid inlining configuration.`);
@ -273,6 +274,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node); shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
// Checking this template would be unsupported, so don't try. // Checking this template would be unsupported, so don't try.
this.perf.eventCount(PerfEvent.SkipGenerateTcbNoInline);
return; return;
} }
@ -282,6 +284,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
pipes, pipes,
schemas, schemas,
}; };
this.perf.eventCount(PerfEvent.GenerateTcb);
if (tcbRequiresInline) { if (tcbRequiresInline) {
// This class didn't meet the requirements for external type checking, so generate an inline // This class didn't meet the requirements for external type checking, so generate an inline
// TCB for the class. // TCB for the class.

View File

@ -17,6 +17,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/incremental",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/shims",

View File

@ -14,6 +14,7 @@ import {TestFile} from '../../file_system/testing';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reexport, Reference, ReferenceEmitter, RelativePathStrategy} from '../../imports'; import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reexport, Reference, ReferenceEmitter, RelativePathStrategy} from '../../imports';
import {NOOP_INCREMENTAL_BUILD} from '../../incremental'; import {NOOP_INCREMENTAL_BUILD} from '../../incremental';
import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata'; import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata';
import {NOOP_PERF_RECORDER} from '../../perf';
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope'; import {ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope';
import {makeProgram} from '../../testing'; import {makeProgram} from '../../testing';
@ -515,7 +516,7 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
const templateTypeChecker = new TemplateTypeCheckerImpl( const templateTypeChecker = new TemplateTypeCheckerImpl(
program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host, program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host,
NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry); NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry, NOOP_PERF_RECORDER);
return { return {
templateTypeChecker, templateTypeChecker,
program, program,

View File

@ -10,6 +10,7 @@ import * as ts from 'typescript';
import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system'; import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system';
import {runInEachFileSystem, TestFile} from '../../file_system/testing'; import {runInEachFileSystem, TestFile} from '../../file_system/testing';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports'; import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing'; import {getDeclaration, makeProgram} from '../../testing';
import {getRootDirs} from '../../util/src/typescript'; import {getRootDirs} from '../../util/src/typescript';
@ -74,7 +75,7 @@ TestClass.ngTypeCtor({value: 'test'});
]); ]);
const ctx = new TypeCheckContextImpl( const ctx = new TypeCheckContextImpl(
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
new TestTypeCheckingHost(), InliningMode.InlineOps); new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
const TestClass = const TestClass =
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
const pendingFile = makePendingFile(); const pendingFile = makePendingFile();
@ -113,7 +114,7 @@ TestClass.ngTypeCtor({value: 'test'});
const pendingFile = makePendingFile(); const pendingFile = makePendingFile();
const ctx = new TypeCheckContextImpl( const ctx = new TypeCheckContextImpl(
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
new TestTypeCheckingHost(), InliningMode.InlineOps); new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
const TestClass = const TestClass =
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
ctx.addInlineTypeCtor( ctx.addInlineTypeCtor(
@ -158,7 +159,7 @@ TestClass.ngTypeCtor({value: 'test'});
const pendingFile = makePendingFile(); const pendingFile = makePendingFile();
const ctx = new TypeCheckContextImpl( const ctx = new TypeCheckContextImpl(
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
new TestTypeCheckingHost(), InliningMode.InlineOps); new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
const TestClass = const TestClass =
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
ctx.addInlineTypeCtor( ctx.addInlineTypeCtor(

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/incremental",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/shims",
"//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/typecheck",

View File

@ -9,6 +9,7 @@
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core'; import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core';
import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api'; import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental'; import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
import {ActivePerfRecorder} from '@angular/compiler-cli/src/ngtsc/perf';
import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
import * as ts from 'typescript/lib/tsserverlibrary'; import * as ts from 'typescript/lib/tsserverlibrary';
@ -44,6 +45,10 @@ export class CompilerFactory {
// Only resource files have changed since the last NgCompiler was created. // Only resource files have changed since the last NgCompiler was created.
const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles); const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles);
this.compiler = NgCompiler.fromTicket(ticket, this.adapter); this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
} else {
// The previous NgCompiler is being reused, but we still want to reset its performance
// tracker to capture only the operations that are needed to service the current request.
this.compiler.perfRecorder.reset();
} }
return this.compiler; return this.compiler;
@ -52,11 +57,12 @@ export class CompilerFactory {
let ticket: CompilationTicket; let ticket: CompilationTicket;
if (this.compiler === null || this.lastKnownProgram === null) { if (this.compiler === null || this.lastKnownProgram === null) {
ticket = freshCompilationTicket( ticket = freshCompilationTicket(
program, this.options, this.incrementalStrategy, this.programStrategy, true, true); program, this.options, this.incrementalStrategy, this.programStrategy,
/* perfRecorder */ null, true, true);
} else { } else {
ticket = incrementalFromCompilerTicket( ticket = incrementalFromCompilerTicket(
this.compiler, program, this.incrementalStrategy, this.programStrategy, this.compiler, program, this.incrementalStrategy, this.programStrategy,
modifiedResourceFiles); modifiedResourceFiles, /* perfRecorder */ null);
} }
this.compiler = NgCompiler.fromTicket(ticket, this.adapter); this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
this.lastKnownProgram = program; this.lastKnownProgram = program;

View File

@ -8,6 +8,7 @@
import {AbsoluteSourceSpan, AST, BindingPipe, LiteralPrimitive, MethodCall, ParseSourceSpan, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstNode, TmplAstReference, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler'; import {AbsoluteSourceSpan, AST, BindingPipe, LiteralPrimitive, MethodCall, ParseSourceSpan, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstNode, TmplAstReference, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system'; import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
import {PerfPhase} from '@angular/compiler-cli/src/ngtsc/perf';
import {DirectiveSymbol, ShimLocation, SymbolKind, TemplateTypeChecker, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; import {DirectiveSymbol, ShimLocation, SymbolKind, TemplateTypeChecker, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
import {ExpressionIdentifier, hasExpressionIdentifier} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments'; import {ExpressionIdentifier, hasExpressionIdentifier} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -66,6 +67,7 @@ export class ReferencesAndRenameBuilder {
getRenameInfo(filePath: string, position: number): getRenameInfo(filePath: string, position: number):
Omit<ts.RenameInfoSuccess, 'kind'|'kindModifiers'>|ts.RenameInfoFailure { Omit<ts.RenameInfoSuccess, 'kind'|'kindModifiers'>|ts.RenameInfoFailure {
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler); const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
// We could not get a template at position so we assume the request came from outside the // We could not get a template at position so we assume the request came from outside the
// template. // template.
@ -75,7 +77,10 @@ export class ReferencesAndRenameBuilder {
const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position); const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
if (allTargetDetails === null) { if (allTargetDetails === null) {
return {canRename: false, localizedErrorMessage: 'Could not find template node at position.'}; return {
canRename: false,
localizedErrorMessage: 'Could not find template node at position.',
};
} }
const {templateTarget} = allTargetDetails[0]; const {templateTarget} = allTargetDetails[0];
const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position); const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
@ -89,10 +94,12 @@ export class ReferencesAndRenameBuilder {
fullDisplayName: text, fullDisplayName: text,
triggerSpan: span, triggerSpan: span,
}; };
});
} }
findRenameLocations(filePath: string, position: number): readonly ts.RenameLocation[]|undefined { findRenameLocations(filePath: string, position: number): readonly ts.RenameLocation[]|undefined {
this.ttc.generateAllTypeCheckBlocks(); this.ttc.generateAllTypeCheckBlocks();
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler); const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
// We could not get a template at position so we assume the request came from outside the // We could not get a template at position so we assume the request came from outside the
// template. // template.
@ -106,6 +113,7 @@ export class ReferencesAndRenameBuilder {
} }
return this.findRenameLocationsAtTemplatePosition(templateInfo, position); return this.findRenameLocationsAtTemplatePosition(templateInfo, position);
});
} }
private findRenameLocationsAtTemplatePosition(templateInfo: TemplateInfo, position: number): private findRenameLocationsAtTemplatePosition(templateInfo: TemplateInfo, position: number):
@ -148,6 +156,7 @@ export class ReferencesAndRenameBuilder {
findRenameLocationsAtTypescriptPosition( findRenameLocationsAtTypescriptPosition(
filePath: string, position: number, filePath: string, position: number,
requestOrigin: RequestOrigin): readonly ts.RenameLocation[]|undefined { requestOrigin: RequestOrigin): readonly ts.RenameLocation[]|undefined {
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
let originalNodeText: string; let originalNodeText: string;
if (requestOrigin.kind === RequestKind.TypeScript) { if (requestOrigin.kind === RequestKind.TypeScript) {
originalNodeText = requestOrigin.requestNode.getText(); originalNodeText = requestOrigin.requestNode.getText();
@ -188,15 +197,19 @@ export class ReferencesAndRenameBuilder {
} }
} }
return Array.from(entries.values()); return Array.from(entries.values());
});
} }
getReferencesAtPosition(filePath: string, position: number): ts.ReferenceEntry[]|undefined { getReferencesAtPosition(filePath: string, position: number): ts.ReferenceEntry[]|undefined {
this.ttc.generateAllTypeCheckBlocks(); this.ttc.generateAllTypeCheckBlocks();
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler); const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
if (templateInfo === undefined) { if (templateInfo === undefined) {
return this.getReferencesAtTypescriptPosition(filePath, position); return this.getReferencesAtTypescriptPosition(filePath, position);
} }
return this.getReferencesAtTemplatePosition(templateInfo, position); return this.getReferencesAtTemplatePosition(templateInfo, position);
});
} }
private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number): private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number):