Skip to content

Commit e74e8c2

Browse files
committed
test(multiple): add performance testing page to dev-app (#26391)
* test(multiple): add performance testing page to dev-app * provides easy setup for debugging the rendering speed of our components * fixup! test(multiple): add performance testing page to dev-app (cherry picked from commit 81895ac)
1 parent 4014746 commit e74e8c2

File tree

8 files changed

+296
-0
lines changed

8 files changed

+296
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
/src/dev-app/menu/** @crisbeto
214214
/src/dev-app/menubar/** @jelbourn
215215
/src/dev-app/overlay/** @jelbourn @crisbeto
216+
/src/dev-app/performance/** @wagnermaciel
216217
/src/dev-app/paginator/** @andrewseguin
217218
/src/dev-app/platform/** @andrewseguin @devversion
218219
/src/dev-app/popover-edit/** @andrewseguin

src/dev-app/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ ng_module(
7474
"//src/dev-app/menu",
7575
"//src/dev-app/menubar",
7676
"//src/dev-app/paginator",
77+
"//src/dev-app/performance",
7778
"//src/dev-app/platform",
7879
"//src/dev-app/popover-edit",
7980
"//src/dev-app/portal",

src/dev-app/dev-app/dev-app-layout.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
[routerLink]="['/legacy-baseline']">
2727
Legacy Baseline
2828
</a>
29+
30+
<a mat-list-item
31+
tabindex="-1"
32+
(click)="navigation.close()"
33+
[routerLink]="['/performance']">
34+
Performance
35+
</a>
2936
</mat-nav-list>
3037
<button mat-button tabindex="-1" (click)="navigation.close()">CLOSE</button>
3138
</mat-sidenav>

src/dev-app/performance/BUILD.bazel

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
load("//tools:defaults.bzl", "ng_module", "sass_binary")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ng_module(
6+
name = "performance",
7+
srcs = glob(["**/*.ts"]),
8+
assets = [
9+
"performance-demo.html",
10+
":performance_demo_scss",
11+
],
12+
deps = [
13+
"//src/material/button",
14+
"//src/material/divider",
15+
"//src/material/form-field",
16+
"//src/material/icon",
17+
"//src/material/input",
18+
"//src/material/paginator",
19+
"//src/material/select",
20+
"//src/material/table",
21+
"@npm//@angular/forms",
22+
],
23+
)
24+
25+
sass_binary(
26+
name = "performance_demo_scss",
27+
src = "performance-demo.scss",
28+
)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<p class="demo-disclaimer">
2+
<mat-icon color="warn">warning</mat-icon>
3+
Disclaimer: This is an approximate measurement, for more precise performance benchmarking use
4+
Chrome devtools
5+
</p>
6+
7+
<div>
8+
<mat-form-field appearance="outline" style="margin-right: 32px">
9+
<mat-label>Sample size</mat-label>
10+
<mat-select [(value)]="sampleSize" [disabled]="isRunningBenchmark">
11+
<mat-option [value]="1">1</mat-option>
12+
<mat-option [value]="10">10</mat-option>
13+
<mat-option [value]="100">100</mat-option>
14+
<mat-option [value]="1000">1000</mat-option>
15+
</mat-select>
16+
</mat-form-field>
17+
<mat-form-field appearance="outline">
18+
<mat-label>Component count</mat-label>
19+
<mat-select
20+
[(value)]="componentCount"
21+
[disabled]="isRunningBenchmark"
22+
(selectionChange)="componentArray = [].constructor(componentCount)"
23+
>
24+
<mat-option [value]="1">1</mat-option>
25+
<mat-option [value]="10">10</mat-option>
26+
<mat-option [value]="100">100</mat-option>
27+
<mat-option [value]="1000">1000</mat-option>
28+
</mat-select>
29+
</mat-form-field>
30+
</div>
31+
32+
<div class="demo-table-and-paginator-container">
33+
<div class="demo-table-container">
34+
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
35+
<ng-container matColumnDef="index">
36+
<th mat-header-cell *matHeaderCellDef>No.</th>
37+
<td mat-cell *matCellDef="let item">{{ item.index }}</td>
38+
<td mat-footer-cell *matFooterCellDef>
39+
<ng-container *ngIf="allSamples.length">
40+
<b>Average render time</b>
41+
</ng-container>
42+
<ng-container *ngIf="!allSamples.length"> No data yet </ng-container>
43+
</td>
44+
</ng-container>
45+
46+
<ng-container matColumnDef="time">
47+
<th mat-header-cell *matHeaderCellDef>Time</th>
48+
<td mat-cell *matCellDef="let item">{{ item.time }}</td>
49+
<td mat-footer-cell *matFooterCellDef>
50+
<b>{{ computedResults }}</b>
51+
</td>
52+
</ng-container>
53+
54+
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
55+
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
56+
<tr mat-footer-row *matFooterRowDef="displayedColumns; sticky: true"></tr>
57+
</table>
58+
</div>
59+
<mat-divider></mat-divider>
60+
<mat-paginator
61+
[pageSizeOptions]="[10, 50, 100]"
62+
[pageSize]="100"
63+
showFirstLastButtons
64+
[disabled]="isRunningBenchmark"
65+
></mat-paginator>
66+
</div>
67+
68+
<button
69+
color="accent"
70+
mat-button
71+
(click)="clearMetrics()"
72+
[disabled]="isRunningBenchmark"
73+
style="margin-right: 32px"
74+
*ngIf="allSamples.length"
75+
>
76+
Clear Metrics
77+
</button>
78+
79+
<button color="primary" mat-raised-button (click)="runBenchmark()" [disabled]="isRunningBenchmark">
80+
Run Benchmark
81+
</button>
82+
83+
<ng-container *ngIf="show">
84+
<ng-container *ngFor="let _ of componentArray">
85+
<mat-form-field>
86+
<mat-label>Input</mat-label>
87+
<input matInput />
88+
</mat-form-field>
89+
</ng-container>
90+
</ng-container>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.demo-table-and-paginator-container {
2+
margin-bottom: 22px;
3+
border: 1px solid #ccc;
4+
border-radius: 5px;
5+
overflow: hidden;
6+
}
7+
8+
.demo-table-container {
9+
max-height: 60vh;
10+
overflow: auto;
11+
}
12+
13+
.demo-disclaimer {
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
margin: 0 0 32px;
18+
19+
mat-icon {
20+
flex-shrink: 0;
21+
margin-right: 16px;
22+
}
23+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {AfterViewInit, Component, NgZone, ViewChild} from '@angular/core';
10+
import {CommonModule} from '@angular/common';
11+
import {FormsModule} from '@angular/forms';
12+
13+
import {MatButtonModule} from '@angular/material/button';
14+
import {MatDividerModule} from '@angular/material/divider';
15+
import {MatFormFieldModule} from '@angular/material/form-field';
16+
import {MatIconModule} from '@angular/material/icon';
17+
import {MatInputModule} from '@angular/material/input';
18+
import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
19+
import {MatSelectModule} from '@angular/material/select';
20+
import {MatTableDataSource, MatTableModule} from '@angular/material/table';
21+
22+
import {take} from 'rxjs/operators';
23+
24+
@Component({
25+
selector: 'performance-demo',
26+
templateUrl: 'performance-demo.html',
27+
styleUrls: ['performance-demo.css'],
28+
standalone: true,
29+
imports: [
30+
CommonModule,
31+
FormsModule,
32+
MatButtonModule,
33+
MatDividerModule,
34+
MatFormFieldModule,
35+
MatIconModule,
36+
MatInputModule,
37+
MatPaginatorModule,
38+
MatSelectModule,
39+
MatTableModule,
40+
],
41+
})
42+
export class PerformanceDemo implements AfterViewInit {
43+
/** Controls the rendering of components. */
44+
show = false;
45+
46+
/** The number of times metrics will be gathered. */
47+
sampleSize = 100;
48+
49+
/** The number of components being rendered. */
50+
componentCount = 100;
51+
52+
/** A flat array of every sample recorded. */
53+
allSamples: number[] = [];
54+
55+
/** Used to disable benchmark controls while a benchmark is being run. */
56+
isRunningBenchmark = false;
57+
58+
/** The columns in the metrics table. */
59+
displayedColumns: string[] = ['index', 'time'];
60+
61+
/** Basically the same thing as allSamples but organized as a mat-table data source. */
62+
dataSource = new MatTableDataSource<{index: number; time: string}>();
63+
64+
/** The average plus/minus the stdev. */
65+
computedResults = '';
66+
67+
/** Used in an ngFor to render the desired number of comonents. */
68+
componentArray = [].constructor(this.componentCount);
69+
70+
/** The standard deviation of the recorded samples. */
71+
get stdev(): number | undefined {
72+
if (!this.allSamples.length) {
73+
return undefined;
74+
}
75+
return Math.sqrt(
76+
this.allSamples.map(x => Math.pow(x - this.mean!, 2)).reduce((a, b) => a + b) /
77+
this.allSamples.length,
78+
);
79+
}
80+
81+
/** The average value of the recorded samples. */
82+
get mean(): number | undefined {
83+
if (!this.allSamples.length) {
84+
return undefined;
85+
}
86+
return this.allSamples.reduce((a, b) => a + b) / this.allSamples.length;
87+
}
88+
89+
constructor(private _ngZone: NgZone) {}
90+
91+
@ViewChild(MatPaginator) paginator?: MatPaginator;
92+
93+
ngAfterViewInit() {
94+
this.dataSource.paginator = this.paginator!;
95+
}
96+
97+
getTotalRenderTime(): string {
98+
return this.allSamples.length ? `${this.format(this.mean!)} ± ${this.format(this.stdev!)}` : '';
99+
}
100+
101+
format(num: number): string {
102+
const roundedNum = Math.round(num * 100) / 100;
103+
return roundedNum >= 10 ? roundedNum.toFixed(2) : '0' + roundedNum.toFixed(2);
104+
}
105+
106+
async runBenchmark(): Promise<void> {
107+
this.isRunningBenchmark = true;
108+
const samples = [];
109+
for (let i = 0; i < this.sampleSize; i++) {
110+
samples.push(await this.recordSample());
111+
}
112+
this.dataSource.data = this.dataSource.data.concat(
113+
samples.map((sample, i) => ({
114+
index: this.allSamples.length + i,
115+
time: this.format(sample),
116+
})),
117+
);
118+
this.allSamples.push(...samples);
119+
this.isRunningBenchmark = false;
120+
this.computedResults = this.getTotalRenderTime();
121+
}
122+
123+
clearMetrics() {
124+
this.allSamples = [];
125+
this.dataSource.data = [];
126+
this.computedResults = this.getTotalRenderTime();
127+
}
128+
129+
recordSample(): Promise<number> {
130+
return new Promise(res => {
131+
setTimeout(() => {
132+
this.show = true;
133+
const start = performance.now();
134+
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
135+
const end = performance.now();
136+
this.show = false;
137+
res(end - start);
138+
});
139+
});
140+
});
141+
}
142+
}

src/dev-app/routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ export const DEV_APP_ROUTES: Routes = [
246246
path: 'paginator',
247247
loadComponent: () => import('./paginator/paginator-demo').then(m => m.PaginatorDemo),
248248
},
249+
{
250+
path: 'performance',
251+
loadComponent: () => import('./performance/performance-demo').then(m => m.PerformanceDemo),
252+
},
249253
{
250254
path: 'platform',
251255
loadComponent: () => import('./platform/platform-demo').then(m => m.PlatformDemo),

0 commit comments

Comments
 (0)