Skip to content

Commit

Permalink
Track suspended time when the render doesn't commit because it suspended
Browse files Browse the repository at this point in the history
This stops whenever the same lane group starts rendering again.
Clamped by the preceeding start time/event time/update time.
  • Loading branch information
sebmarkbage committed Nov 15, 2024
1 parent 3da0519 commit 490a279
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 8 deletions.
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/ReactFiberPerformanceTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,19 @@ export function logSuspendedRenderPhase(
}
}

export function logSuspendedWithDelayPhase(
startTime: number,
endTime: number,
): void {
// This means the render was suspended and cannot commit until it gets unblocked.
if (supportsUserTiming) {
reusableLaneDevToolDetails.color = 'primary-dark';
reusableLaneOptions.start = startTime;
reusableLaneOptions.end = endTime;
performance.measure('Suspended', reusableLaneOptions);
}
}

export function logErroredRenderPhase(
startTime: number,
endTime: number,
Expand Down
55 changes: 47 additions & 8 deletions packages/react-reconciler/src/ReactFiberWorkLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
logSuspendedRenderPhase,
logErroredRenderPhase,
logInconsistentRender,
logSuspendedWithDelayPhase,
logSuspenseThrottlePhase,
logSuspendedCommitPhase,
logCommitPhase,
Expand Down Expand Up @@ -239,12 +240,14 @@ import {
blockingEventTime,
blockingEventType,
blockingEventIsRepeat,
blockingSuspendedTime,
transitionClampTime,
transitionStartTime,
transitionUpdateTime,
transitionEventTime,
transitionEventType,
transitionEventIsRepeat,
transitionSuspendedTime,
clearBlockingTimers,
clearTransitionTimers,
clampBlockingTimers,
Expand All @@ -260,6 +263,7 @@ import {
stopProfilerTimerIfRunningAndRecordDuration,
stopProfilerTimerIfRunningAndRecordIncompleteDuration,
markUpdateAsRepeat,
trackSuspendedTime,
} from './ReactProfilerTimer';
import {setCurrentTrackFromLanes} from './ReactFiberPerformanceTrack';

Expand Down Expand Up @@ -1144,7 +1148,7 @@ function finishConcurrentRender(
exitStatus: RootExitStatus,
finishedWork: Fiber,
lanes: Lanes,
renderEndTime: number // Profiling-only
renderEndTime: number, // Profiling-only
) {
// TODO: The fact that most of these branches are identical suggests that some
// of the exit statuses are not best modeled as exit statuses and should be
Expand All @@ -1169,6 +1173,7 @@ function finishConcurrentRender(
setCurrentTrackFromLanes(lanes);
logSuspendedRenderPhase(renderStartTime, renderEndTime);
finalizeRender(lanes, renderEndTime);
trackSuspendedTime(lanes, renderEndTime);
}
const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
markRootSuspended(
Expand Down Expand Up @@ -1704,30 +1709,64 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
}

if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
logBlockingStart(
const clampedUpdateTime =
blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
? blockingClampTime
: blockingUpdateTime,
: blockingUpdateTime;
const clampedEventTime =
blockingEventTime >= 0 && blockingEventTime < blockingClampTime
? blockingClampTime
: blockingEventTime,
: blockingEventTime;
if (blockingSuspendedTime >= 0) {
setCurrentTrackFromLanes(lanes);
logSuspendedWithDelayPhase(
blockingSuspendedTime,
// Clamp the suspended time to the first event/update.
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime,
);
}
logBlockingStart(
clampedUpdateTime,
clampedEventTime,
blockingEventType,
blockingEventIsRepeat,
renderStartTime,
);
clearBlockingTimers();
}
if (includesTransitionLane(lanes)) {
logTransitionStart(
const clampedStartTime =
transitionStartTime >= 0 && transitionStartTime < transitionClampTime
? transitionClampTime
: transitionStartTime,
: transitionStartTime;
const clampedUpdateTime =
transitionUpdateTime >= 0 && transitionUpdateTime < transitionClampTime
? transitionClampTime
: transitionUpdateTime,
: transitionUpdateTime;
const clampedEventTime =
transitionEventTime >= 0 && transitionEventTime < transitionClampTime
? transitionClampTime
: transitionEventTime,
: transitionEventTime;
if (transitionSuspendedTime >= 0) {
setCurrentTrackFromLanes(lanes);
logSuspendedWithDelayPhase(
transitionSuspendedTime,
// Clamp the suspended time to the first event/update.
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime,
);
}
logTransitionStart(
clampedStartTime,
clampedUpdateTime,
clampedEventTime,
transitionEventType,
transitionEventIsRepeat,
renderStartTime,
Expand Down
15 changes: 15 additions & 0 deletions packages/react-reconciler/src/ReactProfilerTimer.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
export let blockingEventType: null | string = null; // Event type of the first setState.
export let blockingEventIsRepeat: boolean = false;
export let blockingSuspendedTime: number = -1.1;
// TODO: This should really be one per Transition lane.
export let transitionClampTime: number = -0;
export let transitionStartTime: number = -1.1; // First startTransition call before setState.
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
export let transitionEventType: null | string = null; // Event type of the first transition.
export let transitionEventIsRepeat: boolean = false;
export let transitionSuspendedTime: number = -1.1;

export function startUpdateTimerByLane(lane: Lane): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
Expand Down Expand Up @@ -100,8 +102,20 @@ export function markUpdateAsRepeat(lanes: Lanes): void {
}
}

export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
blockingSuspendedTime = renderEndTime;
} else if (includesTransitionLane(lanes)) {
transitionSuspendedTime = renderEndTime;
}
}

export function clearBlockingTimers(): void {
blockingUpdateTime = -1.1;
blockingSuspendedTime = -1.1;
}

export function startAsyncTransitionTimer(): void {
Expand Down Expand Up @@ -145,6 +159,7 @@ export function clearAsyncTransitionTimer(): void {
export function clearTransitionTimers(): void {
transitionStartTime = -1.1;
transitionUpdateTime = -1.1;
transitionSuspendedTime = -1.1;
}

export function clampBlockingTimers(finalTime: number): void {
Expand Down

0 comments on commit 490a279

Please sign in to comment.