main
js 200 lines 4.96 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import {formatDuration} from './utils';
11 import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
12
13 import type {CommitTree} from './types';
14
15 export type ChartNode = {
16 actualDuration: number,
17 didRender: boolean,
18 id: number,
19 label: string,
20 name: string,
21 offset: number,
22 selfDuration: number,
23 treeBaseDuration: number,
24 };
25
26 export type ChartData = {
27 baseDuration: number,
28 depth: number,
29 idToDepthMap: Map<number, number>,
30 maxSelfDuration: number,
31 renderPathNodes: Set<number>,
32 rows: Array<Array<ChartNode>>,
33 };
34
35 const cachedChartData: Map<string, ChartData> = new Map();
36
37 export function getChartData({
38 commitIndex,
39 commitTree,
40 profilerStore,
41 rootID,
42 }: {
43 commitIndex: number,
44 commitTree: CommitTree,
45 profilerStore: ProfilerStore,
46 rootID: number,
47 }): ChartData {
48 const commitDatum = profilerStore.getCommitData(rootID, commitIndex);
49
50 const {fiberActualDurations, fiberSelfDurations} = commitDatum;
51 const {nodes} = commitTree;
52
53 const chartDataKey = `${rootID}-${commitIndex}`;
54 if (cachedChartData.has(chartDataKey)) {
55 return cachedChartData.get(chartDataKey) as any as ChartData;
56 }
57
58 const idToDepthMap: Map<number, number> = new Map();
59 const renderPathNodes: Set<number> = new Set();
60 const rows: Array<Array<ChartNode>> = [];
61
62 let maxDepth = 0;
63 let maxSelfDuration = 0;
64
65 // Generate flame graph structure using tree base durations.
66 const walkTree = (
67 id: number,
68 rightOffset: number,
69 currentDepth: number,
70 ): ChartNode => {
71 idToDepthMap.set(id, currentDepth);
72
73 const node = nodes.get(id);
74 if (node == null) {
75 throw Error(`Could not find node with id "${id}" in commit tree`);
76 }
77
78 const {
79 children,
80 displayName,
81 hocDisplayNames,
82 key,
83 treeBaseDuration,
84 compiledWithForget,
85 } = node;
86
87 const actualDuration = fiberActualDurations.get(id) || 0;
88 const selfDuration = fiberSelfDurations.get(id) || 0;
89 const didRender = fiberActualDurations.has(id);
90
91 const name = displayName || 'Anonymous';
92 const maybeKey = key !== null ? ` key="${key}"` : '';
93
94 let maybeBadge = '';
95 const maybeForgetBadge = compiledWithForget ? '' : '';
96
97 if (hocDisplayNames !== null && hocDisplayNames.length > 0) {
98 maybeBadge = ` (${hocDisplayNames[0]})`;
99 }
100
101 let label = `${maybeForgetBadge}${name}${maybeBadge}${maybeKey}`;
102 if (didRender) {
103 label += ` (${formatDuration(selfDuration)}ms of ${formatDuration(
104 actualDuration,
105 )}ms)`;
106 }
107
108 maxDepth = Math.max(maxDepth, currentDepth);
109 maxSelfDuration = Math.max(maxSelfDuration, selfDuration);
110
111 const chartNode: ChartNode = {
112 actualDuration,
113 didRender,
114 id,
115 label,
116 name,
117 offset: rightOffset - treeBaseDuration,
118 selfDuration,
119 treeBaseDuration,
120 };
121
122 if (currentDepth > rows.length) {
123 rows.push([chartNode]);
124 } else {
125 rows[currentDepth - 1].push(chartNode);
126 }
127
128 for (let i = children.length - 1; i >= 0; i--) {
129 const childID = children[i];
130 const childChartNode: $FlowFixMe = walkTree(
131 childID,
132 rightOffset,
133 currentDepth + 1,
134 );
135 rightOffset -= childChartNode.treeBaseDuration;
136 }
137
138 return chartNode;
139 };
140
141 let baseDuration = 0;
142
143 // Special case to handle unmounted roots.
144 if (nodes.size > 0) {
145 // Skip over the root; we don't want to show it in the flamegraph.
146 const root = nodes.get(rootID);
147 if (root == null) {
148 throw Error(
149 `Could not find root node with id "${rootID}" in commit tree`,
150 );
151 }
152
153 // Don't assume a single root.
154 // Component filters or Fragments might lead to multiple "roots" in a flame graph.
155 for (let i = root.children.length - 1; i >= 0; i--) {
156 const id = root.children[i];
157 const node = nodes.get(id);
158 if (node == null) {
159 throw Error(`Could not find node with id "${id}" in commit tree`);
160 }
161 baseDuration += node.treeBaseDuration;
162 walkTree(id, baseDuration, 1);
163 }
164
165 fiberActualDurations.forEach((duration, id) => {
166 let node = nodes.get(id);
167 if (node != null) {
168 let currentID = node.parentID;
169 while (currentID !== 0) {
170 if (renderPathNodes.has(currentID)) {
171 // We've already walked this path; we can skip it.
172 break;
173 } else {
174 renderPathNodes.add(currentID);
175 }
176
177 node = nodes.get(currentID);
178 currentID = node != null ? node.parentID : 0;
179 }
180 }
181 });
182 }
183
184 const chartData = {
185 baseDuration,
186 depth: maxDepth,
187 idToDepthMap,
188 maxSelfDuration,
189 renderPathNodes,
190 rows,
191 };
192
193 cachedChartData.set(chartDataKey, chartData);
194
195 return chartData;
196 }
197
198 export function invalidateChartData(): void {
199 cachedChartData.clear();
200 }