From f415faa67d42e675778eb518ca72da8af799014c Mon Sep 17 00:00:00 2001 From: James Daniels Date: Wed, 22 May 2019 18:02:42 -0700 Subject: [PATCH] Operator rename, more configuration, and docs --- docs/performance/getting-started.md | 132 +++++++++++++++++++++------- src/performance/performance.ts | 41 +++++++-- 2 files changed, 135 insertions(+), 38 deletions(-) diff --git a/docs/performance/getting-started.md b/docs/performance/getting-started.md index c1b7740ed..e38abd933 100644 --- a/docs/performance/getting-started.md +++ b/docs/performance/getting-started.md @@ -1,8 +1,40 @@ # Getting started with Performance Monitoring -## Basic usage +## Automatic page load tracing -**TBD** basic explainer +Understand your Angular application's real-world performance with [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon). Performance Monitoring automatically provides a trace for **page load** when you add `AngularFirePerformanceModule` into your App Module's imports. + +```ts +import { AngularFireModule } from '@angular/fire'; +import { AngularFirePerformanceModule } from '@angular/fire/performance'; +import { environment } from '../environments/environment'; + +@NgModule({ + imports: [ + BrowserModule, + AngularFireModule.initializeApp(environment.firebase), + AngularFirePerformanceModule, + ... + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} +``` + +The page load trace breaks down into the following default metrics: + +* [first paint traces](https://firebase.google.com/docs/perf-mon/automatic-web#first-paint) — measure the time between when the user navigates to a page and when any visual change happens +* [first contentful paint traces](https://firebase.google.com/docs/perf-mon/automatic-web#contentful-paint) — measure the time between when a user navigates to a page and when meaningful content displays, like an image or text +* [domInteractive traces](https://firebase.google.com/docs/perf-mon/automatic-web#domInteractive) — measure the time between when the user navigates to a page and when the page is considered interactive for the user +* [domContentLoadedEventEnd traces](https://firebase.google.com/docs/perf-mon/automatic-web#domContentLoaded) — measure the time between when the user navigates to a page and when the initial HTML document is completely loaded and parsed +* [loadEventEnd traces](https://firebase.google.com/docs/perf-mon/automatic-web#loadEventEnd) — measure the time between when the user navigates to the page and when the current document's load event completes +* [first input delay traces](https://firebase.google.com/docs/perf-mon/automatic-web#input-delay) — measure the time between when the user interacts with a page and when the browser is able to respond to that input +* **Angular specific traces** - measure the time needed for `ApplicationRef.isStable` to be true, an important metric to track if you're concerned about solving Zone.js issues for proper functionality of NGSW and Server Side Rendering + +## Manual traces + +You can inject `AngularFirePerformance` to perform manual traces on Observables. ```ts constructor(private afp: AngularFirePerformance, private afs: AngularFirestore) {} @@ -12,62 +44,102 @@ ngOnInit() { .collection('articles', ref => ref.orderBy('publishedAt', 'desc')) .snapshotChanges() .pipe( + // measure the amount of time between the Observable being subscribed to and first emission (or completion) this.afp.trace('getArticles'), map(articles => ...) ); } ``` -`trace(name:string)` will trace the time it takes for your observable to either complete or emit it's first value. Beyond the basic trace there are three other operators: - -### `traceComplete(name:string)` +### `trace(name:string)` -Traces the observable until the completion. +The most basic operator, `trace` will measure the amount of time it takes for your observable to either complete or emit its first value. Beyond the basic trace there are several other operators: ### `traceUntil(name:string, test: (T) => Boolean)` -Traces the observable until the first emission that passes the provided test. +Trace the observable until the first emission that passes the provided test. -### `traceFirst(name: string)` +### `traceWhile(name:string, test: (T) => Boolean)` -Traces the observable until the first emission or the first emission that matches the provided test. +Trace the observable until the first emission that fails the provided test. -## Advanced usage +### `traceUntilLast(name:string)` -### `trace$(...) => Observable` +Trace the observable until completion. -`(name:string)` -`(name:string, options: TraceOptions)` +### `traceUntilFirst(name: string)` -Observable version of `firebase/perf`'s `.trace` method; the basis for `AngularFirePerfomance`'s pipes. +Traces the observable until the first emission. -`.subscribe()` is equivalent to calling `.start()` -`.unsubscribe()` is equivalent to calling `.stop()` +## Advanced usage -### `TraceOptions` +### Configuration via Dependency Injection -**TBD explain how each option is used by `.trace$`** +By default, `AngularFirePerformanceModule` traces your Angular application's time to `ApplicationRef.isStable`. `isStable` is an important metric to track if you're concerned about proper functionality of NGSW and Server Side Rendering. If you want to opt-out of the tracing of this metric use the `AUTOMATICALLY_TRACE_CORE_NG_METRICS` injection token: ```ts -export const gameUpdate = (state: State, input: Input): State => ( - afp.traceComplete('game', { - pluckMetrics: ['score'], - attribute$: { user: this.afAuth.user } - }), - whileNotGameOver(state, input), - processInput(state, input), - updateState(state) -); +import { NgModule } from '@angular/core'; +import { AngularFirePerformanceModule, AUTOMATICALLY_TRACE_CORE_NG_METRICS } from '@angular/fire/functions'; + +@NgModule({ + imports: [ + ... + AngularFirePerformanceModule, + ... + ], + ... + providers: [ + { provide: AUTOMATICALLY_TRACE_CORE_NG_METRICS, useValue: false } + ] +}) +export class AppModule {} ``` +Similarly, setting `INSTRUMENTATION_ENABLED` or `DATA_COLLECTION_ENABLED` to false disable all automatic and custom traces respectively. + +### Get at an observable form of trace + +`trace$(name:string)` provides an observable version of `firebase/perf`'s `.trace` method; the basis for `AngularFirePerfomance`'s pipes. + +`.subscribe()` is equivalent to calling `.start()` +`.unsubscribe()` is equivalent to calling `.stop()` + +### Using `TraceOptions` to collect additional metrics + +`TraceOptions` can be provided to the aformentioned operators to collect custom metrics and attributes on your traces: + ```ts -export type TraceOptions = { - pluckMetrics?: [string], - pluckAttributes?: [string], +type TraceOptions = { metrics?: { [key:string]: number }, attributes?: { [key:string]: string }, attribute$?: { [key:string]: Observable }, incrementMetric$: { [key:string]: Observable }, metric$?: { [key:string]: Observable } }; +``` + +#### Usage: + +```ts +const articleLength$ = this.articles.pipe( + map(actions => actions.length) +); + +const articleSize$ = this.articles.pipe( + map(actions => actions.reduce((sum, a) => sum += JSON.stringify(a.payload.doc.data()).length)) +) + +this.articles = afs.collection('articles') + .collection('articles', ref => ref.orderBy('publishedAt', 'desc')) + .snapshotChanges() + .pipe( + this.afp.trace('getArticles', { + attributes: { gitSha: '1d277f823ad98dd739fb86e9a6c440aa8237ff3a' }, + metrics: { something: 42 }, + metrics$: { count: articleLength$, size: articleSize$ }, + attributes$: { user: this.afAuth.user }, + incrementMetric$: { buttonClicks: fromEvent(button, 'click') } + }), + share() + ); ``` \ No newline at end of file diff --git a/src/performance/performance.ts b/src/performance/performance.ts index 1a79a548a..f1c909565 100644 --- a/src/performance/performance.ts +++ b/src/performance/performance.ts @@ -4,13 +4,15 @@ import { first, tap } from 'rxjs/operators'; import { performance } from 'firebase/app'; export const AUTOMATICALLY_TRACE_CORE_NG_METRICS = new InjectionToken('angularfire2.performance.auto_trace'); +export const INSTRUMENTATION_ENABLED = new InjectionToken('angularfire2.performance.instrumentationEnabled'); +export const DATA_COLLECTION_ENABLED = new InjectionToken('angularfire2.performance.dataCollectionEnabled'); export type TraceOptions = { - metrics: {[key:string]: number}, - attributes?:{[key:string]:string}, - attribute$?:{[key:string]:Observable}, - incrementMetric$:{[key:string]: Observable}, - metric$?:{[key:string]: Observable} + metrics?: {[key:string]: number}, + attributes?: {[key:string]:string}, + attribute$?: {[key:string]:Observable}, + incrementMetric$?: {[key:string]: Observable}, + metric$?: {[key:string]: Observable} }; @Injectable() @@ -20,17 +22,22 @@ export class AngularFirePerformance { constructor( @Optional() @Inject(AUTOMATICALLY_TRACE_CORE_NG_METRICS) automaticallyTraceCoreNgMetrics:boolean|null, + @Optional() @Inject(INSTRUMENTATION_ENABLED) instrumentationEnabled:boolean|null, + @Optional() @Inject(DATA_COLLECTION_ENABLED) dataCollectionEnabled:boolean|null, appRef: ApplicationRef, private zone: NgZone ) { this.performance = zone.runOutsideAngular(() => performance()); + + if (instrumentationEnabled == false) { this.performance.instrumentationEnabled = false } + if (dataCollectionEnabled == false) { this.performance.dataCollectionEnabled = false } if (automaticallyTraceCoreNgMetrics != false) { - // TODO detirmine more built in metrics + // TODO determine more built in metrics appRef.isStable.pipe( - this.traceComplete('isStable'), + this.traceUntilLast('isStable'), first(it => it) ).subscribe(); @@ -73,7 +80,14 @@ export class AngularFirePerformance { ) }; - traceComplete = (name:string, options?: TraceOptions) => (source$: Observable) => { + traceWhile = (name:string, test: (a:T) => boolean, options?: TraceOptions) => (source$: Observable) => { + const traceSubscription = this.trace$(name, options).subscribe(); + return source$.pipe( + tap(a => { if (!test(a)) { traceSubscription.unsubscribe() }}) + ) + }; + + traceUntilLast= (name:string, options?: TraceOptions) => (source$: Observable) => { const traceSubscription = this.trace$(name, options).subscribe(); return source$.pipe( tap( @@ -84,6 +98,17 @@ export class AngularFirePerformance { ) }; + traceUntilFirst = (name:string, options?: TraceOptions) => (source$: Observable) => { + const traceSubscription = this.trace$(name, options).subscribe(); + return source$.pipe( + tap( + () => traceSubscription.unsubscribe(), + () => {}, + () => {} + ) + ) + }; + trace = (name:string, options?: TraceOptions) => (source$: Observable) => { const traceSubscription = this.trace$(name, options).subscribe(); return source$.pipe(