@samitouri / QOS-React / commits / 063394cf82

[Perf Tracks]: Always log effect that spawned blocking update (#34648)

We've observed some scenarios, where cascading update happens in an effect that was shorter than 0.05ms. In this case, this effect won't be displayed on a timeline, because of the threshold that we are using, but it would be shown in entry properties or in a stack trace. To avoid confusion, we should always log such effects. Validated via manually changing the threshold to 100ms+ and observing that only effects that triggered an update are visible on a timeline.

Ruslan Lesiutin committed Sep 30, 2025 at 20:05 UTC 063394cf821e5082e834c72ffb9cf6f8575c9b34
2 files changed +53 -10
packages/react-reconciler/src/ReactFiberCommitWork.js
+33 -10
@@ -130,10 +130,13 @@ import {
130 popComponentEffectDuration,
131 pushComponentEffectErrors,
132 popComponentEffectErrors,
133 + pushComponentEffectDidSpawnUpdate,
134 + popComponentEffectDidSpawnUpdate,
135 componentEffectStartTime,
136 componentEffectEndTime,
137 componentEffectDuration,
138 componentEffectErrors,
139 + componentEffectSpawnedUpdate,
140 } from './ReactProfilerTimer';
141 import {
142 logComponentRender,
@@ -595,6 +598,7 @@ function commitLayoutEffectOnFiber(
598 const prevEffectStart = pushComponentEffectStart();
599 const prevEffectDuration = pushComponentEffectDuration();
600 const prevEffectErrors = pushComponentEffectErrors();
601 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
602 // When updating this function, also update reappearLayoutEffects, which does
603 // most of the same things when an offscreen tree goes from hidden -> visible.
604 const flags = finishedWork.flags;
@@ -876,7 +880,7 @@ function commitLayoutEffectOnFiber(
880 componentEffectStartTime >= 0 &&
881 componentEffectEndTime >= 0
882 ) {
879 - if (componentEffectDuration > 0.05) {
883 + if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
884 logComponentEffect(
885 finishedWork,
886 componentEffectStartTime,
@@ -909,6 +913,7 @@ function commitLayoutEffectOnFiber(
913 popComponentEffectStart(prevEffectStart);
914 popComponentEffectDuration(prevEffectDuration);
915 popComponentEffectErrors(prevEffectErrors);
916 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
917 }
918
919 function abortRootTransitions(
@@ -1430,6 +1435,7 @@ function commitDeletionEffectsOnFiber(
1435 const prevEffectStart = pushComponentEffectStart();
1436 const prevEffectDuration = pushComponentEffectDuration();
1437 const prevEffectErrors = pushComponentEffectErrors();
1438 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
1439
1440 // The cases in this outer switch modify the stack before they traverse
1441 // into their subtree. There are simpler cases in the inner switch
@@ -1750,7 +1756,7 @@ function commitDeletionEffectsOnFiber(
1756 (deletedFiber.mode & ProfileMode) !== NoMode &&
1757 componentEffectStartTime >= 0 &&
1758 componentEffectEndTime >= 0 &&
1753 - componentEffectDuration > 0.05
1759 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
1760 ) {
1761 logComponentEffect(
1762 deletedFiber,
@@ -1764,6 +1770,7 @@ function commitDeletionEffectsOnFiber(
1770 popComponentEffectStart(prevEffectStart);
1771 popComponentEffectDuration(prevEffectDuration);
1772 popComponentEffectErrors(prevEffectErrors);
1773 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
1774 }
1775
1776 function commitSuspenseCallback(finishedWork: Fiber) {
@@ -1987,6 +1994,7 @@ function commitMutationEffectsOnFiber(
1994 const prevEffectStart = pushComponentEffectStart();
1995 const prevEffectDuration = pushComponentEffectDuration();
1996 const prevEffectErrors = pushComponentEffectErrors();
1997 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
1998 const current = finishedWork.alternate;
1999 const flags = finishedWork.flags;
2000
@@ -2611,7 +2619,7 @@ function commitMutationEffectsOnFiber(
2619 componentEffectStartTime >= 0 &&
2620 componentEffectEndTime >= 0
2621 ) {
2614 - if (componentEffectDuration > 0.05) {
2622 + if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
2623 logComponentEffect(
2624 finishedWork,
2625 componentEffectStartTime,
@@ -2644,6 +2652,7 @@ function commitMutationEffectsOnFiber(
2652 popComponentEffectStart(prevEffectStart);
2653 popComponentEffectDuration(prevEffectDuration);
2654 popComponentEffectErrors(prevEffectErrors);
2655 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
2656 }
2657
2658 function commitReconciliationEffects(
@@ -2900,6 +2909,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
2909 const prevEffectStart = pushComponentEffectStart();
2910 const prevEffectDuration = pushComponentEffectDuration();
2911 const prevEffectErrors = pushComponentEffectErrors();
2912 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
2913 switch (finishedWork.tag) {
2914 case FunctionComponent:
2915 case ForwardRef:
@@ -2990,7 +3000,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
3000 (finishedWork.mode & ProfileMode) !== NoMode &&
3001 componentEffectStartTime >= 0 &&
3002 componentEffectEndTime >= 0 &&
2993 - componentEffectDuration > 0.05
3003 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
3004 ) {
3005 logComponentEffect(
3006 finishedWork,
@@ -3004,6 +3014,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
3014 popComponentEffectStart(prevEffectStart);
3015 popComponentEffectDuration(prevEffectDuration);
3016 popComponentEffectErrors(prevEffectErrors);
3017 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
3018 }
3019
3020 function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
@@ -3027,6 +3038,7 @@ export function reappearLayoutEffects(
3038 const prevEffectStart = pushComponentEffectStart();
3039 const prevEffectDuration = pushComponentEffectDuration();
3040 const prevEffectErrors = pushComponentEffectErrors();
3041 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
3042 // Turn on layout effects in a tree that previously disappeared.
3043 const flags = finishedWork.flags;
3044 switch (finishedWork.tag) {
@@ -3224,7 +3236,7 @@ export function reappearLayoutEffects(
3236 (finishedWork.mode & ProfileMode) !== NoMode &&
3237 componentEffectStartTime >= 0 &&
3238 componentEffectEndTime >= 0 &&
3227 - componentEffectDuration > 0.05
3239 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
3240 ) {
3241 logComponentEffect(
3242 finishedWork,
@@ -3238,6 +3250,7 @@ export function reappearLayoutEffects(
3250 popComponentEffectStart(prevEffectStart);
3251 popComponentEffectDuration(prevEffectDuration);
3252 popComponentEffectErrors(prevEffectErrors);
3253 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
3254 }
3255
3256 function recursivelyTraverseReappearLayoutEffects(
@@ -3489,6 +3502,7 @@ function commitPassiveMountOnFiber(
3502 const prevEffectStart = pushComponentEffectStart();
3503 const prevEffectDuration = pushComponentEffectDuration();
3504 const prevEffectErrors = pushComponentEffectErrors();
3505 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
3506 const prevDeepEquality = pushDeepEquality();
3507
3508 const isViewTransitionEligible = enableViewTransition
@@ -4060,7 +4074,7 @@ function commitPassiveMountOnFiber(
4074 }
4075 }
4076 if (componentEffectStartTime >= 0 && componentEffectEndTime >= 0) {
4063 - if (componentEffectDuration > 0.05) {
4077 + if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
4078 logComponentEffect(
4079 finishedWork,
4080 componentEffectStartTime,
@@ -4082,6 +4096,7 @@ function commitPassiveMountOnFiber(
4096 popComponentEffectStart(prevEffectStart);
4097 popComponentEffectDuration(prevEffectDuration);
4098 popComponentEffectErrors(prevEffectErrors);
4099 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
4100 popDeepEquality(prevDeepEquality);
4101 }
4102
@@ -4144,6 +4159,7 @@ export function reconnectPassiveEffects(
4159 const prevEffectStart = pushComponentEffectStart();
4160 const prevEffectDuration = pushComponentEffectDuration();
4161 const prevEffectErrors = pushComponentEffectErrors();
4162 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
4163 const prevDeepEquality = pushDeepEquality();
4164
4165 // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
@@ -4334,7 +4350,7 @@ export function reconnectPassiveEffects(
4350 (finishedWork.mode & ProfileMode) !== NoMode &&
4351 componentEffectStartTime >= 0 &&
4352 componentEffectEndTime >= 0 &&
4337 - componentEffectDuration > 0.05
4353 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
4354 ) {
4355 logComponentEffect(
4356 finishedWork,
@@ -4348,6 +4364,7 @@ export function reconnectPassiveEffects(
4364 popComponentEffectStart(prevEffectStart);
4365 popComponentEffectDuration(prevEffectDuration);
4366 popComponentEffectErrors(prevEffectErrors);
4367 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
4368 popDeepEquality(prevDeepEquality);
4369 }
4370
@@ -4737,6 +4754,7 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
4754 const prevEffectStart = pushComponentEffectStart();
4755 const prevEffectDuration = pushComponentEffectDuration();
4756 const prevEffectErrors = pushComponentEffectErrors();
4757 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
4758 switch (finishedWork.tag) {
4759 case FunctionComponent:
4760 case ForwardRef:
@@ -4833,7 +4851,7 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
4851 (finishedWork.mode & ProfileMode) !== NoMode &&
4852 componentEffectStartTime >= 0 &&
4853 componentEffectEndTime >= 0 &&
4836 - componentEffectDuration > 0.05
4854 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
4855 ) {
4856 logComponentEffect(
4857 finishedWork,
@@ -4846,6 +4864,7 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
4864
4865 popComponentEffectStart(prevEffectStart);
4866 popComponentEffectDuration(prevEffectDuration);
4867 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
4868 popComponentEffectErrors(prevEffectErrors);
4869 }
4870
@@ -4903,6 +4922,7 @@ export function disconnectPassiveEffect(finishedWork: Fiber): void {
4922 const prevEffectStart = pushComponentEffectStart();
4923 const prevEffectDuration = pushComponentEffectDuration();
4924 const prevEffectErrors = pushComponentEffectErrors();
4925 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
4926
4927 switch (finishedWork.tag) {
4928 case FunctionComponent:
@@ -4942,7 +4962,7 @@ export function disconnectPassiveEffect(finishedWork: Fiber): void {
4962 (finishedWork.mode & ProfileMode) !== NoMode &&
4963 componentEffectStartTime >= 0 &&
4964 componentEffectEndTime >= 0 &&
4945 - componentEffectDuration > 0.05
4965 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
4966 ) {
4967 logComponentEffect(
4968 finishedWork,
@@ -4955,6 +4975,7 @@ export function disconnectPassiveEffect(finishedWork: Fiber): void {
4975
4976 popComponentEffectStart(prevEffectStart);
4977 popComponentEffectDuration(prevEffectDuration);
4978 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
4979 popComponentEffectErrors(prevEffectErrors);
4980 }
4981
@@ -5016,6 +5037,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
5037 const prevEffectStart = pushComponentEffectStart();
5038 const prevEffectDuration = pushComponentEffectDuration();
5039 const prevEffectErrors = pushComponentEffectErrors();
5040 + const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
5041 switch (current.tag) {
5042 case FunctionComponent:
5043 case ForwardRef:
@@ -5135,7 +5157,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
5157 (current.mode & ProfileMode) !== NoMode &&
5158 componentEffectStartTime >= 0 &&
5159 componentEffectEndTime >= 0 &&
5138 - componentEffectDuration > 0.05
5160 + (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
5161 ) {
5162 logComponentEffect(
5163 current,
@@ -5148,6 +5170,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
5170
5171 popComponentEffectStart(prevEffectStart);
5172 popComponentEffectDuration(prevEffectDuration);
5173 + popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
5174 popComponentEffectErrors(prevEffectErrors);
5175 }
5176
packages/react-reconciler/src/ReactProfilerTimer.js
+20
@@ -64,6 +64,7 @@ export let componentEffectDuration: number = -0;
64 export let componentEffectStartTime: number = -1.1;
65 export let componentEffectEndTime: number = -1.1;
66 export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
67 +export let componentEffectSpawnedUpdate: boolean = false;
68
69 export let blockingClampTime: number = -0;
70 export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
@@ -153,6 +154,7 @@ export function startUpdateTimerByLane(
154 blockingUpdateComponentName = getComponentNameFromFiber(fiber);
155 }
156 if (isAlreadyRendering()) {
157 + componentEffectSpawnedUpdate = true;
158 blockingUpdateType = SPAWNED_UPDATE;
159 }
160 const newEventTime = resolveEventTimeStamp();
@@ -495,6 +497,24 @@ export function popComponentEffectErrors(
497 componentEffectErrors = prevErrors;
498 }
499
500 +export function pushComponentEffectDidSpawnUpdate(): boolean {
501 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
502 + return false;
503 + }
504 +
505 + const prev = componentEffectSpawnedUpdate;
506 + componentEffectSpawnedUpdate = false; // Reset.
507 + return prev;
508 +}
509 +
510 +export function popComponentEffectDidSpawnUpdate(previousValue: boolean): void {
511 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
512 + return;
513 + }
514 +
515 + componentEffectSpawnedUpdate = previousValue;
516 +}
517 +
518 /**
519 * Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
520 *