|
| 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 | +} |
0 commit comments