Added Ranked chart data generation; fixed some logic errors in backend tree base duration tracking
Brian Vaughn committed
Mar 16, 2019 at 10:52 UTC
734e4146e7bbfdaebdfaf3762aaf79c22f93e328
10 files changed
+258
-96
src/backend/agent.js
+3
@@ -46,6 +46,9 @@ export default class Agent extends EventEmitter {
46
addBridge(bridge: Bridge) {
47
this._bridge = bridge;
48
49
+ // TODO (profiling) Component commits
50
+ // TODO (profiling) Interactions
51
+
52
bridge.addListener('getCommitDetails', this.getCommitDetails);
53
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
54
bridge.addListener('getProfilingSummary', this.getProfilingSummary);
src/backend/renderer.js
+30
-33
@@ -693,30 +693,32 @@ export function attach(
693
) {
694
debug('enqueueUpdateIfNecessary()', fiber);
695
696
- if (isProfiling) {
697
- if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
698
- const id = getFiberID(getPrimaryFiber(fiber));
699
- const { actualDuration, treeBaseDuration } = fiber;
700
-
701
- const operation = new Uint32Array(3);
702
- operation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
703
- operation[1] = id;
704
- operation[2] = treeBaseDuration;
705
- addOperation(operation);
706
-
707
- idToTreeBaseDurationMap.set(id, treeBaseDuration);
708
-
709
- if (actualDuration > 0) {
710
- // If profiling is active, store durations for elements that were rendered during the commit.
711
- const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
712
- metadata.committedFibers.push({
713
- id,
714
- actualDuration,
715
- });
716
- metadata.maxActualDuration = Math.max(
717
- metadata.maxActualDuration,
718
- actualDuration
719
- );
696
+ const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
697
+ if (isProfilingSupported) {
698
+ const id = getFiberID(getPrimaryFiber(fiber));
699
+ const { actualDuration, treeBaseDuration } = fiber;
700
+
701
+ idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration);
702
+
703
+ if (isProfiling) {
704
+ if (treeBaseDuration !== fiber.alternate.treeBaseDuration) {
705
+ const operation = new Uint32Array(3);
706
+ operation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
707
+ operation[1] = getFiberID(getPrimaryFiber(fiber));
708
+ operation[2] = treeBaseDuration;
709
+ addOperation(operation);
710
+ }
711
+
712
+ if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
713
+ if (actualDuration > 0) {
714
+ // If profiling is active, store durations for elements that were rendered during the commit.
715
+ const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
716
+ metadata.actualDurations.push(id, actualDuration);
717
+ metadata.maxActualDuration = Math.max(
718
+ metadata.maxActualDuration,
719
+ actualDuration
720
+ );
721
+ }
722
}
723
}
724
}
@@ -880,7 +882,7 @@ export function attach(
882
// If profiling is active, store commit time and duration, and the current interactions.
883
// The frontend may request this information after profiling has stopped.
884
currentCommitProfilingMetadata = {
883
- committedFibers: [],
885
+ actualDurations: [],
886
commitTime: performance.now() - profilingStartTime,
887
interactions: Array.from(root.memoizedInteractions).map(
888
(interaction: Interaction) => ({
@@ -1377,13 +1379,8 @@ export function attach(
1379
}
1380
}
1381
1380
- type CommittedFiber = {|
1381
- actualDuration: number,
1382
- id: number,
1383
- |};
1384
-
1382
type CommitProfilingData = {|
1386
- committedFibers: Array<CommittedFiber>,
1383
+ actualDurations: Array<number>,
1384
commitTime: number,
1385
interactions: Array<Interaction>,
1386
maxActualDuration: number,
@@ -1410,7 +1407,7 @@ export function attach(
1407
return {
1408
commitIndex,
1409
interactions: commitProfilingData.interactions,
1413
- committedFibers: commitProfilingData.committedFibers,
1410
+ actualDurations: commitProfilingData.actualDurations,
1411
rootID,
1412
};
1413
}
@@ -1419,7 +1416,7 @@ export function attach(
1416
return {
1417
commitIndex,
1418
interactions: [],
1422
- committedFibers: [],
1419
+ actualDurations: [],
1420
rootID,
1421
};
1422
}
src/backend/types.js
+1
-4
@@ -56,12 +56,9 @@ export type Interaction = {|
56
|};
57
58
export type CommitDetails = {|
59
+ actualDurations: Array<number>,
60
commitIndex: number,
61
interactions: Array<Interaction>,
61
- committedFibers: Array<{|
62
- actualDuration: number,
63
- id: number,
64
- |}>,
62
rootID: number,
63
|};
64
src/devtools/ProfilingCache.js
+9
-4
@@ -59,7 +59,7 @@ export default class ProfilingCache {
59
if (!store.profilingOperations.has(rootID)) {
60
// If no profiling data was recorded for this root, skip the round trip.
61
resolve({
62
- committedFibers: [],
62
+ actualDurations: new Map(),
63
interactions: [],
64
});
65
} else {
@@ -139,7 +139,7 @@ export default class ProfilingCache {
139
140
onCommitDetails = ({
141
commitIndex,
142
- committedFibers,
142
+ actualDurations,
143
interactions,
144
rootID,
145
}: CommitDetailsBackend) => {
@@ -148,8 +148,13 @@ export default class ProfilingCache {
148
if (resolve != null) {
149
this._pendingCommitDetailsMap.delete(key);
150
151
+ const actualDurationsMap = new Map();
152
+ for (let i = 0; i < actualDurations.length; i += 2) {
153
+ actualDurationsMap.set(actualDurations[i], actualDurations[i + 1]);
154
+ }
155
+
156
resolve({
152
- committedFibers,
157
+ actualDurations: actualDurationsMap,
158
interactions,
159
});
160
}
@@ -166,7 +171,7 @@ export default class ProfilingCache {
171
if (resolve != null) {
172
this._pendingProfileSummaryMap.delete(rootID);
173
const initialTreeBaseDurationsMap = new Map();
169
- for (let i = 0; i < initialTreeBaseDurations.length; i++) {
174
+ for (let i = 0; i < initialTreeBaseDurations.length; i += 2) {
175
initialTreeBaseDurationsMap.set(
176
initialTreeBaseDurations[i],
177
initialTreeBaseDurations[i + 1]
src/devtools/store.js
+30
-26
@@ -575,33 +575,37 @@ export default class Store extends EventEmitter {
575
576
// DEBUG
577
__printTree = () => {
578
- console.group('__printTree()');
579
- this._roots.forEach((rootID: number) => {
580
- const printElement = (id: number) => {
581
- const element = ((this._idToElement.get(id): any): Element);
582
- console.log(
583
- `${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
584
- ''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})`
585
- );
586
- element.children.forEach(printElement);
587
- };
588
- const root = ((this._idToElement.get(rootID): any): Element);
589
- console.group(`${rootID}:root (${root.weight})`);
590
- root.children.forEach(printElement);
591
- console.groupEnd();
592
- });
593
- console.group(`List of ${this.numElements} elements`);
594
- for (let i = 0; i < this.numElements; i++) {
595
- //if (i === 4) { debugger }
596
- const element = this.getElementAtIndex(i);
597
- if (element != null) {
598
- console.log(
599
- `${'•'.repeat(element.depth)}${i}: ${element.displayName ||
600
- 'Unknown'}`
601
- );
578
+ if (__DEBUG__) {
579
+ console.group('__printTree()');
580
+ this._roots.forEach((rootID: number) => {
581
+ const printElement = (id: number) => {
582
+ const element = ((this._idToElement.get(id): any): Element);
583
+ console.log(
584
+ `${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
585
+ ''} ${element.key ? `key:"${element.key}"` : ''} (${
586
+ element.weight
587
+ })`
588
+ );
589
+ element.children.forEach(printElement);
590
+ };
591
+ const root = ((this._idToElement.get(rootID): any): Element);
592
+ console.group(`${rootID}:root (${root.weight})`);
593
+ root.children.forEach(printElement);
594
+ console.groupEnd();
595
+ });
596
+ console.group(`List of ${this.numElements} elements`);
597
+ for (let i = 0; i < this.numElements; i++) {
598
+ //if (i === 4) { debugger }
599
+ const element = this.getElementAtIndex(i);
600
+ if (element != null) {
601
+ console.log(
602
+ `${'•'.repeat(element.depth)}${i}: ${element.displayName ||
603
+ 'Unknown'}`
604
+ );
605
+ }
606
}
607
+ console.groupEnd();
608
+ console.groupEnd();
609
}
604
- console.groupEnd();
605
- console.groupEnd();
610
};
611
}
src/devtools/views/Profiler/CommitRanked.js
+55
@@ -2,10 +2,13 @@
2
3
import React, { useContext } from 'react';
4
import { ProfilerContext } from './ProfilerContext';
5
+import { calculateSelfDuration } from './utils';
6
import { StoreContext } from '../context';
7
8
import styles from './CommitRanked.css';
9
10
+import type { CommitDetails, CommitTree, Node } from './types';
11
+
12
export default function CommitRanked(_: {||}) {
13
const { rendererID, rootID, selectedCommitIndex } = useContext(
14
ProfilerContext
@@ -31,5 +34,57 @@ export default function CommitRanked(_: {||}) {
34
rootID: ((rootID: any): number),
35
});
36
37
+ const chartData = generateChartData(commitTree, commitDetails);
38
+
39
return 'Coming soon: Ranked';
40
}
41
+
42
+type ChartNode = {|
43
+ id: number,
44
+ label: string,
45
+ name: string,
46
+ title: string,
47
+ value: number,
48
+|};
49
+
50
+type ChartData = {|
51
+ maxValue: number,
52
+ nodes: Array<ChartNode>,
53
+|};
54
+
55
+const generateChartData = (
56
+ commitTree: CommitTree,
57
+ commitDetails: CommitDetails
58
+): ChartData => {
59
+ const { nodes } = commitTree;
60
+
61
+ let maxSelfDuration = 0;
62
+
63
+ const chartNodes: Array<ChartNode> = [];
64
+ commitDetails.actualDurations.forEach((actualDuration, id) => {
65
+ const node = ((nodes.get(id): any): Node);
66
+
67
+ // Don't show the root node in this chart.
68
+ if (node.parentID === 0) {
69
+ return;
70
+ }
71
+
72
+ const selfDuration = calculateSelfDuration(id, commitTree, commitDetails);
73
+ maxSelfDuration = Math.max(maxSelfDuration, selfDuration);
74
+
75
+ const name = node.displayName || 'Unknown';
76
+ const label = `${name} (${selfDuration.toFixed(1)}ms)`;
77
+ chartNodes.push({
78
+ id,
79
+ label,
80
+ name,
81
+ title: label,
82
+ value: selfDuration,
83
+ });
84
+ });
85
+
86
+ return {
87
+ maxValue: maxSelfDuration,
88
+ nodes: chartNodes.sort((a, b) => b.value - a.value),
89
+ };
90
+};
src/devtools/views/Profiler/CommitTreeBuilder.js
+101
-25
@@ -1,6 +1,7 @@
1
// @flow
2
3
import {
4
+ __DEBUG__,
5
TREE_OPERATION_ADD,
6
TREE_OPERATION_REMOVE,
7
TREE_OPERATION_RESET_CHILDREN,
@@ -17,6 +18,17 @@ import type {
18
ProfilingSummary as ProfilingSummaryFrontend,
19
} from 'src/devtools/views/Profiler/types';
20
21
+const debug = (methodName, ...args) => {
22
+ if (__DEBUG__) {
23
+ console.log(
24
+ `%cCommitTreeBuilder %c${methodName}`,
25
+ 'color: pink; font-weight: bold;',
26
+ 'font-weight: bold;',
27
+ ...args
28
+ );
29
+ }
30
+};
31
+
32
const rootToCommitTreeMap: Map<number, Array<CommitTree>> = new Map();
33
34
export function getCommitTree({
@@ -49,40 +61,29 @@ export function getCommitTree({
61
// If this is the very first commit, start with the cached snapshot and apply the first mutation.
62
// Otherwise load (or generate) the previous commit and append a mutation to it.
63
if (commitIndex === 0) {
52
- const initialCommitTree = {
53
- nodes: new Map(),
54
- rootID,
55
- };
64
+ const nodes = new Map();
65
66
// Construct the initial tree.
58
- const queue: Array<number> = [rootID];
59
- while (queue.length > 0) {
60
- const currentID = queue.pop();
61
- const currentNode = ((store.profilingSnapshot.get(
62
- currentID
63
- ): any): Node);
64
-
65
- initialCommitTree.nodes.set(currentID, {
66
- id: currentID,
67
- children: currentNode.children,
68
- displayName: currentNode.displayName,
69
- key: currentNode.key,
70
- parentID: 0,
71
- treeBaseDuration: ((profilingSummary.initialTreeBaseDurations.get(
72
- currentID
73
- ): any): number),
74
- });
75
-
76
- queue.push(...currentNode.children);
77
- }
67
+ recursivelyIniitliazeTree(
68
+ rootID,
69
+ 0,
70
+ nodes,
71
+ profilingSummary.initialTreeBaseDurations,
72
+ store
73
+ );
74
75
// Mutate the tree
76
const commitOperations = store.profilingOperations.get(rootID);
77
if (commitOperations != null && commitIndex < commitOperations.length) {
78
const commitTree = updateTree(
83
- initialCommitTree,
79
+ { nodes, rootID },
80
commitOperations[commitIndex]
81
);
82
+
83
+ if (__DEBUG__) {
84
+ __printTree(commitTree);
85
+ }
86
+
87
commitTrees.push(commitTree);
88
return commitTree;
89
}
@@ -100,6 +101,11 @@ export function getCommitTree({
101
previousCommitTree,
102
commitOperations[commitIndex]
103
);
104
+
105
+ if (__DEBUG__) {
106
+ __printTree(commitTree);
107
+ }
108
+
109
commitTrees.push(commitTree);
110
return commitTree;
111
}
@@ -113,6 +119,35 @@ export function getCommitTree({
119
};
120
}
121
122
+function recursivelyIniitliazeTree(
123
+ id: number,
124
+ parentID: number,
125
+ nodes: Map<number, Node>,
126
+ initialTreeBaseDurations: Map<number, number>,
127
+ store: Store
128
+): void {
129
+ const node = ((store.profilingSnapshot.get(id): any): Node);
130
+
131
+ nodes.set(id, {
132
+ id,
133
+ children: node.children,
134
+ displayName: node.displayName,
135
+ key: node.key,
136
+ parentID,
137
+ treeBaseDuration: ((initialTreeBaseDurations.get(id): any): number),
138
+ });
139
+
140
+ node.children.forEach(childID =>
141
+ recursivelyIniitliazeTree(
142
+ childID,
143
+ id,
144
+ nodes,
145
+ initialTreeBaseDurations,
146
+ store
147
+ )
148
+ );
149
+}
150
+
151
function updateTree(
152
commitTree: CommitTree,
153
operations: Uint32Array
@@ -170,6 +205,11 @@ function updateTree(
205
parentNode = ((nodes.get(parentID): any): Node);
206
parentNode.children = parentNode.children.concat(id);
207
208
+ debug(
209
+ 'Add',
210
+ `fiber ${id} (${displayName || 'null'}) as child of ${parentID}`
211
+ );
212
+
213
const node: Node = {
214
children: [],
215
displayName,
@@ -197,6 +237,8 @@ function updateTree(
237
if (parentNode == null) {
238
// No-op
239
} else {
240
+ debug('Remove', `fiber ${id} from parent ${parentID}`);
241
+
242
parentNode.children = parentNode.children.filter(
243
childID => childID !== id
244
);
@@ -212,8 +254,11 @@ function updateTree(
254
255
i = i + 3 + numChildren;
256
257
+ debug('Re-order', `fiber ${id} children ${children.join(',')}`);
258
+
259
node = ((nodes.get(id): any): Node);
260
node.children = Array.from(children);
261
+
262
break;
263
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
264
id = operations[i + 1];
@@ -221,6 +266,11 @@ function updateTree(
266
node = ((nodes.get(id): any): Node);
267
node.treeBaseDuration = operations[i + 2];
268
269
+ debug(
270
+ 'Update',
271
+ `fiber ${id} treeBaseDuration to ${node.treeBaseDuration}`
272
+ );
273
+
274
i = i + 3;
275
break;
276
default:
@@ -237,3 +287,29 @@ function updateTree(
287
export function invalidateCommitTrees(): void {
288
rootToCommitTreeMap.clear();
289
}
290
+
291
+// DEBUG
292
+const __printTree = (commitTree: CommitTree) => {
293
+ if (__DEBUG__) {
294
+ const { nodes, rootID } = commitTree;
295
+ console.group('__printTree()');
296
+ const queue = [rootID, 0];
297
+ while (queue.length > 0) {
298
+ const id = queue.shift();
299
+ const depth = queue.shift();
300
+
301
+ const node = ((nodes.get(id): any): Node);
302
+
303
+ console.log(
304
+ `${'•'.repeat(depth)}${node.id}:${node.displayName || ''} ${
305
+ node.key ? `key:"${node.key}"` : ''
306
+ } (${node.treeBaseDuration})`
307
+ );
308
+
309
+ node.children.forEach(childID => {
310
+ queue.push(childID, depth + 1);
311
+ });
312
+ }
313
+ console.groupEnd();
314
+ }
315
+};
src/devtools/views/Profiler/SnapshotSelector.js
+2
@@ -55,6 +55,8 @@ export default function SnapshotSelector(_: Props) {
55
return null;
56
}, [filteredCommitIndices, selectedCommitIndex]);
57
58
+ // TODO (profiling) This should be managed by the context controller (reducer).
59
+ // TODO (profiling) We should also reset the selected index to 0 between profiling sessions.
60
if (selectedFilteredCommitIndex === null) {
61
if (numFilteredCommits > 0) {
62
setSelectedCommitIndex(0);
src/devtools/views/Profiler/types.js
+1
-4
@@ -21,11 +21,8 @@ export type Interaction = {|
21
|};
22
23
export type CommitDetails = {|
24
+ actualDurations: Map<number, number>,
25
interactions: Array<Interaction>,
25
- committedFibers: Array<{|
26
- actualDuration: number,
27
- id: number,
28
- |}>,
26
|};
27
28
export type ProfilingSummary = {|
src/devtools/views/Profiler/utils.js
+26
@@ -1,5 +1,7 @@
1
// @flow
2
3
+import type { CommitDetails, CommitTree, Node } from './types';
4
+
5
const commitGradient = [
6
'var(--color-commit-gradient-0)',
7
'var(--color-commit-gradient-1)',
@@ -13,6 +15,30 @@ const commitGradient = [
15
'var(--color-commit-gradient-9)',
16
];
17
18
+export const calculateSelfDuration = (
19
+ id: number,
20
+ commitTree: CommitTree,
21
+ commitDetails: CommitDetails
22
+): number => {
23
+ const { actualDurations } = commitDetails;
24
+ const { nodes } = commitTree;
25
+
26
+ if (!actualDurations.has(id)) {
27
+ return 0;
28
+ }
29
+
30
+ let selfDuration = ((actualDurations.get(id): any): number);
31
+
32
+ const node = ((nodes.get(id): any): Node);
33
+ node.children.forEach(childID => {
34
+ if (actualDurations.has(childID)) {
35
+ selfDuration -= ((actualDurations.get(childID): any): number);
36
+ }
37
+ });
38
+
39
+ return selfDuration;
40
+};
41
+
42
export const getGradientColor = (value: number) => {
43
const maxIndex = commitGradient.length - 1;
44
let index;