Skip to content

Commit

Permalink
perf(compiler-cli): refactor the performance tracing infrastructure (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
alxhub authored and TeriGlover committed Apr 5, 2021
1 parent 54577d5 commit ba9371e
Show file tree
Hide file tree
Showing 44 changed files with 1,183 additions and 633 deletions.
1 change: 1 addition & 0 deletions packages/bazel/src/ngc-wrapped/BUILD.bazel
Expand Up @@ -16,6 +16,7 @@ ts_library(
],
deps = [
"//packages/compiler-cli",
"//packages/compiler-cli/src/ngtsc/perf",
"@npm//@bazel/typescript",
"@npm//@types/node",
"@npm//tsickle",
Expand Down
12 changes: 12 additions & 0 deletions packages/bazel/src/ngc-wrapped/index.ts
Expand Up @@ -7,6 +7,7 @@
*/

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 * as fs from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -515,6 +516,12 @@ function gatherDiagnosticsForInputsOnly(
options: ng.CompilerOptions, bazelOpts: BazelOptions,
ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] {
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)[] = [];
// These checks mirror ts.getPreEmitDiagnostics, with the important
// exception of avoiding b/30708240, which is that if you call
Expand All @@ -529,6 +536,11 @@ function gatherDiagnosticsForInputsOnly(
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
diagnostics.push(...tsProgram.getSemanticDiagnostics(sf));
}

if (ngProgram instanceof ng.NgtscProgram) {
ngProgram.compiler.perfRecorder.phase(previousPhase);
}

if (!diagnostics.length) {
// only gather the angular diagnostics if we have no diagnostics
// in any other files.
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-cli/index.ts
Expand Up @@ -22,5 +22,6 @@ export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'

export {ngToTsDiagnostic} from './src/transformers/util';
export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
export {NgtscProgram} from './src/ngtsc/program';

setFileSystem(new NodeJSFileSystem());
17 changes: 11 additions & 6 deletions packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ConstantPool} from '@angular/compiler';
import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf';
import * as ts from 'typescript';

import {ParsedConfiguration} from '../../..';
Expand Down Expand Up @@ -89,7 +90,7 @@ export class DecorationAnalyzer {
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
evaluator =
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);
injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader);
Expand All @@ -104,7 +105,8 @@ export class DecorationAnalyzer {
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
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.
// clang-format off
Expand All @@ -117,23 +119,26 @@ export class DecorationAnalyzer {
// 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
// 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>,
// clang-format on
// Pipe handler must be before injectable handler in list so pipe factories are printed
// before injectable factories (so injectable factories can delegate to them)
new PipeDecoratorHandler(
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(
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(
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
this.refEmitter,
/* 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);
migrations: Migration[] = [
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel
Expand Up @@ -18,6 +18,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/indexer",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/routing",
"//packages/compiler-cli/src/ngtsc/scope",
Expand Down
4 changes: 3 additions & 1 deletion packages/compiler-cli/src/ngtsc/annotations/src/component.ts
Expand Up @@ -18,6 +18,7 @@ import {extractSemanticTypeParameters, isArrayEqual, isReferenceEqual, SemanticD
import {IndexingContext} from '../../indexer';
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
Expand Down Expand Up @@ -208,7 +209,7 @@ export class ComponentDecoratorHandler implements
private depTracker: DependencyTracker|null,
private injectableRegistry: InjectableClassRegistry,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean) {}
private annotateForClosureCompiler: boolean, private perf: PerfRecorder) {}

private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
private elementSchemaRegistry = new DomElementSchemaRegistry();
Expand Down Expand Up @@ -309,6 +310,7 @@ export class ComponentDecoratorHandler implements
analyze(
node: ClassDeclaration, decorator: Readonly<Decorator>,
flags: HandlerFlags = HandlerFlags.NONE): AnalysisOutput<ComponentAnalysisData> {
this.perf.eventCount(PerfEvent.AnalyzeComponent);
const containingFile = node.getSourceFile().fileName;
this.literalCache.delete(decorator);

Expand Down
5 changes: 4 additions & 1 deletion packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
Expand Up @@ -16,6 +16,7 @@ import {areTypeParametersEqual, extractSemanticTypeParameters, isArrayEqual, isS
import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateGuardMeta} from '../../metadata';
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
Expand Down Expand Up @@ -180,7 +181,7 @@ export class DirectiveDecoratorHandler implements
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean,
private compileUndecoratedClassesWithAngularFeatures: boolean) {}
private compileUndecoratedClassesWithAngularFeatures: boolean, private perf: PerfRecorder) {}

readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = DirectiveDecoratorHandler.name;
Expand Down Expand Up @@ -211,6 +212,8 @@ export class DirectiveDecoratorHandler implements
return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]};
}

this.perf.eventCount(PerfEvent.AnalyzeDirective);

const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
flags, this.annotateForClosureCompiler);
Expand Down
Expand Up @@ -12,6 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder} from '../../imports';
import {InjectableClassRegistry} from '../../metadata';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';

Expand All @@ -34,7 +35,7 @@ export class InjectableDecoratorHandler implements
constructor(
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
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.
*
Expand Down Expand Up @@ -64,6 +65,8 @@ export class InjectableDecoratorHandler implements

analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
AnalysisOutput<InjectableHandlerData> {
this.perf.eventCount(PerfEvent.AnalyzeInjectable);

const meta = extractInjectableMetadata(node, decorator, this.reflector);
const decorators = this.reflector.getDecoratorsOfDeclaration(node);

Expand Down
6 changes: 5 additions & 1 deletion packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts
Expand Up @@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports'
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph';
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing';
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
Expand Down Expand Up @@ -131,7 +132,8 @@ export class NgModuleDecoratorHandler implements
private factoryTracker: FactoryTracker|null,
private defaultImportRecorder: DefaultImportRecorder,
private annotateForClosureCompiler: boolean,
private injectableRegistry: InjectableClassRegistry, private localeId?: string) {}
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
private localeId?: string) {}

readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = NgModuleDecoratorHandler.name;
Expand All @@ -154,6 +156,8 @@ export class NgModuleDecoratorHandler implements

analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
AnalysisOutput<NgModuleAnalysis> {
this.perf.eventCount(PerfEvent.AnalyzeNgModule);

const name = node.name.text;
if (decorator.args === null || decorator.args.length > 1) {
throw new FatalDiagnosticError(
Expand Down
6 changes: 5 additions & 1 deletion packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts
Expand Up @@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference} from '../../imports';
import {SemanticSymbol} from '../../incremental/semantic_graph';
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
Expand Down Expand Up @@ -55,7 +56,8 @@ export class PipeDecoratorHandler implements
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private defaultImportRecorder: DefaultImportRecorder,
private injectableRegistry: InjectableClassRegistry, private isCore: boolean) {}
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
private perf: PerfRecorder) {}

readonly precedence = HandlerPrecedence.PRIMARY;
readonly name = PipeDecoratorHandler.name;
Expand All @@ -78,6 +80,8 @@ export class PipeDecoratorHandler implements

analyze(clazz: ClassDeclaration, decorator: Readonly<Decorator>):
AnalysisOutput<PipeHandlerData> {
this.perf.eventCount(PerfEvent.AnalyzePipe);

const name = clazz.name.text;
const type = wrapTypeReference(this.reflector, clazz);
const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(clazz));
Expand Down
Expand Up @@ -20,6 +20,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/incremental:api",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/testing",
Expand Down
Expand Up @@ -16,6 +16,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing';
Expand All @@ -41,7 +42,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null);
const moduleResolver =
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 metaRegistry = new LocalMetadataRegistry();
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
Expand Down Expand Up @@ -80,6 +81,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
injectableRegistry,
/* semanticDepGraphUpdater */ null,
/* annotateForClosureCompiler */ false,
NOOP_PERF_RECORDER,
);
return {reflectionHost, handler};
}
Expand Down
Expand Up @@ -13,6 +13,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing';
Expand Down Expand Up @@ -171,7 +172,7 @@ runInEachFileSystem(() => {
NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false,
/*semanticDepGraphUpdater*/ null,
/*annotateForClosureCompiler*/ false,
/*detectUndecoratedClassesWithAngularFeatures*/ false);
/*detectUndecoratedClassesWithAngularFeatures*/ false, NOOP_PERF_RECORDER);

const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);

Expand Down
Expand Up @@ -10,6 +10,7 @@ import {absoluteFrom} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
import {InjectableClassRegistry} from '../../metadata';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing';
import {InjectableDecoratorHandler} from '../src/injectable';
Expand Down Expand Up @@ -70,7 +71,7 @@ function setupHandler(errorOnDuplicateProv: boolean) {
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
const handler = new InjectableDecoratorHandler(
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 ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
if (ɵprov === undefined) {
Expand Down
Expand Up @@ -14,6 +14,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../perf';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing';
Expand Down Expand Up @@ -71,7 +72,8 @@ runInEachFileSystem(() => {
const handler = new NgModuleDecoratorHandler(
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
/* 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 =
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
const detected =
Expand Down

0 comments on commit ba9371e

Please sign in to comment.