Skip to content

Commit e104795

Browse files
authored
[Fiber] Show Diff Render Props in Performance Track in DEV (facebook#33658)
<img width="634" alt="Screenshot 2025-06-27 at 1 13 20 PM" src="https://github.com/user-attachments/assets/dc8c488b-4a23-453f-918f-36b245364934" /> We have to be careful with performance in DEV. It can slow down DX since these are ran whether you're currently running a performance trace or not. It can also show up as misleading since these add time to the "Remaining Effects" entry. I'm not adding all props to the entries. Instead, I'm only adding the changed props after diffing and none for initial mount. I'm trying to as much as possible pick a fast path when possible. I'm also only logging this for the "render" entries and not the effects. If we did something for effects, it would be more like checking with dep changed. This could still have a negative effect on dev performance since we're now also using the slower `performance.measure` API when there's a diff.
1 parent c0d151c commit e104795

File tree

3 files changed

+215
-63
lines changed

3 files changed

+215
-63
lines changed

packages/react-client/src/ReactFlightPerformanceTrack.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ export function logComponentRender(
103103
if (__DEV__ && debugTask) {
104104
const properties: Array<[string, string]> = [];
105105
if (componentInfo.key != null) {
106-
addValueToProperties('key', componentInfo.key, properties, 0);
106+
addValueToProperties('key', componentInfo.key, properties, 0, '');
107107
}
108108
if (componentInfo.props != null) {
109-
addObjectToProperties(componentInfo.props, properties, 0);
109+
addObjectToProperties(componentInfo.props, properties, 0, '');
110110
}
111111
debugTask.run(
112112
// $FlowFixMe[method-unbinding]
@@ -158,10 +158,10 @@ export function logComponentAborted(
158158
],
159159
];
160160
if (componentInfo.key != null) {
161-
addValueToProperties('key', componentInfo.key, properties, 0);
161+
addValueToProperties('key', componentInfo.key, properties, 0, '');
162162
}
163163
if (componentInfo.props != null) {
164-
addObjectToProperties(componentInfo.props, properties, 0);
164+
addObjectToProperties(componentInfo.props, properties, 0, '');
165165
}
166166
performance.measure(entryName, {
167167
start: startTime < 0 ? 0 : startTime,
@@ -215,10 +215,10 @@ export function logComponentErrored(
215215
String(error);
216216
const properties = [['Error', message]];
217217
if (componentInfo.key != null) {
218-
addValueToProperties('key', componentInfo.key, properties, 0);
218+
addValueToProperties('key', componentInfo.key, properties, 0, '');
219219
}
220220
if (componentInfo.props != null) {
221-
addObjectToProperties(componentInfo.props, properties, 0);
221+
addObjectToProperties(componentInfo.props, properties, 0, '');
222222
}
223223
performance.measure(entryName, {
224224
start: startTime < 0 ? 0 : startTime,
@@ -423,9 +423,9 @@ export function logComponentAwait(
423423
if (__DEV__ && debugTask) {
424424
const properties: Array<[string, string]> = [];
425425
if (typeof value === 'object' && value !== null) {
426-
addObjectToProperties(value, properties, 0);
426+
addObjectToProperties(value, properties, 0, '');
427427
} else if (value !== undefined) {
428-
addValueToProperties('Resolved', value, properties, 0);
428+
addValueToProperties('Resolved', value, properties, 0, '');
429429
}
430430
debugTask.run(
431431
// $FlowFixMe[method-unbinding]
@@ -525,9 +525,9 @@ export function logIOInfo(
525525
if (__DEV__ && debugTask) {
526526
const properties: Array<[string, string]> = [];
527527
if (typeof value === 'object' && value !== null) {
528-
addObjectToProperties(value, properties, 0);
528+
addObjectToProperties(value, properties, 0, '');
529529
} else if (value !== undefined) {
530-
addValueToProperties('Resolved', value, properties, 0);
530+
addValueToProperties('Resolved', value, properties, 0, '');
531531
}
532532
debugTask.run(
533533
// $FlowFixMe[method-unbinding]

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,26 @@ import {
2929
import {
3030
addValueToProperties,
3131
addObjectToProperties,
32+
addObjectDiffToProperties,
3233
} from 'shared/ReactPerformanceTrackProperties';
3334

3435
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
3536

3637
const supportsUserTiming =
3738
enableProfilerTimer &&
3839
typeof console !== 'undefined' &&
39-
typeof console.timeStamp === 'function';
40+
typeof console.timeStamp === 'function' &&
41+
(!__DEV__ ||
42+
// In DEV we also rely on performance.measure
43+
(typeof performance !== 'undefined' &&
44+
// $FlowFixMe[method-unbinding]
45+
typeof performance.measure === 'function'));
4046

4147
const COMPONENTS_TRACK = 'Components ⚛';
4248
const LANES_TRACK_GROUP = 'Scheduler ⚛';
4349

4450
let currentTrack: string = 'Blocking'; // Lane
4551

46-
const reusableLaneDevToolDetails = {
47-
color: 'primary',
48-
track: 'Blocking', // Lane
49-
trackGroup: LANES_TRACK_GROUP,
50-
};
51-
const reusableLaneOptions = {
52-
start: -0,
53-
end: -0,
54-
detail: {
55-
devtools: reusableLaneDevToolDetails,
56-
},
57-
};
58-
5952
export function setCurrentTrackFromLanes(lanes: Lanes): void {
6053
currentTrack = getGroupNameOfHighestPriorityLane(lanes);
6154
}
@@ -166,6 +159,21 @@ export function logComponentDisappeared(
166159
logComponentTrigger(fiber, startTime, endTime, 'Disconnect');
167160
}
168161

162+
const reusableComponentDevToolDetails = {
163+
color: 'primary',
164+
properties: (null: null | Array<[string, string]>),
165+
track: COMPONENTS_TRACK,
166+
};
167+
const reusableComponentOptions = {
168+
start: -0,
169+
end: -0,
170+
detail: {
171+
devtools: reusableComponentDevToolDetails,
172+
},
173+
};
174+
175+
const resuableChangedPropsEntry = ['Changed Props', ''];
176+
169177
export function logComponentRender(
170178
fiber: Fiber,
171179
startTime: number,
@@ -178,8 +186,9 @@ export function logComponentRender(
178186
return;
179187
}
180188
if (supportsUserTiming) {
189+
const alternate = fiber.alternate;
181190
let selfTime: number = (fiber.actualDuration: any);
182-
if (fiber.alternate === null || fiber.alternate.child !== fiber.child) {
191+
if (alternate === null || alternate.child !== fiber.child) {
183192
for (let child = fiber.child; child !== null; child = child.sibling) {
184193
selfTime -= (child.actualDuration: any);
185194
}
@@ -200,6 +209,36 @@ export function logComponentRender(
200209
: 'error';
201210
const debugTask = fiber._debugTask;
202211
if (__DEV__ && debugTask) {
212+
const props = fiber.memoizedProps;
213+
if (
214+
props !== null &&
215+
alternate !== null &&
216+
alternate.memoizedProps !== props
217+
) {
218+
// If this is an update, we'll diff the props and emit which ones changed.
219+
const properties: Array<[string, string]> = [resuableChangedPropsEntry];
220+
addObjectDiffToProperties(
221+
alternate.memoizedProps,
222+
props,
223+
properties,
224+
0,
225+
);
226+
if (properties.length > 1) {
227+
reusableComponentOptions.start = startTime;
228+
reusableComponentOptions.end = endTime;
229+
reusableComponentDevToolDetails.color = color;
230+
reusableComponentDevToolDetails.properties = properties;
231+
debugTask.run(
232+
// $FlowFixMe[method-unbinding]
233+
performance.measure.bind(
234+
performance,
235+
name,
236+
reusableComponentOptions,
237+
),
238+
);
239+
return;
240+
}
241+
}
203242
debugTask.run(
204243
// $FlowFixMe[method-unbinding]
205244
console.timeStamp.bind(
@@ -237,12 +276,7 @@ export function logComponentErrored(
237276
// Skip
238277
return;
239278
}
240-
if (
241-
__DEV__ &&
242-
typeof performance !== 'undefined' &&
243-
// $FlowFixMe[method-unbinding]
244-
typeof performance.measure === 'function'
245-
) {
279+
if (__DEV__) {
246280
let debugTask: ?ConsoleTask = null;
247281
const properties: Array<[string, string]> = [];
248282
for (let i = 0; i < errors.length; i++) {
@@ -267,10 +301,10 @@ export function logComponentErrored(
267301
properties.push(['Error', message]);
268302
}
269303
if (fiber.key !== null) {
270-
addValueToProperties('key', fiber.key, properties, 0);
304+
addValueToProperties('key', fiber.key, properties, 0, '');
271305
}
272306
if (fiber.memoizedProps !== null) {
273-
addObjectToProperties(fiber.memoizedProps, properties, 0);
307+
addObjectToProperties(fiber.memoizedProps, properties, 0, '');
274308
}
275309
if (debugTask == null) {
276310
// If the captured values don't have a debug task, fallback to the
@@ -325,12 +359,7 @@ function logComponentEffectErrored(
325359
// Skip
326360
return;
327361
}
328-
if (
329-
__DEV__ &&
330-
typeof performance !== 'undefined' &&
331-
// $FlowFixMe[method-unbinding]
332-
typeof performance.measure === 'function'
333-
) {
362+
if (__DEV__) {
334363
const properties: Array<[string, string]> = [];
335364
for (let i = 0; i < errors.length; i++) {
336365
const capturedValue = errors[i];
@@ -346,10 +375,10 @@ function logComponentEffectErrored(
346375
properties.push(['Error', message]);
347376
}
348377
if (fiber.key !== null) {
349-
addValueToProperties('key', fiber.key, properties, 0);
378+
addValueToProperties('key', fiber.key, properties, 0, '');
350379
}
351380
if (fiber.memoizedProps !== null) {
352-
addObjectToProperties(fiber.memoizedProps, properties, 0);
381+
addObjectToProperties(fiber.memoizedProps, properties, 0, '');
353382
}
354383
const options = {
355384
start: startTime,
@@ -815,12 +844,7 @@ export function logRecoveredRenderPhase(
815844
hydrationFailed: boolean,
816845
): void {
817846
if (supportsUserTiming) {
818-
if (
819-
__DEV__ &&
820-
typeof performance !== 'undefined' &&
821-
// $FlowFixMe[method-unbinding]
822-
typeof performance.measure === 'function'
823-
) {
847+
if (__DEV__) {
824848
const properties: Array<[string, string]> = [];
825849
for (let i = 0; i < recoverableErrors.length; i++) {
826850
const capturedValue = recoverableErrors[i];
@@ -939,12 +963,7 @@ export function logCommitErrored(
939963
passive: boolean,
940964
): void {
941965
if (supportsUserTiming) {
942-
if (
943-
__DEV__ &&
944-
typeof performance !== 'undefined' &&
945-
// $FlowFixMe[method-unbinding]
946-
typeof performance.measure === 'function'
947-
) {
966+
if (__DEV__) {
948967
const properties: Array<[string, string]> = [];
949968
for (let i = 0; i < errors.length; i++) {
950969
const capturedValue = errors[i];
@@ -997,8 +1016,6 @@ export function logCommitPhase(
9971016
return;
9981017
}
9991018
if (supportsUserTiming) {
1000-
reusableLaneOptions.start = startTime;
1001-
reusableLaneOptions.end = endTime;
10021019
console.timeStamp(
10031020
'Commit',
10041021
startTime,

0 commit comments

Comments
 (0)