Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(performance): AngularFire Performance Monitoring #2064

Merged
merged 14 commits into from
May 24, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ Firebase offers two cloud-based, client-accessible database solutions that suppo

- [Getting started with Firebase Messaging](docs/messaging/messaging.md)

### Monitor your application performance in production

- [Getting started with Performance Monitoring](docs/performance/getting-started.md)

### Directly call Cloud Functions

- [Getting started with Callable Functions](docs/functions/functions.md)
Expand Down
16 changes: 9 additions & 7 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@
# @next `gcloud builds submit --substitutions=TAG_NAME="v1.2.3-rc.1"`
# @latest `gcloud builds submit --substitutions=TAG_NAME="v1.2.3"`
steps:
- name: 'gcr.io/cloud-builders/yarn'
entrypoint: 'bash'
- name: node:lts
entrypoint: bash
args: ["./tools/build.sh"]
env:
- 'TAG_NAME=$TAG_NAME'
- 'SHORT_SHA=$SHORT_SHA'

- name: 'gcr.io/cloud-builders/yarn'
entrypoint: 'bash'
- name: node:lts
entrypoint: bash
args: ["./tools/test.sh"]

- name: 'gcr.io/cloud-builders/npm'
entrypoint: 'bash'
- name: node:lts
entrypoint: bash
env: ['TAG_NAME=$TAG_NAME']
args: ["./tools/release.sh"]
secretEnv: ['NPM_TOKEN']

secrets:
- kmsKeyName: projects/angularfire/locations/global/keyRings/cloud-build/cryptoKeys/cloud-build
secretEnv:
NPM_TOKEN: CiQAwtE8WoPa1sNqAQJZ1WMODuJooVmO6zihz2hAZOfUmDsgogUSTQCq8yp8qgltY+8jWpAR9GuS1JaVhd+fTVRilqLtdi2yXSdiDPTzLhZ+30bMlAOcoc0PxhCBn3JOpn8H1xshX+mG8yK7xog2Uq+CLVx/
NPM_TOKEN: CiQAwtE8WoPa1sNqAQJZ1WMODuJooVmO6zihz2hAZOfUmDsgogUSTQCq8yp8qgltY+8jWpAR9GuS1JaVhd+fTVRilqLtdi2yXSdiDPTzLhZ+30bMlAOcoc0PxhCBn3JOpn8H1xshX+mG8yK7xog2Uq+CLVx/

timeout: 1200s
145 changes: 145 additions & 0 deletions docs/performance/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Getting started with Performance Monitoring

## Automatic page load tracing

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) {}

ngOnInit() {
this.articles = afs.collection('articles')
.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'),
jamesdaniels marked this conversation as resolved.
Show resolved Hide resolved
map(articles => ...)
);
}
```

### `trace(name:string)`

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)`

Trace the observable until the first emission that passes the provided test.

### `traceWhile(name:string, test: (T) => Boolean)`

Trace the observable until the first emission that fails the provided test.

### `traceUntilLast(name:string)`

Trace the observable until completion.

### `traceUntilFirst(name: string)`

Traces the observable until the first emission.

## Advanced usage

### Configuration via Dependency Injection

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
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
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()
);
```
2 changes: 2 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = function(config) {
'node_modules/firebase/firebase-database.js',
'node_modules/firebase/firebase-firestore.js',
'node_modules/firebase/firebase-functions.js',
'node_modules/firebase/firebase-performance.js',
'node_modules/firebase/firebase-storage.js',
'dist/packages-dist/bundles/core.umd.{js,map}',
'dist/packages-dist/bundles/auth.umd.{js,map}',
Expand All @@ -38,6 +39,7 @@ module.exports = function(config) {
'dist/packages-dist/bundles/firestore.umd.{js,map}',
'dist/packages-dist/bundles/functions.umd.{js,map}',
'dist/packages-dist/bundles/storage.umd.{js,map}',
'dist/packages-dist/bundles/performance.umd.{js,map}',
'dist/packages-dist/bundles/database-deprecated.umd.{js,map}',
'dist/packages-dist/bundles/test.umd.{js,map}',
],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"homepage": "https://github.com/angular/angularfire2#readme",
"dependencies": {
"@angular-devkit/architect": "^0.800.0-rc.4",
"@angular-devkit/architect": "^0.800.0-rc.4 || >=8.0.0 <9 || 9.0.0-0",
"@angular-devkit/core": ">=6.0.0 <9 || 9.0.0-0",
"@angular-devkit/schematics": ">=6.0.0 <9 || 9.0.0-0",
"@angular/common": ">=6.0.0 <9 || 9.0.0-0",
Expand Down
1 change: 1 addition & 0 deletions src/core/firebase.app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class FirebaseApp {
auth: () => FirebaseAuth;
database: (databaseURL?: string) => FirebaseDatabase;
messaging: () => FirebaseMessaging;
performance: () => any; // SEMVER: once >= 6 import performance.Performance
storage: (storageBucket?: string) => FirebaseStorage;
delete: () => Promise<void>;
firestore: () => FirebaseFirestore;
Expand Down
1 change: 1 addition & 0 deletions src/performance/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './performance.spec';
1 change: 1 addition & 0 deletions src/performance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './public_api';
30 changes: 30 additions & 0 deletions src/performance/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@angular/fire/performance",
"version": "ANGULARFIRE2_VERSION",
"description": "The performance monitoring module",
"main": "../bundles/performance.umd.js",
"module": "index.js",
"es2015": "./es2015/index.js",
"keywords": [
"angular",
"firebase",
"rxjs"
],
"repository": {
"type": "git",
"url": "git+https://github.com/angular/angularfire2.git"
},
"author": "angular,firebase",
"license": "MIT",
"peerDependencies": {
"@angular/fire": "ANGULARFIRE2_VERSION",
"@angular/common": "ANGULAR_VERSION",
"@angular/core": "ANGULAR_VERSION",
"@angular/platform-browser": "ANGULAR_VERSION",
"@angular/platform-browser-dynamic": "ANGULAR_VERSION",
"firebase": "FIREBASE_VERSION",
"rxjs": "RXJS_VERSION",
"zone.js": "ZONEJS_VERSION"
},
"typings": "index.d.ts"
}
9 changes: 9 additions & 0 deletions src/performance/performance.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { AngularFirePerformance } from './performance';

import 'firebase/performance';

@NgModule({
providers: [ AngularFirePerformance ]
})
export class AngularFirePerformanceModule { }
36 changes: 36 additions & 0 deletions src/performance/performance.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { TestBed, inject } from '@angular/core/testing';
import { FirebaseApp, AngularFireModule } from '@angular/fire';
import { AngularFirePerformance, AngularFirePerformanceModule } from '@angular/fire/performance';
import { COMMON_CONFIG } from './test-config';

describe('AngularFirePerformance', () => {
let app: FirebaseApp;
let afp: AngularFirePerformance;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AngularFireModule.initializeApp(COMMON_CONFIG),
AngularFirePerformanceModule
]
});
inject([FirebaseApp, AngularFirePerformance], (app_: FirebaseApp, _perf: AngularFirePerformance) => {
app = app_;
afp = _perf;
})();
});

afterEach(done => {
app.delete();
done();
});

it('should be exist', () => {
expect(afp instanceof AngularFirePerformance).toBe(true);
});

it('should have the Performance instance', () => {
expect(afp.performance).toBeDefined();
});

});
Loading