Skip to content

Commit

Permalink
Operator rename, more configuration, and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesdaniels committed May 23, 2019
1 parent 1d277f8 commit f415faa
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 38 deletions.
132 changes: 102 additions & 30 deletions docs/performance/getting-started.md
Original file line number Diff line number Diff line change
@@ -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) {}
Expand All @@ -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<void>`
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<string> },
incrementMetric$: { [key:string]: Observable<number|void|null|undefined> },
metric$?: { [key:string]: Observable<number> }
};
```

#### 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()
);
```
41 changes: 33 additions & 8 deletions src/performance/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { first, tap } from 'rxjs/operators';
import { performance } from 'firebase/app';

export const AUTOMATICALLY_TRACE_CORE_NG_METRICS = new InjectionToken<boolean>('angularfire2.performance.auto_trace');
export const INSTRUMENTATION_ENABLED = new InjectionToken<boolean>('angularfire2.performance.instrumentationEnabled');
export const DATA_COLLECTION_ENABLED = new InjectionToken<boolean>('angularfire2.performance.dataCollectionEnabled');

export type TraceOptions = {
metrics: {[key:string]: number},
attributes?:{[key:string]:string},
attribute$?:{[key:string]:Observable<string>},
incrementMetric$:{[key:string]: Observable<number|void|null|undefined>},
metric$?:{[key:string]: Observable<number>}
metrics?: {[key:string]: number},
attributes?: {[key:string]:string},
attribute$?: {[key:string]:Observable<string>},
incrementMetric$?: {[key:string]: Observable<number|void|null|undefined>},
metric$?: {[key:string]: Observable<number>}
};

@Injectable()
Expand All @@ -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();

Expand Down Expand Up @@ -73,7 +80,14 @@ export class AngularFirePerformance {
)
};

traceComplete = <T=any>(name:string, options?: TraceOptions) => (source$: Observable<T>) => {
traceWhile = <T=any>(name:string, test: (a:T) => boolean, options?: TraceOptions) => (source$: Observable<T>) => {
const traceSubscription = this.trace$(name, options).subscribe();
return source$.pipe(
tap(a => { if (!test(a)) { traceSubscription.unsubscribe() }})
)
};

traceUntilLast= <T=any>(name:string, options?: TraceOptions) => (source$: Observable<T>) => {
const traceSubscription = this.trace$(name, options).subscribe();
return source$.pipe(
tap(
Expand All @@ -84,6 +98,17 @@ export class AngularFirePerformance {
)
};

traceUntilFirst = <T=any>(name:string, options?: TraceOptions) => (source$: Observable<T>) => {
const traceSubscription = this.trace$(name, options).subscribe();
return source$.pipe(
tap(
() => traceSubscription.unsubscribe(),
() => {},
() => {}
)
)
};

trace = <T=any>(name:string, options?: TraceOptions) => (source$: Observable<T>) => {
const traceSubscription = this.trace$(name, options).subscribe();
return source$.pipe(
Expand Down

0 comments on commit f415faa

Please sign in to comment.