Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c0746c0

Browse files
mgechevAleksanderBodurri
authored andcommitted
feat(profiler): add duration field to the ProfilerFrame
This way we open the door for further optimizations and make it possible to introduce customization of the output of the formatters per component-level.
1 parent ee8bdc3 commit c0746c0

File tree

15 files changed

+118
-197
lines changed

15 files changed

+118
-197
lines changed

projects/ng-devtools-backend/src/lib/observer/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ let observer: DirectiveForestObserver;
88
let inProgress = false;
99
let inChangeDetection = false;
1010
let eventMap: Map<any, DirectiveProfile>;
11+
let frameDuration = 0;
1112

1213
export const start = (onFrame: (frame: ProfilerFrame) => void): void => {
1314
if (inProgress) {
@@ -54,6 +55,7 @@ export const start = (onFrame: (frame: ProfilerFrame) => void): void => {
5455
current = 0;
5556
}
5657
profile.changeDetection = current + duration;
58+
frameDuration += duration;
5759
} else {
5860
console.warn('Could not find profile for', component);
5961
}
@@ -93,6 +95,7 @@ export const start = (onFrame: (frame: ProfilerFrame) => void): void => {
9395
return;
9496
}
9597
dir.lifecycle[hook] = (dir.lifecycle[hook] || 0) + duration;
98+
frameDuration += duration;
9699
},
97100
});
98101
observer.initialize();
@@ -156,9 +159,10 @@ const insertElementProfile = (frames: ElementProfile[], position: ElementPositio
156159
insertOrMerge(lastFrame, profile);
157160
};
158161

