Skip to content

Commit 3720870

Browse files
authored
Log Render Phases that Never Committed (#31548)
This includes: - `Interrupted Render`: Interrupted Renders (setState or ping at higher priority) - `Prewarm`: Suspended Renders outside a Suspense boundary (RootSuspendedWithDelay/RootSuspendedAtTheShell) - `Errored Render`: Render that errored somewhere in the tree (Fatal or Not) (which may or may not be retried and then complete) - `Teared Render`: Due to useSyncExternalStore not matching (which will do another sync attempt) Suspended Commit: <img width="893" alt="Screenshot 2024-11-14 at 11 47 40 PM" src="https://github.com/user-attachments/assets/b25a6a8b-a5e9-4d66-b325-57aef4bf9dad"> Errored with a second recovery attempt that also errors: <img width="976" alt="Screenshot 2024-11-15 at 12 09 06 AM" src="https://github.com/user-attachments/assets/9ce52cbb-b587-4f1e-8b67-e51d9073ae5b">
1 parent e1ef8c9 commit 3720870

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

+48
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,54 @@ export function logRenderPhase(startTime: number, endTime: number): void {
198198
}
199199
}
200200

201+
export function logInterruptedRenderPhase(
202+
startTime: number,
203+
endTime: number,
204+
): void {
205+
if (supportsUserTiming) {
206+
reusableLaneDevToolDetails.color = 'primary-dark';
207+
reusableLaneOptions.start = startTime;
208+
reusableLaneOptions.end = endTime;
209+
performance.measure('Interrupted Render', reusableLaneOptions);
210+
}
211+
}
212+
213+
export function logSuspendedRenderPhase(
214+
startTime: number,
215+
endTime: number,
216+
): void {
217+
if (supportsUserTiming) {
218+
reusableLaneDevToolDetails.color = 'primary-dark';
219+
reusableLaneOptions.start = startTime;
220+
reusableLaneOptions.end = endTime;
221+
performance.measure('Prewarm', reusableLaneOptions);
222+
}
223+
}
224+
225+
export function logErroredRenderPhase(
226+
startTime: number,
227+
endTime: number,
228+
): void {
229+
if (supportsUserTiming) {
230+
reusableLaneDevToolDetails.color = 'error';
231+
reusableLaneOptions.start = startTime;
232+
reusableLaneOptions.end = endTime;
233+
performance.measure('Errored Render', reusableLaneOptions);
234+
}
235+
}
236+
237+
export function logInconsistentRender(
238+
startTime: number,
239+
endTime: number,
240+
): void {
241+
if (supportsUserTiming) {
242+
reusableLaneDevToolDetails.color = 'error';
243+
reusableLaneOptions.start = startTime;
244+
reusableLaneOptions.end = endTime;
245+
performance.measure('Teared Render', reusableLaneOptions);
246+
}
247+
}
248+
201249
export function logSuspenseThrottlePhase(
202250
startTime: number,
203251
endTime: number,

packages/react-reconciler/src/ReactFiberWorkLoop.js

+51-3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ import {
6969
logBlockingStart,
7070
logTransitionStart,
7171
logRenderPhase,
72+
logInterruptedRenderPhase,
73+
logSuspendedRenderPhase,
74+
logErroredRenderPhase,
75+
logInconsistentRender,
7276
logSuspenseThrottlePhase,
7377
logSuspendedCommitPhase,
7478
logCommitPhase,
@@ -255,6 +259,7 @@ import {
255259
startProfilerTimer,
256260
stopProfilerTimerIfRunningAndRecordDuration,
257261
stopProfilerTimerIfRunningAndRecordIncompleteDuration,
262+
markUpdateAsRepeat,
258263
} from './ReactProfilerTimer';
259264
import {setCurrentTrackFromLanes} from './ReactFiberPerformanceTrack';
260265

@@ -955,6 +960,13 @@ export function performWorkOnRoot(
955960
renderWasConcurrent &&
956961
!isRenderConsistentWithExternalStores(finishedWork)
957962
) {
963+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
964+
setCurrentTrackFromLanes(lanes);
965+
const renderEndTime = now();
966+
logInconsistentRender(renderStartTime, renderEndTime);
967+
finalizeRender(lanes, renderEndTime);
968+
markUpdateAsRepeat(lanes);
969+
}
958970
// A store was mutated in an interleaved event. Render again,
959971
// synchronously, to block further mutations.
960972
exitStatus = renderRootSync(root, lanes, false);
@@ -976,6 +988,13 @@ export function performWorkOnRoot(
976988
lanesThatJustErrored,
977989
);
978990
if (errorRetryLanes !== NoLanes) {
991+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
992+
setCurrentTrackFromLanes(lanes);
993+
const renderEndTime = now();
994+
logErroredRenderPhase(renderStartTime, renderEndTime);
995+
finalizeRender(lanes, renderEndTime);
996+
markUpdateAsRepeat(lanes);
997+
}
979998
lanes = errorRetryLanes;
980999
exitStatus = recoverFromConcurrentError(
9811000
root,
@@ -999,6 +1018,12 @@ export function performWorkOnRoot(
9991018
}
10001019
}
10011020
if (exitStatus === RootFatalErrored) {
1021+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
1022+
setCurrentTrackFromLanes(lanes);
1023+
const renderEndTime = now();
1024+
logErroredRenderPhase(renderStartTime, renderEndTime);
1025+
finalizeRender(lanes, renderEndTime);
1026+
}
10021027
prepareFreshStack(root, NoLanes);
10031028
// Since this is a fatal error, we're going to pretend we attempted
10041029
// the entire tree, to avoid scheduling a prerender.
@@ -1136,6 +1161,8 @@ function finishConcurrentRender(
11361161
// placeholder and without scheduling a timeout. Delay indefinitely
11371162
// until we receive more data.
11381163
if (enableProfilerTimer && enableComponentPerformanceTrack) {
1164+
setCurrentTrackFromLanes(lanes);
1165+
logSuspendedRenderPhase(renderStartTime, renderEndTime);
11391166
finalizeRender(lanes, renderEndTime);
11401167
}
11411168
const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
@@ -1180,6 +1207,7 @@ function finishConcurrentRender(
11801207
workInProgressDeferredLane,
11811208
workInProgressRootInterleavedUpdatedLanes,
11821209
workInProgressSuspendedRetryLanes,
1210+
exitStatus,
11831211
IMMEDIATE_COMMIT,
11841212
renderStartTime,
11851213
renderEndTime,
@@ -1230,6 +1258,7 @@ function finishConcurrentRender(
12301258
workInProgressRootInterleavedUpdatedLanes,
12311259
workInProgressSuspendedRetryLanes,
12321260
workInProgressRootDidSkipSuspendedSiblings,
1261+
exitStatus,
12331262
THROTTLED_COMMIT,
12341263
renderStartTime,
12351264
renderEndTime,
@@ -1250,6 +1279,7 @@ function finishConcurrentRender(
12501279
workInProgressRootInterleavedUpdatedLanes,
12511280
workInProgressSuspendedRetryLanes,
12521281
workInProgressRootDidSkipSuspendedSiblings,
1282+
exitStatus,
12531283
IMMEDIATE_COMMIT,
12541284
renderStartTime,
12551285
renderEndTime,
@@ -1268,6 +1298,7 @@ function commitRootWhenReady(
12681298
updatedLanes: Lanes,
12691299
suspendedRetryLanes: Lanes,
12701300
didSkipSuspendedSiblings: boolean,
1301+
exitStatus: RootExitStatus,
12711302
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
12721303
completedRenderStartTime: number, // Profiling-only
12731304
completedRenderEndTime: number, // Profiling-only
@@ -1311,6 +1342,7 @@ function commitRootWhenReady(
13111342
spawnedLane,
13121343
updatedLanes,
13131344
suspendedRetryLanes,
1345+
exitStatus,
13141346
SUSPENDED_COMMIT,
13151347
completedRenderStartTime,
13161348
completedRenderEndTime,
@@ -1331,6 +1363,7 @@ function commitRootWhenReady(
13311363
spawnedLane,
13321364
updatedLanes,
13331365
suspendedRetryLanes,
1366+
exitStatus,
13341367
suspendedCommitReason,
13351368
completedRenderStartTime,
13361369
completedRenderEndTime,
@@ -1649,13 +1682,21 @@ function finalizeRender(lanes: Lanes, finalizationTime: number): void {
16491682

16501683
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
16511684
if (enableProfilerTimer && enableComponentPerformanceTrack) {
1685+
const previousRenderStartTime = renderStartTime;
16521686
// Starting a new render. Log the end of any previous renders and the
16531687
// blocked time before the render started.
16541688
recordRenderTime();
16551689
// If this was a restart, e.g. due to an interrupting update, then there's no space
16561690
// in the track to log the cause since we'll have rendered all the way up until the
16571691
// restart so we need to clamp that.
1658-
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
1692+
if (
1693+
workInProgressRootRenderLanes !== NoLanes &&
1694+
previousRenderStartTime > 0
1695+
) {
1696+
setCurrentTrackFromLanes(workInProgressRootRenderLanes);
1697+
logInterruptedRenderPhase(previousRenderStartTime, renderStartTime);
1698+
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
1699+
}
16591700

16601701
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
16611702
logBlockingStart(
@@ -2983,6 +3024,7 @@ function commitRoot(
29833024
spawnedLane: Lane,
29843025
updatedLanes: Lanes,
29853026
suspendedRetryLanes: Lanes,
3027+
exitStatus: RootExitStatus,
29863028
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
29873029
completedRenderStartTime: number, // Profiling-only
29883030
completedRenderEndTime: number, // Profiling-only
@@ -3003,6 +3045,7 @@ function commitRoot(
30033045
spawnedLane,
30043046
updatedLanes,
30053047
suspendedRetryLanes,
3048+
exitStatus,
30063049
suspendedCommitReason,
30073050
completedRenderStartTime,
30083051
completedRenderEndTime,
@@ -3022,6 +3065,7 @@ function commitRootImpl(
30223065
spawnedLane: Lane,
30233066
updatedLanes: Lanes,
30243067
suspendedRetryLanes: Lanes,
3068+
exitStatus: RootExitStatus, // Profiling-only
30253069
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
30263070
completedRenderStartTime: number, // Profiling-only
30273071
completedRenderEndTime: number, // Profiling-only
@@ -3047,7 +3091,11 @@ function commitRootImpl(
30473091
if (enableProfilerTimer && enableComponentPerformanceTrack) {
30483092
// Log the previous render phase once we commit. I.e. we weren't interrupted.
30493093
setCurrentTrackFromLanes(lanes);
3050-
logRenderPhase(completedRenderStartTime, completedRenderEndTime);
3094+
if (exitStatus === RootErrored) {
3095+
logErroredRenderPhase(completedRenderStartTime, completedRenderEndTime);
3096+
} else {
3097+
logRenderPhase(completedRenderStartTime, completedRenderEndTime);
3098+
}
30513099
}
30523100

30533101
if (__DEV__) {
@@ -3409,7 +3457,7 @@ function commitRootImpl(
34093457

34103458
if (enableProfilerTimer && enableComponentPerformanceTrack) {
34113459
if (!rootDidHavePassiveEffects) {
3412-
finalizeRender(lanes, now());
3460+
finalizeRender(lanes, commitEndTime);
34133461
}
34143462
}
34153463

packages/react-reconciler/src/ReactProfilerTimer.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@
99

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

12-
import type {Lane} from './ReactFiberLane';
13-
import {isTransitionLane, isBlockingLane, isSyncLane} from './ReactFiberLane';
12+
import type {Lane, Lanes} from './ReactFiberLane';
13+
import {
14+
isTransitionLane,
15+
isBlockingLane,
16+
isSyncLane,
17+
includesTransitionLane,
18+
includesBlockingLane,
19+
includesSyncLane,
20+
} from './ReactFiberLane';
1421

1522
import {resolveEventType, resolveEventTimeStamp} from './ReactFiberConfig';
1623

@@ -80,6 +87,19 @@ export function startUpdateTimerByLane(lane: Lane): void {
8087
}
8188
}
8289

90+
export function markUpdateAsRepeat(lanes: Lanes): void {
91+
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
92+
return;
93+
}
94+
// We're about to do a retry of this render. It is not a new update, so treat this
95+
// as a repeat within the same event.
96+
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
97+
blockingEventIsRepeat = true;
98+
} else if (includesTransitionLane(lanes)) {
99+
transitionEventIsRepeat = true;
100+
}
101+
}
102+
83103
export function clearBlockingTimers(): void {
84104
blockingUpdateTime = -1.1;
85105
}

packages/react/src/__tests__/ReactProfiler-test.internal.js

-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ describe(`onRender`, () => {
181181
'read current time',
182182
'read current time',
183183
'read current time',
184-
'read current time',
185184
]);
186185
} else {
187186
assertLog([

0 commit comments

Comments
 (0)