Skip to content

Commit 490a279

Browse files
committed
Track suspended time when the render doesn't commit because it suspended
This stops whenever the same lane group starts rendering again. Clamped by the preceeding start time/event time/update time.
1 parent 3da0519 commit 490a279

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,19 @@ export function logSuspendedRenderPhase(
222222
}
223223
}
224224

225+
export function logSuspendedWithDelayPhase(
226+
startTime: number,
227+
endTime: number,
228+
): void {
229+
// This means the render was suspended and cannot commit until it gets unblocked.
230+
if (supportsUserTiming) {
231+
reusableLaneDevToolDetails.color = 'primary-dark';
232+
reusableLaneOptions.start = startTime;
233+
reusableLaneOptions.end = endTime;
234+
performance.measure('Suspended', reusableLaneOptions);
235+
}
236+
}
237+
225238
export function logErroredRenderPhase(
226239
startTime: number,
227240
endTime: number,

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import {
7373
logSuspendedRenderPhase,
7474
logErroredRenderPhase,
7575
logInconsistentRender,
76+
logSuspendedWithDelayPhase,
7677
logSuspenseThrottlePhase,
7778
logSuspendedCommitPhase,
7879
logCommitPhase,
@@ -239,12 +240,14 @@ import {
239240
blockingEventTime,
240241
blockingEventType,
241242
blockingEventIsRepeat,
243+
blockingSuspendedTime,
242244
transitionClampTime,
243245
transitionStartTime,
244246
transitionUpdateTime,
245247
transitionEventTime,
246248
transitionEventType,
247249
transitionEventIsRepeat,
250+
transitionSuspendedTime,
248251
clearBlockingTimers,
249252
clearTransitionTimers,
250253
clampBlockingTimers,
@@ -260,6 +263,7 @@ import {
260263
stopProfilerTimerIfRunningAndRecordDuration,
261264
stopProfilerTimerIfRunningAndRecordIncompleteDuration,
262265
markUpdateAsRepeat,
266+
trackSuspendedTime,
263267
} from './ReactProfilerTimer';
264268
import {setCurrentTrackFromLanes} from './ReactFiberPerformanceTrack';
265269

@@ -1144,7 +1148,7 @@ function finishConcurrentRender(
11441148
exitStatus: RootExitStatus,
11451149
finishedWork: Fiber,
11461150
lanes: Lanes,
1147-
renderEndTime: number // Profiling-only
1151+
renderEndTime: number, // Profiling-only
11481152
) {
11491153
// TODO: The fact that most of these branches are identical suggests that some
11501154
// of the exit statuses are not best modeled as exit statuses and should be
@@ -1169,6 +1173,7 @@ function finishConcurrentRender(
11691173
setCurrentTrackFromLanes(lanes);
11701174
logSuspendedRenderPhase(renderStartTime, renderEndTime);
11711175
finalizeRender(lanes, renderEndTime);
1176+
trackSuspendedTime(lanes, renderEndTime);
11721177
}
11731178
const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
11741179
markRootSuspended(
@@ -1704,30 +1709,64 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
17041709
}
17051710

17061711
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
1707-
logBlockingStart(
1712+
const clampedUpdateTime =
17081713
blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
17091714
? blockingClampTime
1710-
: blockingUpdateTime,
1715+
: blockingUpdateTime;
1716+
const clampedEventTime =
17111717
blockingEventTime >= 0 && blockingEventTime < blockingClampTime
17121718
? blockingClampTime
1713-
: blockingEventTime,
1719+
: blockingEventTime;
1720+
if (blockingSuspendedTime >= 0) {
1721+
setCurrentTrackFromLanes(lanes);
1722+
logSuspendedWithDelayPhase(
1723+
blockingSuspendedTime,
1724+
// Clamp the suspended time to the first event/update.
1725+
clampedEventTime >= 0
1726+
? clampedEventTime
1727+
: clampedUpdateTime >= 0
1728+
? clampedUpdateTime
1729+
: renderStartTime,
1730+
);
1731+
}
1732+
logBlockingStart(
1733+
clampedUpdateTime,
1734+
clampedEventTime,
17141735
blockingEventType,
17151736
blockingEventIsRepeat,
17161737
renderStartTime,
17171738
);
17181739
clearBlockingTimers();
17191740
}
17201741
if (includesTransitionLane(lanes)) {
1721-
logTransitionStart(
1742+
const clampedStartTime =
17221743
transitionStartTime >= 0 && transitionStartTime < transitionClampTime
17231744
? transitionClampTime
1724-
: transitionStartTime,
1745+
: transitionStartTime;
1746+
const clampedUpdateTime =
17251747
transitionUpdateTime >= 0 && transitionUpdateTime < transitionClampTime
17261748
? transitionClampTime
1727-
: transitionUpdateTime,
1749+
: transitionUpdateTime;
1750+
const clampedEventTime =
17281751
transitionEventTime >= 0 && transitionEventTime < transitionClampTime
17291752
? transitionClampTime
1730-
: transitionEventTime,
1753+
: transitionEventTime;
1754+
if (transitionSuspendedTime >= 0) {
1755+
setCurrentTrackFromLanes(lanes);
1756+
logSuspendedWithDelayPhase(
1757+
transitionSuspendedTime,
1758+
// Clamp the suspended time to the first event/update.
1759+
clampedEventTime >= 0
1760+
? clampedEventTime
1761+
: clampedUpdateTime >= 0
1762+
? clampedUpdateTime
1763+
: renderStartTime,
1764+
);
1765+
}
1766+
logTransitionStart(
1767+
clampedStartTime,
1768+
clampedUpdateTime,
1769+
clampedEventTime,
17311770
transitionEventType,
17321771
transitionEventIsRepeat,
17331772
renderStartTime,

packages/react-reconciler/src/ReactProfilerTimer.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,15 @@ export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
4848
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
4949
export let blockingEventType: null | string = null; // Event type of the first setState.
5050
export let blockingEventIsRepeat: boolean = false;
51+
export let blockingSuspendedTime: number = -1.1;
5152
// TODO: This should really be one per Transition lane.
5253
export let transitionClampTime: number = -0;
5354
export let transitionStartTime: number = -1.1; // First startTransition call before setState.
5455
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
5556
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
5657
export let transitionEventType: null | string = null; // Event type of the first transition.
5758
export let transitionEventIsRepeat: boolean = false;
59+
export let transitionSuspendedTime: number = -1.1;
5860

5961
export function startUpdateTimerByLane(lane: Lane): void {
6062
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
@@ -100,8 +102,20 @@ export function markUpdateAsRepeat(lanes: Lanes): void {
100102
}
101103
}
102104

105+
export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
106+
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
107+
return;
108+
}
109+
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
110+
blockingSuspendedTime = renderEndTime;
111+
} else if (includesTransitionLane(lanes)) {
112+
transitionSuspendedTime = renderEndTime;
113+
}
114+
}
115+
103116
export function clearBlockingTimers(): void {
104117
blockingUpdateTime = -1.1;
118+
blockingSuspendedTime = -1.1;
105119
}
106120

107121
export function startAsyncTransitionTimer(): void {
@@ -145,6 +159,7 @@ export function clearAsyncTransitionTimer(): void {
145159
export function clearTransitionTimers(): void {
146160
transitionStartTime = -1.1;
147161
transitionUpdateTime = -1.1;
162+
transitionSuspendedTime = -1.1;
148163
}
149164

150165
export function clampBlockingTimers(finalTime: number): void {

0 commit comments

Comments
 (0)