main
js 253 lines 7.8 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 {PROFILER_EXPORT_VERSION} from 'react-devtools-shared/src/constants';
11 import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
12
13 import type {ProfilingDataBackend} from 'react-devtools-shared/src/backend/types';
14 import type {
15 ProfilingDataExport,
16 ProfilingDataForRootExport,
17 ProfilingDataForRootFrontend,
18 ProfilingDataFrontend,
19 SnapshotNode,
20 } from './types';
21
22 const commitGradient = [
23 'var(--color-commit-gradient-0)',
24 'var(--color-commit-gradient-1)',
25 'var(--color-commit-gradient-2)',
26 'var(--color-commit-gradient-3)',
27 'var(--color-commit-gradient-4)',
28 'var(--color-commit-gradient-5)',
29 'var(--color-commit-gradient-6)',
30 'var(--color-commit-gradient-7)',
31 'var(--color-commit-gradient-8)',
32 'var(--color-commit-gradient-9)',
33 ];
34
35 // Combines info from the Store (frontend) and renderer interfaces (backend) into the format required by the Profiler UI.
36 // This format can then be quickly exported (and re-imported).
37 export function prepareProfilingDataFrontendFromBackendAndStore(
38 dataBackends: Array<ProfilingDataBackend>,
39 operationsByRootID: Map<number, Array<Array<number>>>,
40 snapshotsByRootID: Map<number, Map<number, SnapshotNode>>,
41 ): ProfilingDataFrontend {
42 const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
43
44 dataBackends.forEach(dataBackend => {
45 dataBackend.dataForRoots.forEach(
46 ({commitData, displayName, initialTreeBaseDurations, rootID}) => {
47 const operations = operationsByRootID.get(rootID);
48 if (operations == null) {
49 throw Error(
50 `Could not find profiling operations for root "${rootID}"`,
51 );
52 }
53
54 const snapshots = snapshotsByRootID.get(rootID);
55 if (snapshots == null) {
56 throw Error(
57 `Could not find profiling snapshots for root "${rootID}"`,
58 );
59 }
60
61 // Do not filter empty commits from the profiler data!
62 // Hiding "empty" commits might cause confusion too.
63 // A commit *did happen* even if none of the components the Profiler is showing were involved.
64 const convertedCommitData = commitData.map(
65 (commitDataBackend, commitIndex) => ({
66 changeDescriptions:
67 commitDataBackend.changeDescriptions != null
68 ? new Map(commitDataBackend.changeDescriptions)
69 : null,
70 duration: commitDataBackend.duration,
71 effectDuration: commitDataBackend.effectDuration,
72 fiberActualDurations: new Map(
73 commitDataBackend.fiberActualDurations,
74 ),
75 fiberSelfDurations: new Map(commitDataBackend.fiberSelfDurations),
76 passiveEffectDuration: commitDataBackend.passiveEffectDuration,
77 priorityLevel: commitDataBackend.priorityLevel,
78 timestamp: commitDataBackend.timestamp,
79 updaters:
80 commitDataBackend.updaters !== null
81 ? commitDataBackend.updaters.map(
82 backendToFrontendSerializedElementMapper,
83 )
84 : null,
85 }),
86 );
87
88 dataForRoots.set(rootID, {
89 commitData: convertedCommitData,
90 displayName,
91 initialTreeBaseDurations: new Map(initialTreeBaseDurations),
92 operations,
93 rootID,
94 snapshots,
95 });
96 },
97 );
98 });
99
100 return {dataForRoots, imported: false};
101 }
102
103 // Converts a Profiling data export into the format required by the Store.
104 export function prepareProfilingDataFrontendFromExport(
105 profilingDataExport: ProfilingDataExport,
106 ): ProfilingDataFrontend {
107 const {version} = profilingDataExport;
108
109 if (version !== PROFILER_EXPORT_VERSION) {
110 throw Error(
111 `Unsupported profile export version "${version}". Supported version is "${PROFILER_EXPORT_VERSION}".`,
112 );
113 }
114
115 const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
116 profilingDataExport.dataForRoots.forEach(
117 ({
118 commitData,
119 displayName,
120 initialTreeBaseDurations,
121 operations,
122 rootID,
123 snapshots,
124 }) => {
125 dataForRoots.set(rootID, {
126 commitData: commitData.map(
127 ({
128 changeDescriptions,
129 duration,
130 effectDuration,
131 fiberActualDurations,
132 fiberSelfDurations,
133 passiveEffectDuration,
134 priorityLevel,
135 timestamp,
136 updaters,
137 }) => ({
138 changeDescriptions:
139 changeDescriptions != null ? new Map(changeDescriptions) : null,
140 duration,
141 effectDuration,
142 fiberActualDurations: new Map(fiberActualDurations),
143 fiberSelfDurations: new Map(fiberSelfDurations),
144 passiveEffectDuration,
145 priorityLevel,
146 timestamp,
147 updaters,
148 }),
149 ),
150 displayName,
151 initialTreeBaseDurations: new Map(initialTreeBaseDurations),
152 operations,
153 rootID,
154 snapshots: new Map(snapshots),
155 });
156 },
157 );
158
159 return {
160 dataForRoots,
161 imported: true,
162 };
163 }
164
165 // Converts a Store Profiling data into a format that can be safely (JSON) serialized for export.
166 export function prepareProfilingDataExport(
167 profilingDataFrontend: ProfilingDataFrontend,
168 ): ProfilingDataExport {
169 const dataForRoots: Array<ProfilingDataForRootExport> = [];
170 profilingDataFrontend.dataForRoots.forEach(
171 ({
172 commitData,
173 displayName,
174 initialTreeBaseDurations,
175 operations,
176 rootID,
177 snapshots,
178 }) => {
179 dataForRoots.push({
180 commitData: commitData.map(
181 ({
182 changeDescriptions,
183 duration,
184 effectDuration,
185 fiberActualDurations,
186 fiberSelfDurations,
187 passiveEffectDuration,
188 priorityLevel,
189 timestamp,
190 updaters,
191 }) => ({
192 changeDescriptions:
193 changeDescriptions != null
194 ? Array.from(changeDescriptions.entries())
195 : null,
196 duration,
197 effectDuration,
198 fiberActualDurations: Array.from(fiberActualDurations.entries()),
199 fiberSelfDurations: Array.from(fiberSelfDurations.entries()),
200 passiveEffectDuration,
201 priorityLevel,
202 timestamp,
203 updaters,
204 }),
205 ),
206 displayName,
207 initialTreeBaseDurations: Array.from(
208 initialTreeBaseDurations.entries(),
209 ),
210 operations,
211 rootID,
212 snapshots: Array.from(snapshots.entries()),
213 });
214 },
215 );
216
217 return {
218 version: PROFILER_EXPORT_VERSION,
219 dataForRoots,
220 };
221 }
222
223 export const getGradientColor = (value: number): string => {
224 const maxIndex = commitGradient.length - 1;
225 let index;
226 if (Number.isNaN(value)) {
227 index = 0;
228 } else if (!Number.isFinite(value)) {
229 index = maxIndex;
230 } else {
231 index = Math.max(0, Math.min(maxIndex, value)) * maxIndex;
232 }
233 return commitGradient[Math.round(index)];
234 };
235
236 export const formatDuration = (duration: number): number | string =>
237 Math.round(duration * 10) / 10 || '<0.1';
238 export const formatPercentage = (percentage: number): number =>
239 Math.round(percentage * 100);
240 export const formatTime = (timestamp: number): number =>
241 Math.round(Math.round(timestamp) / 100) / 10;
242
243 export const scale =
244 (
245 minValue: number,
246 maxValue: number,
247 minRange: number,
248 maxRange: number,
249 ): ((value: number, fallbackValue: number) => number) =>
250 (value: number, fallbackValue: number) =>
251 maxValue - minValue === 0
252 ? fallbackValue
253 : ((value - minValue) / (maxValue - minValue)) * (maxRange - minRange);