Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fiber] Log Component Renders to Custom Performance Track #30967

Merged
merged 8 commits into from
Sep 16, 2024
74 changes: 66 additions & 8 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
enableUseEffectEventHook,
enableLegacyHidden,
disableLegacyMode,
enableComponentPerformanceTrack,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
Expand Down Expand Up @@ -102,7 +103,9 @@ import {
getCommitTime,
recordLayoutEffectDuration,
startLayoutEffectTimer,
getCompleteTime,
} from './ReactProfilerTimer';
import {logComponentRender} from './ReactFiberPerformanceTrack';
import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';
import {
Expand Down Expand Up @@ -2648,6 +2651,9 @@ export function commitPassiveMountEffects(
finishedWork,
committedLanes,
committedTransitions,
enableProfilerTimer && enableComponentPerformanceTrack
? getCompleteTime()
: 0,
);
}

Expand All @@ -2656,17 +2662,41 @@ function recursivelyTraversePassiveMountEffects(
parentFiber: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
) {
if (parentFiber.subtreeFlags & PassiveMask) {
if (
parentFiber.subtreeFlags & PassiveMask ||
// If this subtree rendered with profiling this commit, we need to visit it to log it.
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
parentFiber.actualDuration !== 0 &&
(parentFiber.alternate === null ||
parentFiber.alternate.child !== parentFiber.child))
) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveMountOnFiber(
root,
child,
committedLanes,
committedTransitions,
);
child = child.sibling;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const nextSibling = child.sibling;
commitPassiveMountOnFiber(
root,
child,
committedLanes,
committedTransitions,
nextSibling !== null
? ((nextSibling.actualStartTime: any): number)
: endTime,
);
child = nextSibling;
} else {
commitPassiveMountOnFiber(
root,
child,
committedLanes,
committedTransitions,
0,
);
child = child.sibling;
}
}
}
}
Expand All @@ -2676,7 +2706,25 @@ function commitPassiveMountOnFiber(
finishedWork: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
): void {
// 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
) {
logComponentRender(
finishedWork,
((finishedWork.actualStartTime: any): number),
endTime,
);
}

// When updating this function, also update reconnectPassiveEffects, which does
// most of the same things when an offscreen tree goes from hidden -> visible,
// or when toggling effects inside a hidden tree.
Expand All @@ -2690,6 +2738,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
commitHookPassiveMountEffects(
Expand All @@ -2705,6 +2754,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
if (enableCache) {
Expand Down Expand Up @@ -2762,6 +2812,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);

// Only Profilers with work in their subtree will have a Passive effect scheduled.
Expand Down Expand Up @@ -2809,6 +2860,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);

if (flags & Passive) {
Expand All @@ -2834,6 +2886,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
} else {
if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
Expand All @@ -2858,6 +2911,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
}
}
Expand All @@ -2870,6 +2924,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
} else {
// The effects are currently disconnected. Reconnect them, while also
Expand Down Expand Up @@ -2901,6 +2956,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
// TODO: Pass `current` as argument to this function
Expand All @@ -2916,6 +2972,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
commitTracingMarkerPassiveMountEffect(finishedWork);
Expand All @@ -2930,6 +2987,7 @@ function commitPassiveMountOnFiber(
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
break;
}
Expand Down
32 changes: 32 additions & 0 deletions packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,35 @@ export function clearTransitionsForLanes(root: FiberRoot, lanes: Lane | Lanes) {
lanes &= ~lane;
}
}

// Used to name the Performance Track
export function getGroupNameOfHighestPriorityLane(lanes: Lanes): string {
if (
lanes &
(SyncHydrationLane |
SyncLane |
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane)
) {
return 'Blocking';
}
if (lanes & (TransitionHydrationLane | TransitionLanes)) {
return 'Transition';
}
if (lanes & RetryLanes) {
return 'Suspense';
}
if (
lanes &
(SelectiveHydrationLane |
IdleHydrationLane |
IdleLane |
OffscreenLane |
DeferredLane)
) {
return 'Idle';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you thinking about splitting 'hidden' out for offscreen?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's actually quite a lot of "Idle" work like hydration and the prerender. I think we'll actually put other lanes here too that end up suspending because that work weren't necessarily blocking anything - the suspended thing was.

There's too much nuance to communicate here but the one thing they have in common is that it's not directly connected to an outcome like a navigation. They're all warming up for some future work and so you shouldn't be too concerned about them taking a long time. They're red herrings.

}
return 'Other';
}
61 changes: 61 additions & 0 deletions packages/react-reconciler/src/ReactFiberPerformanceTrack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Fiber} from './ReactInternalTypes';

import getComponentNameFromFiber from './getComponentNameFromFiber';

import {getGroupNameOfHighestPriorityLane} from './ReactFiberLane';

import {enableProfilerTimer} from 'shared/ReactFeatureFlags';

const supportsUserTiming =
enableProfilerTimer &&
typeof performance !== 'undefined' &&
// $FlowFixMe[method-unbinding]
typeof performance.measure === 'function';

const TRACK_GROUP = 'Components ⚛';

// Reused to avoid thrashing the GC.
const reusableComponentDevToolDetails = {
dataType: 'track-entry',
color: 'primary',
track: 'Blocking', // Lane
trackGroup: TRACK_GROUP,
};
const reusableComponentOptions = {
start: -0,
end: -0,
detail: {
devtools: reusableComponentDevToolDetails,
},
};

export function setCurrentTrackFromLanes(lanes: number): void {
reusableComponentDevToolDetails.track =
getGroupNameOfHighestPriorityLane(lanes);
}

export function logComponentRender(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
const name = getComponentNameFromFiber(fiber);
if (name === null) {
// Skip
return;
}
if (supportsUserTiming) {
reusableComponentOptions.start = startTime;
reusableComponentOptions.end = endTime;
performance.measure(name, reusableComponentOptions);
}
}
Loading
Loading