mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(core): introduce new DI profiling event (#60158)
This change introduces a new DI profiler event: InjectorToCreateInstanceEvent. This new event allows us to measure DI tokens instantiation time. PR Close #60158
This commit is contained in:
committed by
Miles Malerba
parent
d92fba084b
commit
809b5b4596
@@ -12,6 +12,7 @@ import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {OnDestroy} from '../interface/lifecycle_hooks';
|
||||
import {Type} from '../interface/type';
|
||||
import {
|
||||
emitInjectorToCreateInstanceEvent,
|
||||
emitInstanceCreatedByInjectorEvent,
|
||||
emitProviderConfiguredEvent,
|
||||
InjectorProfilerContext,
|
||||
@@ -434,6 +435,7 @@ export class R3Injector extends EnvironmentInjector {
|
||||
// these are the only providers that do not go through the value hydration logic
|
||||
// where this event would normally be emitted from.
|
||||
if (isValueProvider(provider)) {
|
||||
emitInjectorToCreateInstanceEvent(token);
|
||||
emitInstanceCreatedByInjectorEvent(provider.useValue);
|
||||
}
|
||||
|
||||
@@ -478,6 +480,7 @@ export class R3Injector extends EnvironmentInjector {
|
||||
|
||||
if (ngDevMode) {
|
||||
runInInjectorProfilerContext(this, token as Type<T>, () => {
|
||||
emitInjectorToCreateInstanceEvent(token);
|
||||
record.value = record.factory!();
|
||||
emitInstanceCreatedByInjectorEvent(record.value);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import {Injector} from '../../di/injector';
|
||||
import {EnvironmentInjector} from '../../di/r3_injector';
|
||||
import {Type} from '../../interface/type';
|
||||
import {assertDefined, throwError} from '../../util/assert';
|
||||
import {assertTNode, assertTNodeForLView} from '../assert';
|
||||
import {assertTNodeForLView} from '../assert';
|
||||
import {getComponentDef} from '../def_getters';
|
||||
import {getNodeInjectorLView, getNodeInjectorTNode, NodeInjector} from '../di';
|
||||
import {TNode} from '../interfaces/node';
|
||||
@@ -220,6 +220,12 @@ function handleInstanceCreatedByInjectorEvent(
|
||||
): void {
|
||||
const {value} = data;
|
||||
|
||||
// It might happen that a DI token is requested but there is no corresponding value.
|
||||
// The InstanceCreatedByInjectorEvent will be still emitted in this case (to mirror the InjectorToCreateInstanceEvent) but we don't want to do any particular processing for those situations.
|
||||
if (data.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getDIResolver(context.injector) === null) {
|
||||
throwError('An InjectorCreatedInstance event must be run within an injection context.');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import type {FactoryProvider} from '../../di';
|
||||
import type {FactoryProvider, ProviderToken} from '../../di';
|
||||
import {resolveForwardRef} from '../../di/forward_ref';
|
||||
import {InjectionToken} from '../../di/injection_token';
|
||||
import type {Injector} from '../../di/injector';
|
||||
@@ -41,6 +41,11 @@ export const enum InjectorProfilerEventType {
|
||||
* Emits when an effect is created.
|
||||
*/
|
||||
EffectCreated,
|
||||
|
||||
/**
|
||||
* Emits when an Angular DI system is about to create an instance corresponding to a given token.
|
||||
*/
|
||||
InjectorToCreateInstanceEvent,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +73,12 @@ export interface InjectedServiceEvent {
|
||||
service: InjectedService;
|
||||
}
|
||||
|
||||
export interface InjectorToCreateInstanceEvent {
|
||||
type: InjectorProfilerEventType.InjectorToCreateInstanceEvent;
|
||||
context: InjectorProfilerContext;
|
||||
token: ProviderToken<unknown>;
|
||||
}
|
||||
|
||||
export interface InjectorCreatedInstanceEvent {
|
||||
type: InjectorProfilerEventType.InstanceCreatedByInjector;
|
||||
context: InjectorProfilerContext;
|
||||
@@ -92,6 +103,7 @@ export interface EffectCreatedEvent {
|
||||
|
||||
export type InjectorProfilerEvent =
|
||||
| InjectedServiceEvent
|
||||
| InjectorToCreateInstanceEvent
|
||||
| InjectorCreatedInstanceEvent
|
||||
| ProviderConfiguredEvent
|
||||
| EffectCreatedEvent;
|
||||
@@ -254,6 +266,22 @@ export function emitProviderConfiguredEvent(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the injector profiler when an instance corresponding to a given token is about to be created be an injector. Note that
|
||||
* the injector associated with this emission can be accessed by using getDebugInjectContext()
|
||||
*
|
||||
* @param instance an object created by an injector
|
||||
*/
|
||||
export function emitInjectorToCreateInstanceEvent(token: ProviderToken<unknown>): void {
|
||||
!ngDevMode && throwError('Injector profiler should never be called in production mode');
|
||||
|
||||
injectorProfiler({
|
||||
type: InjectorProfilerEventType.InjectorToCreateInstanceEvent,
|
||||
context: getInjectorProfilerContext(),
|
||||
token: token,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the injector profiler with the instance that was created. Note that
|
||||
* the injector associated with this emission can be accessed by using getDebugInjectContext()
|
||||
|
||||
@@ -19,6 +19,7 @@ import {noSideEffects} from '../util/closure';
|
||||
|
||||
import {assertDirectiveDef, assertNodeInjector, assertTNodeForLView} from './assert';
|
||||
import {
|
||||
emitInjectorToCreateInstanceEvent,
|
||||
emitInstanceCreatedByInjectorEvent,
|
||||
InjectorProfilerContext,
|
||||
runInInjectorProfilerContext,
|
||||
@@ -521,11 +522,9 @@ function lookupTokenUsingNodeInjector<T>(
|
||||
new NodeInjector(getCurrentTNode() as TElementNode, getLView()),
|
||||
token as Type<T>,
|
||||
() => {
|
||||
emitInjectorToCreateInstanceEvent(token);
|
||||
value = bloomHash(flags);
|
||||
|
||||
if (value != null) {
|
||||
emitInstanceCreatedByInjectorEvent(value);
|
||||
}
|
||||
emitInstanceCreatedByInjectorEvent(value);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -737,14 +736,15 @@ export function getNodeInjectable(
|
||||
const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders);
|
||||
factory.resolving = true;
|
||||
|
||||
// tData indexes mirror the concrete instances in its corresponding LView.
|
||||
// lView[index] here is either the injectable instance itself or a factory,
|
||||
// therefore tData[index] is the constructor of that injectable or a
|
||||
// definition object that contains the constructor in a `.type` field.
|
||||
const token =
|
||||
(tData[index] as DirectiveDef<unknown> | ComponentDef<unknown>).type || tData[index];
|
||||
|
||||
let prevInjectContext: InjectorProfilerContext | undefined;
|
||||
if (ngDevMode) {
|
||||
// tData indexes mirror the concrete instances in its corresponding LView.
|
||||
// lView[index] here is either the injectable instace itself or a factory,
|
||||
// therefore tData[index] is the constructor of that injectable or a
|
||||
// definition object that contains the constructor in a `.type` field.
|
||||
const token =
|
||||
(tData[index] as DirectiveDef<unknown> | ComponentDef<unknown>).type || tData[index];
|
||||
const injector = new NodeInjector(tNode, lView);
|
||||
prevInjectContext = setInjectorProfilerContext({injector, token});
|
||||
}
|
||||
@@ -760,6 +760,8 @@ export function getNodeInjectable(
|
||||
"Because flags do not contain `SkipSelf' we expect this to always succeed.",
|
||||
);
|
||||
try {
|
||||
ngDevMode && emitInjectorToCreateInstanceEvent(token);
|
||||
|
||||
value = lView[index] = factory.factory(undefined, tData, lView, tNode);
|
||||
|
||||
ngDevMode && emitInstanceCreatedByInjectorEvent(value);
|
||||
|
||||
@@ -37,6 +37,7 @@ import {setupFrameworkInjectorProfiler} from '@angular/core/src/render3/debug/fr
|
||||
import {
|
||||
getInjectorProfilerContext,
|
||||
InjectedServiceEvent,
|
||||
InjectorToCreateInstanceEvent,
|
||||
InjectorCreatedInstanceEvent,
|
||||
InjectorProfilerEvent,
|
||||
InjectorProfilerEventType,
|
||||
@@ -57,6 +58,7 @@ import {Router, RouterModule, RouterOutlet} from '@angular/router';
|
||||
|
||||
describe('setProfiler', () => {
|
||||
let injectEvents: InjectedServiceEvent[] = [];
|
||||
let aboutToCreateEvents: InjectorToCreateInstanceEvent[] = [];
|
||||
let createEvents: InjectorCreatedInstanceEvent[] = [];
|
||||
let providerConfiguredEvents: ProviderConfiguredEvent[] = [];
|
||||
|
||||
@@ -69,6 +71,7 @@ describe('setProfiler', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
injectEvents = [];
|
||||
aboutToCreateEvents = [];
|
||||
createEvents = [];
|
||||
providerConfiguredEvents = [];
|
||||
|
||||
@@ -80,20 +83,22 @@ describe('setProfiler', () => {
|
||||
context: getInjectorProfilerContext(),
|
||||
type,
|
||||
});
|
||||
}
|
||||
if (type === InjectorProfilerEventType.InstanceCreatedByInjector) {
|
||||
} else if (type === InjectorProfilerEventType.InstanceCreatedByInjector) {
|
||||
createEvents.push({
|
||||
instance: injectorProfilerEvent.instance,
|
||||
context: getInjectorProfilerContext(),
|
||||
type,
|
||||
});
|
||||
}
|
||||
if (type === InjectorProfilerEventType.ProviderConfigured) {
|
||||
} else if (type === InjectorProfilerEventType.ProviderConfigured) {
|
||||
providerConfiguredEvents.push({
|
||||
providerRecord: injectorProfilerEvent.providerRecord,
|
||||
context: getInjectorProfilerContext(),
|
||||
type,
|
||||
});
|
||||
} else if (type === InjectorProfilerEventType.InjectorToCreateInstanceEvent) {
|
||||
aboutToCreateEvents.push(injectorProfilerEvent);
|
||||
} else {
|
||||
throw new Error('Unexpected event type: ' + type);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -139,6 +144,11 @@ describe('setProfiler', () => {
|
||||
createEvents,
|
||||
(event) => event.instance.value === myComp,
|
||||
);
|
||||
const componentAboutToCreateEvent = searchForProfilerEvent<InjectorToCreateInstanceEvent>(
|
||||
aboutToCreateEvents,
|
||||
(event) => event.token === MyComponent,
|
||||
);
|
||||
expect(componentAboutToCreateEvent).toBeDefined();
|
||||
expect(componentCreateEvent).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user