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,42 +345,66 @@ 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++;
}
}
livePerfRecorder.eventCount(PerfEvent.InputDtsFile, dtsFileCount);
livePerfRecorder.eventCount(PerfEvent.InputTsFile, nonDtsFileCount);
} }
private updateWithChangedResources(changedResources: Set<string>): void { get perfRecorder(): ActivePerfRecorder {
if (this.compilation === null) { return this.livePerfRecorder;
// Analysis hasn't happened yet, so no update is necessary - any changes to resources will be }
// captured by the inital analysis pass itself.
return;
}
this.resourceManager.invalidate(); private updateWithChangedResources(
changedResources: Set<string>, perfRecorder: ActivePerfRecorder): void {
this.livePerfRecorder = perfRecorder;
this.delegatingPerfRecorder.target = perfRecorder;
const classesToUpdate = new Set<DeclarationNode>(); perfRecorder.inPhase(PerfPhase.ResourceUpdate, () => {
for (const resourceFile of changedResources) { if (this.compilation === null) {
for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) { // Analysis hasn't happened yet, so no update is necessary - any changes to resources will
classesToUpdate.add(templateClass); // be captured by the inital analysis pass itself.
return;
} }
for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) { this.resourceManager.invalidate();
classesToUpdate.add(styleClass);
}
}
for (const clazz of classesToUpdate) { const classesToUpdate = new Set<DeclarationNode>();
this.compilation.traitCompiler.updateResources(clazz); for (const resourceFile of changedResources) {
if (!ts.isClassDeclaration(clazz)) { for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
continue; classesToUpdate.add(templateClass);
}
for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
classesToUpdate.add(styleClass);
}
} }
this.compilation.templateTypeChecker.invalidateClass(clazz); for (const clazz of classesToUpdate) {
} this.compilation.traitCompiler.updateResources(clazz);
if (!ts.isClassDeclaration(clazz)) {
continue;
}
this.compilation.templateTypeChecker.invalidateClass(clazz);
}
});
} }
/** /**
@ -481,33 +528,28 @@ export class NgCompiler {
if (this.compilation !== null) { if (this.compilation !== null) {
return; return;
} }
this.compilation = this.makeCompilation();
const analyzeSpan = this.perfRecorder.start('analyze'); await this.perfRecorder.inPhase(PerfPhase.Analysis, async () => {
const promises: Promise<void>[] = []; this.compilation = this.makeCompilation();
for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile) { const promises: Promise<void>[] = [];
continue; for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile) {
continue;
}
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
this.scanForMwp(sf);
if (analysisPromise !== undefined) {
promises.push(analysisPromise);
}
} }
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf); await Promise.all(promises);
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(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) {
promises.push(analysisPromise);
}
}
await Promise.all(promises); this.perfRecorder.memory(PerfCheckpoint.Analysis);
this.resolveCompilation(this.compilation.traitCompiler);
this.perfRecorder.stop(analyzeSpan); });
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;
}
this.compilation.traitCompiler.analyzeSync(sf);
this.scanForMwp(sf);
} }
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
this.compilation.traitCompiler.analyzeSync(sf);
this.scanForMwp(sf);
this.perfRecorder.stop(analyzeFileSpan);
}
this.perfRecorder.stop(analyzeSpan);
this.resolveCompilation(this.compilation.traitCompiler); this.perfRecorder.memory(PerfCheckpoint.Analysis);
this.resolveCompilation(this.compilation.traitCompiler);
});
} }
private resolveCompilation(traitCompiler: TraitCompiler): void { private resolveCompilation(traitCompiler: TraitCompiler): void {
traitCompiler.resolve(); this.perfRecorder.inPhase(PerfPhase.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,26 +101,28 @@ export class ImportGraph {
} }
private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> { private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> {
const imports = new Set<ts.SourceFile>(); return this.perf.inPhase(PerfPhase.CycleDetection, () => {
// Look through the source file for import and export statements. const imports = new Set<ts.SourceFile>();
for (const stmt of sf.statements) { // Look through the source file for import and export statements.
if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) || for (const stmt of sf.statements) {
stmt.moduleSpecifier === undefined) { if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) ||
continue; stmt.moduleSpecifier === undefined) {
} continue;
}
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier); const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
if (symbol === undefined || symbol.valueDeclaration === undefined) { if (symbol === undefined || symbol.valueDeclaration === undefined) {
// No symbol could be found to skip over this import/export. // No symbol could be found to skip over this import/export.
continue; continue;
}
const moduleFile = symbol.valueDeclaration;
if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
// Record this local import.
imports.add(moduleFile);
}
} }
const moduleFile = symbol.valueDeclaration; return imports;
if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) { });
// Record this local import.
imports.add(moduleFile);
}
}
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,118 +44,122 @@ 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 {
// Initialize the state of the current build based on the previous one. return perf.inPhase(PerfPhase.Reconciliation, () => {
let state: PendingBuildState; // Initialize the state of the current build based on the previous one.
if (oldDriver.state.kind === BuildStateKind.Pending) { let state: PendingBuildState;
// The previous build never made it past the pending state. Reuse it as the starting state for if (oldDriver.state.kind === BuildStateKind.Pending) {
// this build. // The previous build never made it past the pending state. Reuse it as the starting state
state = oldDriver.state; // for this build.
} else { state = oldDriver.state;
let priorGraph: SemanticDepGraph|null = null;
if (oldDriver.state.lastGood !== null) {
priorGraph = oldDriver.state.lastGood.semanticDepGraph;
}
// The previous build was successfully analyzed. `pendingEmit` is the only state carried
// forward into this build.
state = {
kind: BuildStateKind.Pending,
pendingEmit: oldDriver.state.pendingEmit,
pendingTypeCheckEmit: oldDriver.state.pendingTypeCheckEmit,
changedResourcePaths: new Set<AbsoluteFsPath>(),
changedTsPaths: new Set<string>(),
lastGood: oldDriver.state.lastGood,
semanticDepGraphUpdater: new SemanticDepGraphUpdater(priorGraph),
};
}
// Merge the freshly modified resource files with any prior ones.
if (modifiedResourceFiles !== null) {
for (const resFile of modifiedResourceFiles) {
state.changedResourcePaths.add(absoluteFrom(resFile));
}
}
// 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`.
// 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
// leaking memory.
// All files in the old program, for easy detection of changes.
const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles());
// Assume all the old files were deleted to begin with. Only TS files are tracked.
const deletedTsPaths = new Set<string>(tsOnlyFiles(oldProgram).map(sf => sf.fileName));
for (const newFile of newProgram.getSourceFiles()) {
if (!newFile.isDeclarationFile) {
// This file exists in the new program, so remove it from `deletedTsPaths`.
deletedTsPaths.delete(newFile.fileName);
}
if (oldFiles.has(newFile)) {
// This file hasn't changed; no need to look at it further.
continue;
}
// The file has changed since the last successful build. The appropriate reaction depends on
// what kind of file it is.
if (!newFile.isDeclarationFile) {
// It's a .ts file, so track it as a change.
state.changedTsPaths.add(newFile.fileName);
} else { } else {
// It's a .d.ts file. Currently the compiler does not do a great job of tracking let priorGraph: SemanticDepGraph|null = null;
// dependencies on .d.ts files, so bail out of incremental builds here and do a full build. if (oldDriver.state.lastGood !== null) {
// This usually only happens if something in node_modules changes. priorGraph = oldDriver.state.lastGood.semanticDepGraph;
return IncrementalDriver.fresh(newProgram); }
}
}
// The next step is to remove any deleted files from the state. // The previous build was successfully analyzed. `pendingEmit` is the only state carried
for (const filePath of deletedTsPaths) { // forward into this build.
state.pendingEmit.delete(filePath); state = {
state.pendingTypeCheckEmit.delete(filePath); kind: BuildStateKind.Pending,
pendingEmit: oldDriver.state.pendingEmit,
// Even if the file doesn't exist in the current compilation, it still might have been changed pendingTypeCheckEmit: oldDriver.state.pendingTypeCheckEmit,
// in a previous one, so delete it from the set of changed TS files, just in case. changedResourcePaths: new Set<AbsoluteFsPath>(),
state.changedTsPaths.delete(filePath); changedTsPaths: new Set<string>(),
} lastGood: oldDriver.state.lastGood,
semanticDepGraphUpdater: new SemanticDepGraphUpdater(priorGraph),
// Now, changedTsPaths contains physically changed TS paths. Use the previous program's logical };
// dependency graph to determine logically changed files.
const depGraph = new FileDependencyGraph();
// If a previous compilation exists, use its dependency graph to determine the set of logically
// changed files.
let logicalChanges: Set<string>|null = null;
if (state.lastGood !== null) {
// Extract the set of logically changed files. At the same time, this operation populates the
// current (fresh) dependency graph with information about those files which have not
// logically changed.
logicalChanges = depGraph.updateWithPhysicalChanges(
state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths,
state.changedResourcePaths);
for (const fileName of state.changedTsPaths) {
logicalChanges.add(fileName);
} }
// Any logically changed files need to be re-emitted. Most of the time this would happen // Merge the freshly modified resource files with any prior ones.
// regardless because the new dependency graph would _also_ identify the file as stale. if (modifiedResourceFiles !== null) {
// However there are edge cases such as removing a component from an NgModule without adding for (const resFile of modifiedResourceFiles) {
// it to another one, where the previous graph identifies the file as logically changed, but state.changedResourcePaths.add(absoluteFrom(resFile));
// the new graph (which does not have that edge) fails to identify that the file should be }
// re-emitted.
for (const change of logicalChanges) {
state.pendingEmit.add(change);
state.pendingTypeCheckEmit.add(change);
} }
}
// `state` now reflects the initial pending state of the current compilation. // 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`.
// 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 leaking memory.
return new IncrementalDriver(state, depGraph, logicalChanges); // All files in the old program, for easy detection of changes.
const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles());
// Assume all the old files were deleted to begin with. Only TS files are tracked.
const deletedTsPaths = new Set<string>(tsOnlyFiles(oldProgram).map(sf => sf.fileName));
for (const newFile of newProgram.getSourceFiles()) {
if (!newFile.isDeclarationFile) {
// This file exists in the new program, so remove it from `deletedTsPaths`.
deletedTsPaths.delete(newFile.fileName);
}
if (oldFiles.has(newFile)) {
// This file hasn't changed; no need to look at it further.
continue;
}
// The file has changed since the last successful build. The appropriate reaction depends on
// what kind of file it is.
if (!newFile.isDeclarationFile) {
// It's a .ts file, so track it as a change.
state.changedTsPaths.add(newFile.fileName);
} else {
// 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. This usually only happens if something in node_modules changes.
return IncrementalDriver.fresh(newProgram);
}
}
// The next step is to remove any deleted files from the state.
for (const filePath of deletedTsPaths) {
state.pendingEmit.delete(filePath);
state.pendingTypeCheckEmit.delete(filePath);
// Even if the file doesn't exist in the current compilation, it still might have been
// changed in a previous one, so delete it from the set of changed TS files, just in case.
state.changedTsPaths.delete(filePath);
}
perf.eventCount(PerfEvent.SourceFilePhysicalChange, state.changedTsPaths.size);
// Now, changedTsPaths contains physically changed TS paths. Use the previous program's
// logical dependency graph to determine logically changed files.
const depGraph = new FileDependencyGraph();
// If a previous compilation exists, use its dependency graph to determine the set of
// logically changed files.
let logicalChanges: Set<string>|null = null;
if (state.lastGood !== null) {
// Extract the set of logically changed files. At the same time, this operation populates
// the current (fresh) dependency graph with information about those files which have not
// logically changed.
logicalChanges = depGraph.updateWithPhysicalChanges(
state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths,
state.changedResourcePaths);
perf.eventCount(PerfEvent.SourceFileLogicalChange, logicalChanges.size);
for (const fileName of state.changedTsPaths) {
logicalChanges.add(fileName);
}
// Any logically changed files need to be re-emitted. Most of the time this would happen
// regardless because the new dependency graph would _also_ identify the file as stale.
// However there are edge cases such as removing a component from an NgModule without adding
// it to another one, where the previous graph identifies the file as logically changed, but
// the new graph (which does not have that edge) fails to identify that the file should be
// re-emitted.
for (const change of logicalChanges) {
state.pendingEmit.add(change);
state.pendingTypeCheckEmit.add(change);
}
}
// `state` now reflects the initial pending state of the current compilation.
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,49 +142,59 @@ 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[] {
const ignoredFiles = this.compiler.ignoreForDiagnostics; return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
if (sourceFile !== undefined) { const ignoredFiles = this.compiler.ignoreForDiagnostics;
if (ignoredFiles.has(sourceFile)) { let res: readonly ts.Diagnostic[];
return []; if (sourceFile !== undefined) {
} if (ignoredFiles.has(sourceFile)) {
return [];
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
} else {
const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) {
if (!ignoredFiles.has(sf)) {
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
} }
res = this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
} else {
const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) {
if (!ignoredFiles.has(sf)) {
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
}
}
res = diagnostics;
} }
return 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[] {
const ignoredFiles = this.compiler.ignoreForDiagnostics; return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
if (sourceFile !== undefined) { const ignoredFiles = this.compiler.ignoreForDiagnostics;
if (ignoredFiles.has(sourceFile)) { let res: readonly ts.Diagnostic[];
return []; if (sourceFile !== undefined) {
} if (ignoredFiles.has(sourceFile)) {
return [];
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
} else {
const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) {
if (!ignoredFiles.has(sf)) {
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
} }
res = this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
} else {
const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) {
if (!ignoredFiles.has(sf)) {
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
}
}
res = diagnostics;
} }
return diagnostics; return res;
} });
} }
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken| getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
@ -235,73 +249,82 @@ 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 {
const {transformers} = this.compiler.prepareEmit(); this.compiler.perfRecorder.memory(PerfCheckpoint.PreEmit);
const ignoreFiles = this.compiler.ignoreForEmit;
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
const writeFile: ts.WriteFileCallback = const res = this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptEmit, () => {
(fileName: string, data: string, writeByteOrderMark: boolean, const {transformers} = this.compiler.prepareEmit();
onError: ((message: string) => void)|undefined, const ignoreFiles = this.compiler.ignoreForEmit;
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => { const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
if (sourceFiles !== undefined) {
// Record successful writes for any `ts.SourceFile` (that's not a declaration file) const writeFile: ts.WriteFileCallback =
// that's an input to this write. (fileName: string, data: string, writeByteOrderMark: boolean,
for (const writtenSf of sourceFiles) { onError: ((message: string) => void)|undefined,
if (writtenSf.isDeclarationFile) { sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => {
continue; if (sourceFiles !== undefined) {
// Record successful writes for any `ts.SourceFile` (that's not a declaration file)
// that's an input to this write.
for (const writtenSf of sourceFiles) {
if (writtenSf.isDeclarationFile) {
continue;
}
this.compiler.incrementalDriver.recordSuccessfulEmit(writtenSf);
} }
this.compiler.incrementalDriver.recordSuccessfulEmit(writtenSf);
} }
} this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); };
};
const customTransforms = opts && opts.customTransformers; const customTransforms = opts && opts.customTransformers;
const beforeTransforms = transformers.before || []; const beforeTransforms = transformers.before || [];
const afterDeclarationsTransforms = transformers.afterDeclarations; const afterDeclarationsTransforms = transformers.afterDeclarations;
if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) { if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
beforeTransforms.push(...customTransforms.beforeTs); beforeTransforms.push(...customTransforms.beforeTs);
}
const emitSpan = this.perfRecorder.start('emit');
const emitResults: ts.EmitResult[] = [];
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
continue;
} }
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) { const emitResults: ts.EmitResult[] = [];
continue;
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
continue;
}
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) {
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSkipSourceFile);
continue;
}
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSourceFile);
emitResults.push(emitCallback({
targetSourceFile,
program: this.tsProgram,
host: this.host,
options: this.options,
emitOnlyDtsFiles: false,
writeFile,
customTransformers: {
before: beforeTransforms,
after: customTransforms && customTransforms.afterTs,
afterDeclarations: afterDeclarationsTransforms,
} as any,
}));
} }
const fileEmitSpan = this.perfRecorder.start('emitFile', targetSourceFile); this.compiler.perfRecorder.memory(PerfCheckpoint.Emit);
emitResults.push(emitCallback({
targetSourceFile, // Run the emit, including a custom transformer that will downlevel the Ivy decorators in
program: this.tsProgram, // code.
host: this.host, return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
options: this.options, });
emitOnlyDtsFiles: false,
writeFile, // Record performance analysis information to disk if we've been asked to do so.
customTransformers: { if (this.options.tracePerformance !== undefined) {
before: beforeTransforms, const perf = this.compiler.perfRecorder.finalize();
after: customTransforms && customTransforms.afterTs, getFileSystem().writeFile(
afterDeclarations: afterDeclarationsTransforms, getFileSystem().resolve(this.options.tracePerformance), JSON.stringify(perf, null, 2));
} as any,
}));
this.perfRecorder.stop(fileEmitSpan);
} }
return res;
this.perfRecorder.stop(emitSpan);
if (this.perfTracker !== null && this.options.tracePerformance !== undefined) {
this.perfTracker.serializeToFile(this.options.tracePerformance, this.host);
}
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
} }
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,19 +183,60 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
break; break;
} }
const sfPath = absoluteFromSourceFile(sf); return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
const fileRecord = this.state.get(sfPath)!; const sfPath = absoluteFromSourceFile(sf);
const fileRecord = this.state.get(sfPath)!;
const typeCheckProgram = this.typeCheckingStrategy.getProgram(); const typeCheckProgram = this.typeCheckingStrategy.getProgram();
const diagnostics: (ts.Diagnostic|null)[] = []; const diagnostics: (ts.Diagnostic|null)[] = [];
if (fileRecord.hasInlines) { if (fileRecord.hasInlines) {
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath); const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map( diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceManager))); diag => convertDiagnostic(diag, fileRecord.sourceManager)));
} }
for (const [shimPath, shimRecord] of fileRecord.shimData) {
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
diagnostics.push(...shimRecord.genesisDiagnostics);
for (const templateData of shimRecord.templates.values()) {
diagnostics.push(...templateData.templateDiagnostics);
}
}
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
});
}
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] {
this.ensureShimForComponent(component);
return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
const sf = component.getSourceFile();
const sfPath = absoluteFromSourceFile(sf);
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
const fileRecord = this.getFileData(sfPath);
if (!fileRecord.shimData.has(shimPath)) {
return [];
}
const templateId = fileRecord.sourceManager.getTemplateId(component);
const shimRecord = fileRecord.shimData.get(shimPath)!;
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
const diagnostics: (TemplateDiagnostic|null)[] = [];
if (shimRecord.hasInlines) {
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
}
for (const [shimPath, shimRecord] of fileRecord.shimData) {
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath); const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map( diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceManager))); diag => convertDiagnostic(diag, fileRecord.sourceManager)));
@ -202,48 +245,11 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
for (const templateData of shimRecord.templates.values()) { for (const templateData of shimRecord.templates.values()) {
diagnostics.push(...templateData.templateDiagnostics); diagnostics.push(...templateData.templateDiagnostics);
} }
}
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null); return diagnostics.filter(
} (diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
diag !== null && diag.templateId === templateId);
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] { });
this.ensureShimForComponent(component);
const sf = component.getSourceFile();
const sfPath = absoluteFromSourceFile(sf);
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
const fileRecord = this.getFileData(sfPath);
if (!fileRecord.shimData.has(shimPath)) {
return [];
}
const templateId = fileRecord.sourceManager.getTemplateId(component);
const shimRecord = fileRecord.shimData.get(shimPath)!;
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
const diagnostics: (TemplateDiagnostic|null)[] = [];
if (shimRecord.hasInlines) {
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
}
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
diagnostics.push(...shimRecord.genesisDiagnostics);
for (const templateData of shimRecord.templates.values()) {
diagnostics.push(...templateData.templateDiagnostics);
}
return diagnostics.filter(
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
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,50 +335,55 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
return; return;
} }
const host = new WholeProgramTypeCheckingHost(this); this.perf.inPhase(PerfPhase.TcbGeneration, () => {
const ctx = this.newContext(host); const host = new WholeProgramTypeCheckingHost(this);
const ctx = this.newContext(host);
for (const sf of this.originalProgram.getSourceFiles()) { for (const sf of this.originalProgram.getSourceFiles()) {
if (sf.isDeclarationFile || isShim(sf)) { if (sf.isDeclarationFile || isShim(sf)) {
continue; continue;
}
this.maybeAdoptPriorResultsForFile(sf);
const sfPath = absoluteFromSourceFile(sf);
const fileData = this.getFileData(sfPath);
if (fileData.isComplete) {
continue;
}
this.typeCheckAdapter.typeCheck(sf, ctx);
fileData.isComplete = true;
} }
this.updateFromContext(ctx);
this.isComplete = true;
});
}
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);
const fileData = this.getFileData(sfPath); const fileData = this.getFileData(sfPath);
if (fileData.isComplete) { if (fileData.isComplete) {
continue; // All data for this file is present and accounted for already.
return;
} }
const host =
new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this);
const ctx = this.newContext(host);
this.typeCheckAdapter.typeCheck(sf, ctx); this.typeCheckAdapter.typeCheck(sf, ctx);
fileData.isComplete = true; fileData.isComplete = true;
}
this.updateFromContext(ctx); this.updateFromContext(ctx);
this.isComplete = true; });
}
private ensureAllShimsForOneFile(sf: ts.SourceFile): void {
this.maybeAdoptPriorResultsForFile(sf);
const sfPath = absoluteFromSourceFile(sf);
const fileData = this.getFileData(sfPath);
if (fileData.isComplete) {
// All data for this file is present and accounted for already.
return;
}
const host = new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this);
const ctx = this.newContext(host);
this.typeCheckAdapter.typeCheck(sf, ctx);
fileData.isComplete = true;
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();
this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental); return this.perf.inPhase(PerfPhase.TcbUpdateProgram, () => {
this.priorBuild.recordSuccessfulTypeCheck(this.state); if (updates.size > 0) {
this.perf.eventCount(PerfEvent.UpdateTypeCheckProgram);
}
this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental);
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,46 +67,53 @@ 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 {
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler); return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
// We could not get a template at position so we assume the request came from outside the const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
// template. // We could not get a template at position so we assume the request came from outside the
if (templateInfo === undefined) { // template.
return this.tsLS.getRenameInfo(filePath, position); if (templateInfo === undefined) {
} return this.tsLS.getRenameInfo(filePath, position);
}
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,
const {templateTarget} = allTargetDetails[0]; localizedErrorMessage: 'Could not find template node at position.',
const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position); };
if (templateTextAndSpan === null) { }
return {canRename: false, localizedErrorMessage: 'Could not determine template node text.'}; const {templateTarget} = allTargetDetails[0];
} const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
const {text, span} = templateTextAndSpan; if (templateTextAndSpan === null) {
return { return {canRename: false, localizedErrorMessage: 'Could not determine template node text.'};
canRename: true, }
displayName: text, const {text, span} = templateTextAndSpan;
fullDisplayName: text, return {
triggerSpan: span, canRename: true,
}; displayName: text,
fullDisplayName: text,
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();
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler); return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
// We could not get a template at position so we assume the request came from outside the const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
// template. // We could not get a template at position so we assume the request came from outside the
if (templateInfo === undefined) { // template.
const requestNode = this.getTsNodeAtPosition(filePath, position); if (templateInfo === undefined) {
if (requestNode === null) { const requestNode = this.getTsNodeAtPosition(filePath, position);
return undefined; if (requestNode === null) {
return undefined;
}
const requestOrigin: TypeScriptRequest = {kind: RequestKind.TypeScript, requestNode};
return this.findRenameLocationsAtTypescriptPosition(filePath, position, requestOrigin);
} }
const requestOrigin: TypeScriptRequest = {kind: RequestKind.TypeScript, requestNode};
return this.findRenameLocationsAtTypescriptPosition(filePath, position, requestOrigin);
}
return this.findRenameLocationsAtTemplatePosition(templateInfo, position); return this.findRenameLocationsAtTemplatePosition(templateInfo, position);
});
} }
private findRenameLocationsAtTemplatePosition(templateInfo: TemplateInfo, position: number): private findRenameLocationsAtTemplatePosition(templateInfo: TemplateInfo, position: number):
@ -148,55 +156,60 @@ 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 {
let originalNodeText: string; return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
if (requestOrigin.kind === RequestKind.TypeScript) { let originalNodeText: string;
originalNodeText = requestOrigin.requestNode.getText(); if (requestOrigin.kind === RequestKind.TypeScript) {
} else { originalNodeText = requestOrigin.requestNode.getText();
const templateNodeText = } else {
getRenameTextAndSpanAtPosition(requestOrigin.requestNode, requestOrigin.position); const templateNodeText =
if (templateNodeText === null) { getRenameTextAndSpanAtPosition(requestOrigin.requestNode, requestOrigin.position);
if (templateNodeText === null) {
return undefined;
}
originalNodeText = templateNodeText.text;
}
const locations = this.tsLS.findRenameLocations(
filePath, position, /*findInStrings*/ false, /*findInComments*/ false);
if (locations === undefined) {
return undefined; return undefined;
} }
originalNodeText = templateNodeText.text;
}
const locations = this.tsLS.findRenameLocations( const entries: Map<string, ts.RenameLocation> = new Map();
filePath, position, /*findInStrings*/ false, /*findInComments*/ false); for (const location of locations) {
if (locations === undefined) { // TODO(atscott): Determine if a file is a shim file in a more robust way and make the API
return undefined; // available in an appropriate location.
} if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) {
const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText);
const entries: Map<string, ts.RenameLocation> = new Map(); // There is no template node whose text matches the original rename request. Bail on
for (const location of locations) { // renaming completely rather than providing incomplete results.
// TODO(atscott): Determine if a file is a shim file in a more robust way and make the API if (entry === null) {
// available in an appropriate location. return undefined;
if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) { }
const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText); entries.set(createLocationKey(entry), entry);
// There is no template node whose text matches the original rename request. Bail on } else {
// renaming completely rather than providing incomplete results. // Ensure we only allow renaming a TS result with matching text
if (entry === null) { const refNode = this.getTsNodeAtPosition(location.fileName, location.textSpan.start);
return undefined; if (refNode === null || refNode.getText() !== originalNodeText) {
return undefined;
}
entries.set(createLocationKey(location), location);
} }
entries.set(createLocationKey(entry), entry);
} else {
// Ensure we only allow renaming a TS result with matching text
const refNode = this.getTsNodeAtPosition(location.fileName, location.textSpan.start);
if (refNode === null || refNode.getText() !== originalNodeText) {
return undefined;
}
entries.set(createLocationKey(location), location);
} }
} 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();
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
if (templateInfo === undefined) { return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
return this.getReferencesAtTypescriptPosition(filePath, position); const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
} if (templateInfo === undefined) {
return this.getReferencesAtTemplatePosition(templateInfo, position); return this.getReferencesAtTypescriptPosition(filePath, position);
}
return this.getReferencesAtTemplatePosition(templateInfo, position);
});
} }
private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number): private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number):