@samitouri / QOS-React / commits / b9ec735de2

[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',