159-
const prepareInitialFrame = (source: string) => {
162+
const prepareInitialFrame = (source: string, duration: number) => {
160163
const frame: ProfilerFrame = {
161164
source,
165+
duration,
162166
directives: [],
163167
};
164168
const directiveForest = observer.getDirectiveForest();
@@ -214,7 +218,10 @@ const flushBuffer = (obs: DirectiveForestObserver, source: string = '') => {
214218
positionDirective.set(position, dir);
215219
});
216220
positions.sort(lexicographicOrder);
217-
const result = prepareInitialFrame(source);
221+
222+
const result = prepareInitialFrame(source, frameDuration);
223+
frameDuration = 0;
224+
218225
positions.forEach((position) => {
219226
const dir = positionDirective.get(position);
220227
insertElementProfile(result.directives, position, eventMap.get(dir));

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/record-formatter/bargraph-formatter/bargraph-formatter.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,11 @@ export interface BargraphNode {
88
original: ElementProfile;
99
}
1010

11-
export class BarGraphFormatter extends RecordFormatter<BargraphNode> {
12-
format(records: ProfilerFrame[]): TimelineView<BargraphNode> {
13-
const result: TimelineView<BargraphNode> = {
14-
timeline: [],
15-
};
16-
records.forEach((record) => {
17-
this.insertTimelineRecord(result.timeline, record);
18-
});
19-
result.timeline = result.timeline
20-
.filter((entry) => entry.app.length > 0 && entry.timeSpent > 0)
21-
.map((entry) => {
22-
entry.app = entry.app.filter((element) => element.value > 0).sort((a, b) => b.value - a.value);
23-
return entry;
24-
});
25-
console.log(result);
26-
return result;
27-
}
28-
29-
insertTimelineRecord(result: AppEntry<BargraphNode>[], record: ProfilerFrame): void {
30-
const entry: AppEntry<BargraphNode> = {
31-
app: [],
32-
timeSpent: 0,
33-
source: record.source,
34-
};
35-
entry.timeSpent = this.addFrames(entry.app, record.directives, []);
36-
result.push(entry);
11+
export class BarGraphFormatter extends RecordFormatter<BargraphNode[]> {
12+
formatFrame(frame: ProfilerFrame): BargraphNode[] {
13+
const result: BargraphNode[] = [];
14+
this.addFrame(result, frame.directives);
15+
return result.filter((element) => element.value > 0).sort((a, b) => b.value - a.value);
3716
}
3817

3918
addFrame(nodes: BargraphNode[], elements: ElementProfile[]): number {

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,21 @@ export interface FlamegraphNode {
1313
export const ROOT_LEVEL_ELEMENT_LABEL = 'Entire application';
1414

1515
export class FlamegraphFormatter extends RecordFormatter<FlamegraphNode> {
16-
format(records: ProfilerFrame[]): TimelineView<FlamegraphNode> {
17-
const result: TimelineView<FlamegraphNode> = {
18-
timeline: [],
16+
formatFrame(frame: ProfilerFrame): FlamegraphNode {
17+
const result: FlamegraphNode = {
18+
value: 0,
19+
label: ROOT_LEVEL_ELEMENT_LABEL,
20+
children: [],
21+
color: '#ccc',
22+
instances: 1,
23+
original: {
24+
children: [],
25+
directives: [],
26+
},
1927
};
20-
records.forEach((record) => {
21-
this.insertTimelineRecord(result.timeline, record);
22-
const last = result.timeline[result.timeline.length - 1];
23-
if (last.app.length > 1) {
24-
const roots = last.app;
25-
last.app = [
26-
{
27-
value: 0,
28-
label: ROOT_LEVEL_ELEMENT_LABEL,
29-
children: roots,
30-
color: '#ccc',
31-
instances: 1,
32-
original: {
33-
children: [],
34-
directives: [],
35-
},
36-
},
37-
];
38-
}
39-
});
40-
result.timeline = result.timeline.filter((entry) => entry.app.length > 0 && entry.timeSpent > 0);
41-
return result;
42-
}
4328

44-
insertTimelineRecord(result: AppEntry<FlamegraphNode>[], record: ProfilerFrame): void {
45-
const entry: AppEntry<FlamegraphNode> = {
46-
app: [],
47-
timeSpent: 0,
48-
source: record.source,
49-
};
50-
entry.timeSpent = this.addFrame(entry.app, record.directives);
51-
result.push(entry);
29+
this.addFrame(result.children, frame.directives);
30+
return result;
5231
}
5332

5433
addFrame(nodes: FlamegraphNode[], elements: ElementProfile[]): number {

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/record-formatter/record-formatter.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,19 @@ import { ElementProfile, ProfilerFrame } from 'protocol';
22
import { AppEntry, RecordFormatter } from './record-formatter';
33

44
class MockFormatter extends RecordFormatter<any> {
5-
addFrame(nodes: any[], elements: ElementProfile[], prev?: any): void {
5+
addFrame(nodes: any[], elements: ElementProfile[]): void {
66
return;
77
}
88

9-
format(records: ProfilerFrame[]): any {
9+
formatFrame(frame: ProfilerFrame): any {
1010
return;
1111
}
12-
13-
insertTimelineRecord(result: AppEntry<any>[], record: ProfilerFrame): void {}
1412
}
1513

1614
const formatter = new MockFormatter();
1715

1816
describe('getValue cases', () => {
19-
let element;
17+
let element: any;
2018

2119
it('calculates value with no lifecycle hooks', () => {
2220
element = {

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/record-formatter/record-formatter.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ export interface AppEntry<T> {
1010
source: string;
1111
}
1212

13-
export interface GraphNode<T> extends AppEntry<T> {
13+
export interface GraphNode {
1414
toolTip: string;
1515
style: any;
1616
}
1717

1818
export abstract class RecordFormatter<T> {
19-
abstract format(records: ProfilerFrame[]): TimelineView<T>;
20-
abstract insertTimelineRecord(result: AppEntry<T>[], record: ProfilerFrame): void;
21-
abstract addFrame(nodes: T[], elements: ElementProfile[], prev?: T): number | void;
19+
abstract formatFrame(frame: ProfilerFrame): T;
20+
abstract addFrame(nodes: T | T[], elements: ElementProfile[], prev?: T): number | void;
2221

2322
getLabel(element: ElementProfile): string {
2423
const name = element.directives

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/record-formatter/tree-map-formatter/tree-map-formatter.spec.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
NESTED_FORMATTED_FLAMEGRAPH_RECORD,
3-
NESTED_RECORD,
4-
SIMPLE_FORMATTED_FLAMEGRAPH_RECORD,
5-
SIMPLE_FORMATTED_TREE_MAP_RECORD,
6-
SIMPLE_RECORD,
7-
} from '../record-formatter-spec-constants';
1+
import { SIMPLE_FORMATTED_TREE_MAP_RECORD, SIMPLE_RECORD } from '../record-formatter-spec-constants';
82
import { AppEntry } from '../record-formatter';
93
import { TreeMapFormatter, TreeMapNode } from './tree-map-formatter';
104

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/record-formatter/tree-map-formatter/tree-map-formatter.ts

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,19 @@ export interface TreeMapNode {
1010
}
1111

1212
export class TreeMapFormatter extends RecordFormatter<TreeMapNode> {
13-
format(records: ProfilerFrame[]): any {
14-
const result: TimelineView<TreeMapNode> = {
15-
timeline: [],
16-
};
17-
records.forEach((record) => {
18-
this.insertTimelineRecord(result.timeline, record);
19-
});
20-
result.timeline = result.timeline.filter((entry) => entry.app.length > 0 && entry.timeSpent > 0);
21-
return result;
22-
}
23-
24-
insertTimelineRecord(result: AppEntry<TreeMapNode>[], record: ProfilerFrame): void {
25-
const entry: AppEntry<TreeMapNode> = {
26-
app: [],
27-
timeSpent: 0,
28-
source: record.source,
29-
};
30-
this.addFrame(entry.app, record.directives);
31-
32-
const size = entry.app.reduce((accum, curr) => {
13+
formatFrame(record: ProfilerFrame): TreeMapNode {
14+
const children: TreeMapNode[] = [];
15+
this.addFrame(children, record.directives);
16+
const size = children.reduce((accum, curr) => {
3317
return accum + curr.size;
3418
}, 0);
35-
entry.app = [
36-
{
37-
id: 'Application',
38-
size,
39-
value: size,
40-
children: entry.app,
41-
original: null,
42-
},
43-
];
44-
entry.timeSpent = size;
45-
result.push(entry);
19+
return {
20+
id: 'Application',
21+
size,
22+
value: size,
23+
children,
24+
original: null,
25+
};
4626
}
4727

4828
addFrame(nodes: TreeMapNode[], elements: ElementProfile[], prev: TreeMapNode | null = null): void {

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-visualizer/bargraph-visualizer/bargraph-visualizer.component.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Component, Input } from '@angular/core';
2-
import { BargraphNode } from '../../record-formatter/bargraph-formatter';
2+
import { BargraphNode, BarGraphFormatter } from '../../record-formatter/bargraph-formatter';
3+
import { ProfilerFrame } from 'protocol';
34

45
export interface GraphNode {
56
name: string;
@@ -25,19 +26,24 @@ export class BargraphVisualizerComponent {
2526
pieChartData: GraphNode[] = [];
2627
parentHierarchy: { name: string }[] = [];
2728

28-
@Input() set records(data: BargraphNode[]) {
29-
this.profileRecords = data;
29+
private _formatter = new BarGraphFormatter();
30+
31+
@Input() set frame(data: ProfilerFrame) {
32+
this.profileRecords = this._formatter.formatFrame(data);
3033
this.selectedEntry = null;
31-
this.view = [1000, data.length * 30];
34+
this.view = [1000, this.profileRecords.length * 30];
3235
}
3336

3437
formatPieChartData(bargraphNode: BargraphNode): GraphNode[] {
3538
const graphData: GraphNode[] = [];
3639
bargraphNode.original.directives.forEach((node) => {
37-
graphData.push({
38-
name: `${node.name} changeDetection`,
39-
value: +node.changeDetection.toFixed(2),
40-
});
40+
const {changeDetection} = node;
41+
if (changeDetection) {
42+
graphData.push({
43+
name: `${node.name} changeDetection`,
44+
value: parseFloat(changeDetection.toFixed(2)),
45+
});
46+
}
4147
Object.keys(node.lifecycle).forEach((key) => {
4248
graphData.push({
4349
name: `${node.name} ${key}`,

projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-visualizer/flamegraph-visualizer/flamegraph-visualizer.component.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { Component, ElementRef, Input } from '@angular/core';
22
import {
33
FlamegraphNode,
44
ROOT_LEVEL_ELEMENT_LABEL,
5+
FlamegraphFormatter,
56
} from '../../record-formatter/flamegraph-formatter/flamegraph-formatter';
67
import { RawData } from 'ngx-flamegraph/lib/utils';
8+
import { ProfilerFrame } from 'protocol';
79

810
export interface GraphNode {
911
name: string;
@@ -26,9 +28,11 @@ export class FlamegraphVisualizerComponent {
2628
domain: ['#E71D36', '#2EC4B6', '#FF9F1C', '#011627'],
2729
};
2830

29-
@Input() set bars(data: FlamegraphNode[]) {
31+
private _formatter = new FlamegraphFormatter();
32+
33+
@Input() set frame(frame: ProfilerFrame) {
3034
this.selectedEntry = null;
31-
this.profilerBars = data;
35+
this.profilerBars = [this._formatter.formatFrame(frame)];
3236
}
3337

3438
constructor(private _el: ElementRef) {}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<ng-container [ngSwitch]="visualizationMode">
22
<ng-container *ngSwitchCase="cmpVisualizationModes.FlameGraph">
3-
<ng-flamegraph-visualizer [bars]="records"></ng-flamegraph-visualizer>
3+
<ng-flamegraph-visualizer [frame]="frame"></ng-flamegraph-visualizer>
44
</ng-container>
55
<ng-container *ngSwitchCase="cmpVisualizationModes.TreeMap">
6-
<ng-tree-map-visualizer [records]="records"></ng-tree-map-visualizer>
6+
<ng-tree-map-visualizer [frame]="frame"></ng-tree-map-visualizer>
77
</ng-container>
88
<ng-container *ngSwitchCase="cmpVisualizationModes.BarGraph">
9-
<ng-bargraph-visualizer [records]="records"></ng-bargraph-visualizer>
9+
<ng-bargraph-visualizer [frame]="frame"></ng-bargraph-visualizer>
1010
</ng-container>
1111
</ng-container>

0 commit comments

Comments
 (0)