[Perf Tracks]: Clear potentially large measures (#34803)
Fixes https://github.com/facebook/react/issues/34770. We need to clear measures at some point, otherwise all these copies of props that we end up recording will allocate too much memory in Chromium. This adds `performance.clearMeasures(...)` calls to such cases in DEV. Validated that entries are still shown on Performance panel timeline.
Ruslan Lesiutin committed
Oct 13, 2025 at 17:42 UTC
b9ec735de248f46da181afbc12aa906422be0dba
2 files changed
+48
-18
packages/react-client/src/ReactFlightPerformanceTrack.js
+27
-10
@@ -102,6 +102,7 @@ export function logComponentRender(
102
const entryName =
103
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
104
const debugTask = componentInfo.debugTask;
105
+ const measureName = '\u200b' + entryName;
106
if (__DEV__ && debugTask) {
107
const properties: Array<[string, string]> = [];
108
if (componentInfo.key != null) {
@@ -110,9 +111,10 @@ export function logComponentRender(
111
if (componentInfo.props != null) {
112
addObjectToProperties(componentInfo.props, properties, 0, '');
113
}
114
+
115
debugTask.run(
116
// $FlowFixMe[method-unbinding]
115
- performance.measure.bind(performance, '\u200b' + entryName, {
117
+ performance.measure.bind(performance, measureName, {
118
start: startTime < 0 ? 0 : startTime,
119
end: childrenEndTime,
120
detail: {
@@ -125,9 +127,10 @@ export function logComponentRender(
127
},
128
}),
129
);
130
+ performance.clearMeasures(measureName);
131
} else {
132
console.timeStamp(
130
- '\u200b' + entryName,
133
+ measureName,
134
startTime < 0 ? 0 : startTime,
135
childrenEndTime,
136
trackNames[trackIdx],
@@ -152,6 +155,7 @@ export function logComponentAborted(
155
const isPrimaryEnv = env === rootEnv;
156
const entryName =
157
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
158
+ const measureName = '\u200b' + entryName;
159
if (__DEV__) {
160
const properties: Array<[string, string]> = [
161
[
@@ -165,7 +169,8 @@ export function logComponentAborted(
169
if (componentInfo.props != null) {
170
addObjectToProperties(componentInfo.props, properties, 0, '');
171
}
168
- performance.measure('\u200b' + entryName, {
172
+
173
+ performance.measure(measureName, {
174
start: startTime < 0 ? 0 : startTime,
175
end: childrenEndTime,
176
detail: {
@@ -178,9 +183,10 @@ export function logComponentAborted(
183
},
184
},
185
});
186
+ performance.clearMeasures(measureName);
187
} else {
188
console.timeStamp(
183
- entryName,
189
+ measureName,
190
startTime < 0 ? 0 : startTime,
191
childrenEndTime,
192
trackNames[trackIdx],
@@ -206,6 +212,7 @@ export function logComponentErrored(
212
const isPrimaryEnv = env === rootEnv;
213
const entryName =
214
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
215
+ const measureName = '\u200b' + entryName;
216
if (__DEV__) {
217
const message =
218
typeof error === 'object' &&
@@ -222,7 +229,8 @@ export function logComponentErrored(
229
if (componentInfo.props != null) {
230
addObjectToProperties(componentInfo.props, properties, 0, '');
231
}
225
- performance.measure('\u200b' + entryName, {
232
+
233
+ performance.measure(measureName, {
234
start: startTime < 0 ? 0 : startTime,
235
end: childrenEndTime,
236
detail: {
@@ -235,9 +243,10 @@ export function logComponentErrored(
243
},
244
},
245
});
246
+ performance.clearMeasures(measureName);
247
} else {
248
console.timeStamp(
240
- entryName,
249
+ measureName,
250
startTime < 0 ? 0 : startTime,
251
childrenEndTime,
252
trackNames[trackIdx],
@@ -397,6 +406,7 @@ export function logComponentAwaitAborted(
406
},
407
}),
408
);
409
+ performance.clearMeasures(entryName);
410
} else {
411
console.timeStamp(
412
entryName,
@@ -453,6 +463,7 @@ export function logComponentAwaitErrored(
463
},
464
}),
465
);
466
+ performance.clearMeasures(entryName);
467
} else {
468
console.timeStamp(
469
entryName,
@@ -514,6 +525,7 @@ export function logComponentAwait(
525
},
526
}),
527
);
528
+ performance.clearMeasures(entryName);
529
} else {
530
console.timeStamp(
531
entryName,
@@ -538,6 +550,7 @@ export function logIOInfoErrored(
550
const description = getIODescription(error);
551
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
552
const debugTask = ioInfo.debugTask;
553
+ const measureName = '\u200b' + entryName;
554
if (__DEV__ && debugTask) {
555
const message =
556
typeof error === 'object' &&
@@ -550,9 +563,10 @@ export function logIOInfoErrored(
563
const properties = [['rejected with', message]];
564
const tooltipText =
565
getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected';
566
+
567
debugTask.run(
568
// $FlowFixMe[method-unbinding]
555
- performance.measure.bind(performance, '\u200b' + entryName, {
569
+ performance.measure.bind(performance, measureName, {
570
start: startTime < 0 ? 0 : startTime,
571
end: endTime,
572
detail: {
@@ -565,9 +579,10 @@ export function logIOInfoErrored(
579
},
580
}),
581
);
582
+ performance.clearMeasures(measureName);
583
} else {
584
console.timeStamp(
570
- entryName,
585
+ measureName,
586
startTime < 0 ? 0 : startTime,
587
endTime,
588
IO_TRACK,
@@ -590,6 +605,7 @@ export function logIOInfo(
605
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
606
const color = getIOColor(entryName);
607
const debugTask = ioInfo.debugTask;
608
+ const measureName = '\u200b' + entryName;
609
if (__DEV__ && debugTask) {
610
const properties: Array<[string, string]> = [];
611
if (typeof value === 'object' && value !== null) {
@@ -605,7 +621,7 @@ export function logIOInfo(
621
);
622
debugTask.run(
623
// $FlowFixMe[method-unbinding]
608
- performance.measure.bind(performance, '\u200b' + entryName, {
624
+ performance.measure.bind(performance, measureName, {
625
start: startTime < 0 ? 0 : startTime,
626
end: endTime,
627
detail: {
@@ -618,9 +634,10 @@ export function logIOInfo(
634
},
635
}),
636
);
637
+ performance.clearMeasures(measureName);
638
} else {
639
console.timeStamp(
623
- entryName,
640
+ measureName,
641
startTime < 0 ? 0 : startTime,
642
endTime,
643
IO_TRACK,
packages/react-reconciler/src/ReactFiberPerformanceTrack.js
+21
-8
@@ -133,6 +133,7 @@ function logComponentTrigger(
133
} else {
134
performance.measure(trigger, reusableComponentOptions);
135
}
136
+ performance.clearMeasures(trigger);
137
}
138
}
139
@@ -200,7 +201,7 @@ const reusableComponentOptions: PerformanceMeasureOptions = {
201
},
202
};
203
203
-const resuableChangedPropsEntry = ['Changed Props', ''];
204
+const reusableChangedPropsEntry = ['Changed Props', ''];
205
206
const DEEP_EQUALITY_WARNING =
207
'This component received deeply equal props. It might benefit from useMemo or the React Compiler in its owner.';
@@ -261,7 +262,7 @@ export function logComponentRender(
262
alternate.memoizedProps !== props
263
) {
264
// If this is an update, we'll diff the props and emit which ones changed.
264
- const properties: Array<[string, string]> = [resuableChangedPropsEntry];
265
+ const properties: Array<[string, string]> = [reusableChangedPropsEntry];
266
const isDeeplyEqual = addObjectDiffToProperties(
267
alternate.memoizedProps,
268
props,
@@ -293,18 +294,20 @@ export function logComponentRender(
294
reusableComponentOptions.start = startTime;
295
reusableComponentOptions.end = endTime;
296
297
+ const measureName = '\u200b' + name;
298
if (debugTask != null) {
299
debugTask.run(
300
// $FlowFixMe[method-unbinding]
301
performance.measure.bind(
302
performance,
301
- '\u200b' + name,
303
+ measureName,
304
reusableComponentOptions,
305
),
306
);
307
} else {
306
- performance.measure('\u200b' + name, reusableComponentOptions);
308
+ performance.measure(measureName, reusableComponentOptions);
309
}
310
+ performance.clearMeasures(measureName);
311
} else {
312
if (debugTask != null) {
313
debugTask.run(
@@ -421,14 +424,17 @@ export function logComponentErrored(
424
},
425
},
426
};
427
+
428
+ const measureName = '\u200b' + name;
429
if (__DEV__ && debugTask) {
430
debugTask.run(
431
// $FlowFixMe[method-unbinding]
427
- performance.measure.bind(performance, '\u200b' + name, options),
432
+ performance.measure.bind(performance, measureName, options),
433
);
434
} else {
430
- performance.measure('\u200b' + name, options);
435
+ performance.measure(measureName, options);
436
}
437
+ performance.clearMeasures(measureName);
438
} else {
439
console.timeStamp(
440
name,
@@ -488,14 +494,16 @@ function logComponentEffectErrored(
494
},
495
};
496
const debugTask = fiber._debugTask;
497
+ const measureName = '\u200b' + name;
498
if (debugTask) {
499
debugTask.run(
500
// $FlowFixMe[method-unbinding]
494
- performance.measure.bind(performance, '\u200b' + name, options),
501
+ performance.measure.bind(performance, measureName, options),
502
);
503
} else {
497
- performance.measure('\u200b' + name, options);
504
+ performance.measure(measureName, options);
505
}
506
+ performance.clearMeasures(measureName);
507
} else {
508
console.timeStamp(
509
name,
@@ -762,6 +770,7 @@ export function logBlockingStart(
770
} else {
771
performance.measure(label, measureOptions);
772
}
773
+ performance.clearMeasures(label);
774
} else {
775
console.timeStamp(
776
label,
@@ -867,6 +876,7 @@ export function logGestureStart(
876
} else {
877
performance.measure(label, measureOptions);
878
}
879
+ performance.clearMeasures(label);
880
} else {
881
console.timeStamp(
882
label,
@@ -1007,6 +1017,7 @@ export function logTransitionStart(
1017
} else {
1018
performance.measure(label, measureOptions);
1019
}
1020
+ performance.clearMeasures(label);
1021
} else {
1022
console.timeStamp(
1023
label,
@@ -1238,6 +1249,7 @@ export function logRecoveredRenderPhase(
1249
} else {
1250
performance.measure('Recovered', options);
1251
}
1252
+ performance.clearMeasures('Recovered');
1253
} else {
1254
console.timeStamp(
1255
'Recovered',
@@ -1449,6 +1461,7 @@ export function logCommitErrored(
1461
} else {
1462
performance.measure('Errored', options);
1463
}
1464
+ performance.clearMeasures('Errored');
1465
} else {
1466
console.timeStamp(
1467
'Errored',