@samitouri / QOS-React / commits / 734e4146e7

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;