From 996915b7c577213040d893837d3d350f395eb7f7 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 14 Dec 2024 22:20:26 -0500 Subject: [PATCH 1/2] Log component component effect timings for disconnected/reconnected offscreen subtrees This includes initial mount of a Suspense boundary. --- .../src/ReactFiberCommitWork.js | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index df16eb96ef216..43990eea60d5b 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -2209,6 +2209,8 @@ function recursivelyTraverseLayoutEffects( } export function disappearLayoutEffects(finishedWork: Fiber) { + const prevEffectStart = pushComponentEffectStart(); + switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: @@ -2266,6 +2268,25 @@ export function disappearLayoutEffects(finishedWork: Fiber) { break; } } + + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + enableComponentPerformanceTrack && + (finishedWork.mode & ProfileMode) !== NoMode && + componentEffectStartTime >= 0 && + componentEffectEndTime >= 0 && + componentEffectDuration > 0.05 + ) { + logComponentEffect( + finishedWork, + componentEffectStartTime, + componentEffectEndTime, + componentEffectDuration, + ); + } + + popComponentEffectStart(prevEffectStart); } function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) { @@ -2286,6 +2307,8 @@ export function reappearLayoutEffects( // node was reused. includeWorkInProgressEffects: boolean, ) { + const prevEffectStart = pushComponentEffectStart(); + // Turn on layout effects in a tree that previously disappeared. const flags = finishedWork.flags; switch (finishedWork.tag) { @@ -2421,6 +2444,25 @@ export function reappearLayoutEffects( break; } } + + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + enableComponentPerformanceTrack && + (finishedWork.mode & ProfileMode) !== NoMode && + componentEffectStartTime >= 0 && + componentEffectEndTime >= 0 && + componentEffectDuration > 0.05 + ) { + logComponentEffect( + finishedWork, + componentEffectStartTime, + componentEffectEndTime, + componentEffectDuration, + ); + } + + popComponentEffectStart(prevEffectStart); } function recursivelyTraverseReappearLayoutEffects( @@ -2995,6 +3037,8 @@ export function reconnectPassiveEffects( // node was reused. includeWorkInProgressEffects: boolean, ) { + const prevEffectStart = pushComponentEffectStart(); + const flags = finishedWork.flags; switch (finishedWork.tag) { case FunctionComponent: @@ -3145,6 +3189,25 @@ export function reconnectPassiveEffects( break; } } + + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + enableComponentPerformanceTrack && + (finishedWork.mode & ProfileMode) !== NoMode && + componentEffectStartTime >= 0 && + componentEffectEndTime >= 0 && + componentEffectDuration > 0.05 + ) { + logComponentEffect( + finishedWork, + componentEffectStartTime, + componentEffectEndTime, + componentEffectDuration, + ); + } + + popComponentEffectStart(prevEffectStart); } function recursivelyTraverseAtomicPassiveEffects( @@ -3591,6 +3654,8 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber( current: Fiber, nearestMountedAncestor: Fiber | null, ): void { + const prevEffectStart = pushComponentEffectStart(); + switch (current.tag) { case FunctionComponent: case ForwardRef: @@ -3702,6 +3767,25 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber( break; } } + + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + enableComponentPerformanceTrack && + (current.mode & ProfileMode) !== NoMode && + componentEffectStartTime >= 0 && + componentEffectEndTime >= 0 && + componentEffectDuration > 0.05 + ) { + logComponentEffect( + current, + componentEffectStartTime, + componentEffectEndTime, + componentEffectDuration, + ); + } + + popComponentEffectStart(prevEffectStart); } export function invokeLayoutEffectMountInDEV(fiber: Fiber): void { From c18813c2ca910653e03102521e00c62a35ed5c30 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 14 Dec 2024 22:28:34 -0500 Subject: [PATCH 2/2] Log component render timings for reconnected and already offscreen offscreen subtrees --- .../src/ReactFiberCommitWork.js | 113 +++++++++++++++--- .../src/ReactFiberWorkLoop.js | 2 +- 2 files changed, 99 insertions(+), 16 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 43990eea60d5b..83922deed4b26 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -2888,6 +2888,7 @@ function commitPassiveMountOnFiber( finishedWork, committedLanes, committedTransitions, + endTime, ); } else { // Legacy Mode: Fire the effects even if the tree is hidden. @@ -2926,6 +2927,7 @@ function commitPassiveMountOnFiber( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); } } @@ -3005,6 +3007,7 @@ function recursivelyTraverseReconnectPassiveEffects( committedLanes: Lanes, committedTransitions: Array | null, includeWorkInProgressEffects: boolean, + endTime: number, ) { // This function visits both newly finished work and nodes that were re-used // from a previously committed tree. We cannot check non-static flags if the @@ -3016,14 +3019,30 @@ function recursivelyTraverseReconnectPassiveEffects( // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) let child = parentFiber.child; while (child !== null) { - reconnectPassiveEffects( - finishedRoot, - child, - committedLanes, - committedTransitions, - childShouldIncludeWorkInProgressEffects, - ); - child = child.sibling; + if (enableProfilerTimer && enableComponentPerformanceTrack) { + const nextSibling = child.sibling; + reconnectPassiveEffects( + finishedRoot, + child, + committedLanes, + committedTransitions, + childShouldIncludeWorkInProgressEffects, + nextSibling !== null + ? ((nextSibling.actualStartTime: any): number) + : endTime, + ); + child = nextSibling; + } else { + reconnectPassiveEffects( + finishedRoot, + child, + committedLanes, + committedTransitions, + childShouldIncludeWorkInProgressEffects, + endTime, + ); + child = child.sibling; + } } } @@ -3036,9 +3055,28 @@ export function reconnectPassiveEffects( // from a previously committed tree. We cannot check non-static flags if the // node was reused. includeWorkInProgressEffects: boolean, + endTime: number, // Profiling-only. The start time of the next Fiber or root completion. ) { const prevEffectStart = pushComponentEffectStart(); + // If this component rendered in Profiling mode (DEV or in Profiler component) then log its + // render time. We do this after the fact in the passive effect to avoid the overhead of this + // getting in the way of the render characteristics and avoid the overhead of unwinding + // uncommitted renders. + if ( + enableProfilerTimer && + enableComponentPerformanceTrack && + (finishedWork.mode & ProfileMode) !== NoMode && + ((finishedWork.actualStartTime: any): number) > 0 && + (finishedWork.flags & PerformedWork) !== NoFlags + ) { + logComponentRender( + finishedWork, + ((finishedWork.actualStartTime: any): number), + endTime, + ); + } + const flags = finishedWork.flags; switch (finishedWork.tag) { case FunctionComponent: @@ -3050,6 +3088,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); // TODO: Check for PassiveStatic flag commitHookPassiveMountEffects(finishedWork, HookPassive); @@ -3069,6 +3108,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); if (includeWorkInProgressEffects && flags & Passive) { @@ -3095,6 +3135,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); } else { if (disableLegacyMode || finishedWork.mode & ConcurrentMode) { @@ -3108,6 +3149,7 @@ export function reconnectPassiveEffects( finishedWork, committedLanes, committedTransitions, + endTime, ); } else { // Legacy Mode: Fire the effects even if the tree is hidden. @@ -3118,6 +3160,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); } } @@ -3137,6 +3180,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); } @@ -3154,6 +3198,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); if (includeWorkInProgressEffects && flags & Passive) { // TODO: Pass `current` as argument to this function @@ -3170,6 +3215,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); if (includeWorkInProgressEffects && flags & Passive) { commitTracingMarkerPassiveMountEffect(finishedWork); @@ -3185,6 +3231,7 @@ export function reconnectPassiveEffects( committedLanes, committedTransitions, includeWorkInProgressEffects, + endTime, ); break; } @@ -3215,6 +3262,7 @@ function recursivelyTraverseAtomicPassiveEffects( parentFiber: Fiber, committedLanes: Lanes, committedTransitions: Array | null, + endTime: number, // Profiling-only. The start time of the next Fiber or root completion. ) { // "Atomic" effects are ones that need to fire on every commit, even during // pre-rendering. We call this function when traversing a hidden tree whose @@ -3223,13 +3271,28 @@ function recursivelyTraverseAtomicPassiveEffects( if (parentFiber.subtreeFlags & PassiveMask) { let child = parentFiber.child; while (child !== null) { - commitAtomicPassiveEffects( - finishedRoot, - child, - committedLanes, - committedTransitions, - ); - child = child.sibling; + if (enableProfilerTimer && enableComponentPerformanceTrack) { + const nextSibling = child.sibling; + commitAtomicPassiveEffects( + finishedRoot, + child, + committedLanes, + committedTransitions, + nextSibling !== null + ? ((nextSibling.actualStartTime: any): number) + : endTime, + ); + child = nextSibling; + } else { + commitAtomicPassiveEffects( + finishedRoot, + child, + committedLanes, + committedTransitions, + endTime, + ); + child = child.sibling; + } } } } @@ -3239,7 +3302,24 @@ function commitAtomicPassiveEffects( finishedWork: Fiber, committedLanes: Lanes, committedTransitions: Array | null, + endTime: number, // Profiling-only. The start time of the next Fiber or root completion. ) { + // If this component rendered in Profiling mode (DEV or in Profiler component) then log its + // render time. A render can happen even if the subtree is offscreen. + if ( + enableProfilerTimer && + enableComponentPerformanceTrack && + (finishedWork.mode & ProfileMode) !== NoMode && + ((finishedWork.actualStartTime: any): number) > 0 && + (finishedWork.flags & PerformedWork) !== NoFlags + ) { + logComponentRender( + finishedWork, + ((finishedWork.actualStartTime: any): number), + endTime, + ); + } + // "Atomic" effects are ones that need to fire on every commit, even during // pre-rendering. We call this function when traversing a hidden tree whose // regular effects are currently disconnected. @@ -3251,6 +3331,7 @@ function commitAtomicPassiveEffects( finishedWork, committedLanes, committedTransitions, + endTime, ); if (flags & Passive) { // TODO: Pass `current` as argument to this function @@ -3266,6 +3347,7 @@ function commitAtomicPassiveEffects( finishedWork, committedLanes, committedTransitions, + endTime, ); if (flags & Passive) { // TODO: Pass `current` as argument to this function @@ -3280,6 +3362,7 @@ function commitAtomicPassiveEffects( finishedWork, committedLanes, committedTransitions, + endTime, ); break; } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 9a002ce4b6fd9..f0dcbc607d561 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -4107,7 +4107,7 @@ function doubleInvokeEffectsOnFiber( } reappearLayoutEffects(root, fiber.alternate, fiber, false); if (shouldDoubleInvokePassiveEffects) { - reconnectPassiveEffects(root, fiber, NoLanes, null, false); + reconnectPassiveEffects(root, fiber, NoLanes, null, false, 0); } } finally { setIsStrictModeForDevtools(false);