mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Profiler actualDuration bugfix (#13313)
* Simplified profiler actualDuration timing While testing the new DevTools profiler, I noticed that sometimes– in larger, more complicated applications– the actualDuration value was incorrect (either too large, or sometimes negative). I was not able to reproduce this in a smaller application or test (which sucks) but I assume it has something to do with the way I was tracking render times across priorities/roots. So this PR replaces the previous approach with a simpler one. * Changed bubbling logic after chatting out of band with Andrew * Replaced __PROFILE__ with feature-flag conditionals in test * Updated test comment
This commit is contained in:
+3
-2
@@ -162,6 +162,7 @@ export type Fiber = {|
|
||||
|
||||
// Time spent rendering this Fiber and its descendants for the current update.
|
||||
// This tells us how well the tree makes use of sCU for memoization.
|
||||
// It is reset to 0 each time we render and only updated when we don't bailout.
|
||||
// This field is only set when the enableProfilerTimer flag is enabled.
|
||||
actualDuration?: number,
|
||||
|
||||
@@ -238,7 +239,7 @@ function FiberNode(
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
this.actualDuration = 0;
|
||||
this.actualStartTime = 0;
|
||||
this.actualStartTime = -1;
|
||||
this.selfBaseDuration = 0;
|
||||
this.treeBaseDuration = 0;
|
||||
}
|
||||
@@ -330,7 +331,7 @@ export function createWorkInProgress(
|
||||
// This has the downside of resetting values for different priority renders,
|
||||
// But works for yielding (the common case) and should support resuming.
|
||||
workInProgress.actualDuration = 0;
|
||||
workInProgress.actualStartTime = 0;
|
||||
workInProgress.actualStartTime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-13
@@ -62,7 +62,7 @@ import {
|
||||
} from './ReactChildFiber';
|
||||
import {processUpdateQueue} from './ReactUpdateQueue';
|
||||
import {NoWork, Never} from './ReactFiberExpirationTime';
|
||||
import {AsyncMode, ProfileMode, StrictMode} from './ReactTypeOfMode';
|
||||
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
shouldSetTextContent,
|
||||
shouldDeprioritizeSubtree,
|
||||
@@ -75,10 +75,7 @@ import {
|
||||
prepareToReadContext,
|
||||
calculateChangedBits,
|
||||
} from './ReactFiberNewContext';
|
||||
import {
|
||||
markActualRenderTimeStarted,
|
||||
stopBaseRenderTimerIfRunning,
|
||||
} from './ReactProfilerTimer';
|
||||
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
|
||||
import {
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
@@ -379,7 +376,7 @@ function finishClassComponent(
|
||||
nextChildren = null;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
stopBaseRenderTimerIfRunning();
|
||||
stopProfilerTimerIfRunning(workInProgress);
|
||||
}
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
@@ -954,7 +951,7 @@ function bailoutOnAlreadyFinishedWork(
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Don't update "base" render times for bailouts.
|
||||
stopBaseRenderTimerIfRunning();
|
||||
stopProfilerTimerIfRunning(workInProgress);
|
||||
}
|
||||
|
||||
// Check if the children have any pending work.
|
||||
@@ -991,12 +988,6 @@ function beginWork(
|
||||
workInProgress: Fiber,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): Fiber | null {
|
||||
if (enableProfilerTimer) {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
markActualRenderTimeStarted(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
const updateExpirationTime = workInProgress.expirationTime;
|
||||
if (
|
||||
!hasLegacyContextChanged() &&
|
||||
|
||||
@@ -20,7 +20,6 @@ import type {
|
||||
HostContext,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
@@ -38,7 +37,6 @@ import {
|
||||
PlaceholderComponent,
|
||||
} from 'shared/ReactTypeOfWork';
|
||||
import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect';
|
||||
import {ProfileMode} from './ReactTypeOfMode';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
@@ -60,7 +58,6 @@ import {
|
||||
getHostContext,
|
||||
popHostContainer,
|
||||
} from './ReactFiberHostContext';
|
||||
import {recordElapsedActualRenderTime} from './ReactProfilerTimer';
|
||||
import {
|
||||
popContextProvider as popLegacyContextProvider,
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
@@ -518,16 +515,6 @@ function completeWork(
|
||||
);
|
||||
}
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
// Don't record elapsed time unless the "complete" phase has succeeded.
|
||||
// Certain renderers may error during this phase (i.e. ReactNative View/Text nesting validation).
|
||||
// If an error occurs, we'll mark the time while unwinding.
|
||||
// This simplifies the unwinding logic and ensures consistency.
|
||||
recordElapsedActualRenderTime(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+64
-71
@@ -112,15 +112,9 @@ import {
|
||||
import {popProvider, resetContextDependences} from './ReactFiberNewContext';
|
||||
import {popHostContext, popHostContainer} from './ReactFiberHostContext';
|
||||
import {
|
||||
checkActualRenderTimeStackEmpty,
|
||||
pauseActualRenderTimerIfRunning,
|
||||
recordCommitTime,
|
||||
recordElapsedActualRenderTime,
|
||||
recordElapsedBaseRenderTimeIfRunning,
|
||||
resetActualRenderTimerStackAfterFatalErrorInDev,
|
||||
resumeActualRenderTimerIfPaused,
|
||||
startBaseRenderTimer,
|
||||
stopBaseRenderTimerIfRunning,
|
||||
startProfilerTimer,
|
||||
stopProfilerTimerIfRunningAndRecordDelta,
|
||||
} from './ReactProfilerTimer';
|
||||
import {
|
||||
checkThatStackIsEmpty,
|
||||
@@ -312,15 +306,6 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
||||
originalReplayError = null;
|
||||
if (hasCaughtError()) {
|
||||
clearCaughtError();
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
if (failedUnitOfWork.mode & ProfileMode) {
|
||||
recordElapsedActualRenderTime(failedUnitOfWork);
|
||||
}
|
||||
|
||||
// Stop "base" render timer again (after the re-thrown error).
|
||||
stopBaseRenderTimerIfRunning();
|
||||
}
|
||||
} else {
|
||||
// If the begin phase did not fail the second time, set this pointer
|
||||
// back to the original value.
|
||||
@@ -336,12 +321,6 @@ function resetStack() {
|
||||
if (nextUnitOfWork !== null) {
|
||||
let interruptedWork = nextUnitOfWork.return;
|
||||
while (interruptedWork !== null) {
|
||||
if (enableProfilerTimer) {
|
||||
if (interruptedWork.mode & ProfileMode) {
|
||||
// Resume in case we're picking up on work that was paused.
|
||||
resumeActualRenderTimerIfPaused(false);
|
||||
}
|
||||
}
|
||||
unwindInterruptedWork(interruptedWork);
|
||||
interruptedWork = interruptedWork.return;
|
||||
}
|
||||
@@ -691,17 +670,6 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
if (__DEV__) {
|
||||
if (nextRoot === null) {
|
||||
// Only check this stack once we're done processing async work.
|
||||
// This prevents a false positive that occurs after a batched commit,
|
||||
// If there was in-progress async work before the commit.
|
||||
checkActualRenderTimeStackEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isCommitting = false;
|
||||
isWorking = false;
|
||||
stopCommitLifeCyclesTimer();
|
||||
@@ -741,9 +709,22 @@ function resetChildExpirationTime(
|
||||
|
||||
// Bubble up the earliest expiration time.
|
||||
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
|
||||
// We're in profiling mode. Let's use this same traversal to update the
|
||||
// "base" render times.
|
||||
// We're in profiling mode.
|
||||
// Let's use this same traversal to update the render durations.
|
||||
let actualDuration = workInProgress.actualDuration;
|
||||
let treeBaseDuration = workInProgress.selfBaseDuration;
|
||||
|
||||
// When a fiber is cloned, its actualDuration is reset to 0.
|
||||
// This value will only be updated if work is done on the fiber (i.e. it doesn't bailout).
|
||||
// When work is done, it should bubble to the parent's actualDuration.
|
||||
// If the fiber has not been cloned though, (meaning no work was done),
|
||||
// Then this value will reflect the amount of time spent working on a previous render.
|
||||
// In that case it should not bubble.
|
||||
// We determine whether it was cloned by comparing the child pointer.
|
||||
const shouldBubbleActualDurations =
|
||||
workInProgress.alternate === null ||
|
||||
workInProgress.child !== workInProgress.alternate.child;
|
||||
|
||||
let child = workInProgress.child;
|
||||
while (child !== null) {
|
||||
const childUpdateExpirationTime = child.expirationTime;
|
||||
@@ -762,9 +743,13 @@ function resetChildExpirationTime(
|
||||
) {
|
||||
newChildExpirationTime = childChildExpirationTime;
|
||||
}
|
||||
if (shouldBubbleActualDurations) {
|
||||
actualDuration += child.actualDuration;
|
||||
}
|
||||
treeBaseDuration += child.treeBaseDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
workInProgress.actualDuration = actualDuration;
|
||||
workInProgress.treeBaseDuration = treeBaseDuration;
|
||||
} else {
|
||||
let child = workInProgress.child;
|
||||
@@ -811,11 +796,28 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
|
||||
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
|
||||
// This fiber completed.
|
||||
nextUnitOfWork = completeWork(
|
||||
current,
|
||||
workInProgress,
|
||||
nextRenderExpirationTime,
|
||||
);
|
||||
if (enableProfilerTimer) {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
startProfilerTimer(workInProgress);
|
||||
}
|
||||
|
||||
nextUnitOfWork = completeWork(
|
||||
current,
|
||||
workInProgress,
|
||||
nextRenderExpirationTime,
|
||||
);
|
||||
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
// Update render duration assuming we didn't error.
|
||||
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
|
||||
}
|
||||
} else {
|
||||
nextUnitOfWork = completeWork(
|
||||
current,
|
||||
workInProgress,
|
||||
nextRenderExpirationTime,
|
||||
);
|
||||
}
|
||||
let next = nextUnitOfWork;
|
||||
stopWorkTimer(workInProgress);
|
||||
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
|
||||
@@ -886,6 +888,11 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
// Record the render duration for the fiber that errored.
|
||||
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
|
||||
}
|
||||
|
||||
// This fiber did not complete because something threw. Pop values off
|
||||
// the stack without entering the complete phase. If this is a boundary,
|
||||
// capture values if possible.
|
||||
@@ -908,6 +915,19 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
|
||||
}
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Include the time spent working on failed children before continuing.
|
||||
if (next.mode & ProfileMode) {
|
||||
let actualDuration = next.actualDuration;
|
||||
let child = next.child;
|
||||
while (child !== null) {
|
||||
actualDuration += child.actualDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
next.actualDuration = actualDuration;
|
||||
}
|
||||
}
|
||||
|
||||
// If completing this work spawned new work, do that next. We'll come
|
||||
// back here again.
|
||||
// Since we're restarting, remove anything that is not a host effect
|
||||
@@ -968,15 +988,14 @@ function performUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
let next;
|
||||
if (enableProfilerTimer) {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
startBaseRenderTimer();
|
||||
startProfilerTimer(workInProgress);
|
||||
}
|
||||
|
||||
next = beginWork(current, workInProgress, nextRenderExpirationTime);
|
||||
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
// Update "base" time if the render wasn't bailed out on.
|
||||
recordElapsedBaseRenderTimeIfRunning(workInProgress);
|
||||
stopBaseRenderTimerIfRunning();
|
||||
// Record the render duration assuming we didn't bailout (or error).
|
||||
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
|
||||
}
|
||||
} else {
|
||||
next = beginWork(current, workInProgress, nextRenderExpirationTime);
|
||||
@@ -1017,12 +1036,6 @@ function workLoop(isYieldy) {
|
||||
while (nextUnitOfWork !== null && !shouldYield()) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
}
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// If we didn't finish, pause the "actual" render timer.
|
||||
// We'll restart it when we resume work.
|
||||
pauseActualRenderTimerIfRunning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1068,11 +1081,6 @@ function renderRoot(
|
||||
try {
|
||||
workLoop(isYieldy);
|
||||
} catch (thrownValue) {
|
||||
if (enableProfilerTimer) {
|
||||
// Stop "base" render timer in the event of an error.
|
||||
stopBaseRenderTimerIfRunning();
|
||||
}
|
||||
|
||||
if (nextUnitOfWork === null) {
|
||||
// This is a fatal error.
|
||||
didFatal = true;
|
||||
@@ -1139,10 +1147,6 @@ function renderRoot(
|
||||
// There was a fatal error.
|
||||
if (__DEV__) {
|
||||
resetStackAfterFatalErrorInDev();
|
||||
|
||||
// Reset the DEV fiber stack in case we're profiling roots.
|
||||
// (We do this for profiling bulds when DevTools is detected.)
|
||||
resetActualRenderTimerStackAfterFatalErrorInDev();
|
||||
}
|
||||
// `nextRoot` points to the in-progress root. A non-null value indicates
|
||||
// that we're in the middle of an async render. Set it to null to indicate
|
||||
@@ -1871,10 +1875,6 @@ function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
|
||||
// the deadline.
|
||||
findHighestPriorityRoot();
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
resumeActualRenderTimerIfPaused(minExpirationTime === Sync);
|
||||
}
|
||||
|
||||
if (deadline !== null) {
|
||||
recomputeCurrentRendererTime();
|
||||
currentSchedulerTime = currentRendererTime;
|
||||
@@ -1947,7 +1947,6 @@ function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
|
||||
performWorkOnRoot(root, expirationTime, true);
|
||||
// Flush any sync work that was scheduled by lifecycles
|
||||
performSyncWork();
|
||||
pauseActualRenderTimerIfRunning();
|
||||
}
|
||||
|
||||
function finishRendering() {
|
||||
@@ -2049,12 +2048,6 @@ function performWorkOnRoot(
|
||||
// There's no time left. Mark this root as complete. We'll come
|
||||
// back and commit it later.
|
||||
root.finishedWork = finishedWork;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// If we didn't finish, pause the "actual" render timer.
|
||||
// We'll restart it when we resume work.
|
||||
pauseActualRenderTimerIfRunning();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-15
@@ -34,10 +34,9 @@ import {
|
||||
} from 'shared/ReactTypeOfSideEffect';
|
||||
import {
|
||||
enableGetDerivedStateFromCatch,
|
||||
enableProfilerTimer,
|
||||
enableSuspense,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {ProfileMode, StrictMode, AsyncMode} from './ReactTypeOfMode';
|
||||
import {StrictMode, AsyncMode} from './ReactTypeOfMode';
|
||||
|
||||
import {createCapturedValue} from './ReactCapturedValue';
|
||||
import {
|
||||
@@ -52,7 +51,6 @@ import {
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
} from './ReactFiberContext';
|
||||
import {popProvider} from './ReactFiberNewContext';
|
||||
import {recordElapsedActualRenderTime} from './ReactProfilerTimer';
|
||||
import {
|
||||
renderDidSuspend,
|
||||
renderDidError,
|
||||
@@ -380,12 +378,6 @@ function unwindWork(
|
||||
workInProgress: Fiber,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
if (enableProfilerTimer) {
|
||||
if (workInProgress.mode & ProfileMode) {
|
||||
recordElapsedActualRenderTime(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
case ClassComponent: {
|
||||
popLegacyContextProvider(workInProgress);
|
||||
@@ -432,12 +424,6 @@ function unwindWork(
|
||||
}
|
||||
|
||||
function unwindInterruptedWork(interruptedWork: Fiber) {
|
||||
if (enableProfilerTimer) {
|
||||
if (interruptedWork.mode & ProfileMode) {
|
||||
recordElapsedActualRenderTime(interruptedWork);
|
||||
}
|
||||
}
|
||||
|
||||
switch (interruptedWork.tag) {
|
||||
case ClassComponent: {
|
||||
popLegacyContextProvider(interruptedWork);
|
||||
|
||||
+25
-140
@@ -9,27 +9,20 @@
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import {now} from './ReactFiberHostConfig';
|
||||
|
||||
export type ProfilerTimer = {
|
||||
checkActualRenderTimeStackEmpty(): void,
|
||||
getCommitTime(): number,
|
||||
markActualRenderTimeStarted(fiber: Fiber): void,
|
||||
pauseActualRenderTimerIfRunning(): void,
|
||||
recordElapsedActualRenderTime(fiber: Fiber): void,
|
||||
resumeActualRenderTimerIfPaused(isProcessingBatchCommit: boolean): void,
|
||||
recordCommitTime(): void,
|
||||
recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void,
|
||||
resetActualRenderTimerStackAfterFatalErrorInDev(): void,
|
||||
startBaseRenderTimer(): void,
|
||||
stopBaseRenderTimerIfRunning(): void,
|
||||
startProfilerTimer(fiber: Fiber): void,
|
||||
stopProfilerTimerIfRunning(fiber: Fiber): void,
|
||||
stopProfilerTimerIfRunningAndRecordDelta(fiber: Fiber): void,
|
||||
};
|
||||
|
||||
let commitTime: number = 0;
|
||||
let profilerStartTime: number = -1;
|
||||
|
||||
function getCommitTime(): number {
|
||||
return commitTime;
|
||||
@@ -42,155 +35,47 @@ function recordCommitTime(): void {
|
||||
commitTime = now();
|
||||
}
|
||||
|
||||
/**
|
||||
* The "actual" render time is total time required to render the descendants of a Profiler component.
|
||||
* This time is stored as a stack, since Profilers can be nested.
|
||||
* This time is started during the "begin" phase and stopped during the "complete" phase.
|
||||
* It is paused (and accumulated) in the event of an interruption or an aborted render.
|
||||
*/
|
||||
|
||||
let fiberStack: Array<Fiber | null>;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack = [];
|
||||
}
|
||||
|
||||
let syncCommitStartTime: number = 0;
|
||||
let timerPausedAt: number = 0;
|
||||
let totalElapsedPauseTime: number = 0;
|
||||
|
||||
function checkActualRenderTimeStackEmpty(): void {
|
||||
function startProfilerTimer(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
fiberStack.length === 0,
|
||||
'Expected an empty stack. Something was not reset properly.',
|
||||
);
|
||||
|
||||
profilerStartTime = now();
|
||||
|
||||
if (((fiber.actualStartTime: any): number) < 0) {
|
||||
fiber.actualStartTime = now();
|
||||
}
|
||||
}
|
||||
|
||||
function markActualRenderTimeStarted(fiber: Fiber): void {
|
||||
function stopProfilerTimerIfRunning(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
fiberStack.push(fiber);
|
||||
}
|
||||
|
||||
fiber.actualDuration =
|
||||
now() - ((fiber.actualDuration: any): number) - totalElapsedPauseTime;
|
||||
fiber.actualStartTime = now();
|
||||
profilerStartTime = -1;
|
||||
}
|
||||
|
||||
function pauseActualRenderTimerIfRunning(): void {
|
||||
function stopProfilerTimerIfRunningAndRecordDelta(
|
||||
fiber: Fiber,
|
||||
overrideBaseTime: boolean,
|
||||
): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (timerPausedAt === 0) {
|
||||
timerPausedAt = now();
|
||||
}
|
||||
if (syncCommitStartTime > 0) {
|
||||
// Don't count the time spent processing sycn work,
|
||||
// Against yielded async work that will be resumed later.
|
||||
totalElapsedPauseTime += now() - syncCommitStartTime;
|
||||
syncCommitStartTime = 0;
|
||||
} else {
|
||||
totalElapsedPauseTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function recordElapsedActualRenderTime(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
fiber === fiberStack.pop(),
|
||||
'Unexpected Fiber (%s) popped.',
|
||||
getComponentName(fiber.type),
|
||||
);
|
||||
}
|
||||
|
||||
fiber.actualDuration =
|
||||
now() - totalElapsedPauseTime - ((fiber.actualDuration: any): number);
|
||||
}
|
||||
|
||||
function resumeActualRenderTimerIfPaused(isProcessingSyncWork: boolean): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (isProcessingSyncWork && syncCommitStartTime === 0) {
|
||||
syncCommitStartTime = now();
|
||||
}
|
||||
if (timerPausedAt > 0) {
|
||||
totalElapsedPauseTime += now() - timerPausedAt;
|
||||
timerPausedAt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function resetActualRenderTimerStackAfterFatalErrorInDev(): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
fiberStack.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "base" render time is the duration of the “begin” phase of work for a particular fiber.
|
||||
* This time is measured and stored on each fiber.
|
||||
* The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase.
|
||||
* If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated.
|
||||
*/
|
||||
|
||||
let baseStartTime: number = -1;
|
||||
|
||||
function recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (baseStartTime !== -1) {
|
||||
fiber.selfBaseDuration = now() - baseStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
function startBaseRenderTimer(): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (baseStartTime !== -1) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'Cannot start base timer that is already running. ' +
|
||||
'This error is likely caused by a bug in React. ' +
|
||||
'Please file an issue.',
|
||||
);
|
||||
if (profilerStartTime >= 0) {
|
||||
const elapsedTime = now() - profilerStartTime;
|
||||
fiber.actualDuration += elapsedTime;
|
||||
if (overrideBaseTime) {
|
||||
fiber.selfBaseDuration = elapsedTime;
|
||||
}
|
||||
profilerStartTime = -1;
|
||||
}
|
||||
baseStartTime = now();
|
||||
}
|
||||
|
||||
function stopBaseRenderTimerIfRunning(): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
baseStartTime = -1;
|
||||
}
|
||||
|
||||
export {
|
||||
checkActualRenderTimeStackEmpty,
|
||||
getCommitTime,
|
||||
markActualRenderTimeStarted,
|
||||
pauseActualRenderTimerIfRunning,
|
||||
recordCommitTime,
|
||||
recordElapsedActualRenderTime,
|
||||
resetActualRenderTimerStackAfterFatalErrorInDev,
|
||||
resumeActualRenderTimerIfPaused,
|
||||
recordElapsedBaseRenderTimeIfRunning,
|
||||
startBaseRenderTimer,
|
||||
stopBaseRenderTimerIfRunning,
|
||||
startProfilerTimer,
|
||||
stopProfilerTimerIfRunning,
|
||||
stopProfilerTimerIfRunningAndRecordDelta,
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('Profiler', () => {
|
||||
|
||||
// This will throw in production too,
|
||||
// But the test is only interested in verifying the DEV error message.
|
||||
if (__DEV__) {
|
||||
if (__DEV__ && flagEnabled) {
|
||||
it('should warn if required params are missing', () => {
|
||||
expect(() => {
|
||||
ReactTestRenderer.create(<React.unstable_Profiler />);
|
||||
@@ -196,11 +196,10 @@ describe('Profiler', () => {
|
||||
);
|
||||
|
||||
// Should be called two times:
|
||||
// 1. To mark the start (resuming) of work
|
||||
// 2. To compute the update expiration time,
|
||||
// 3. To record the commit time.
|
||||
// 2. To compute the update expiration time
|
||||
// 3. To record the commit time
|
||||
// No additional calls from ProfilerTimer are expected.
|
||||
expect(mockNow).toHaveBeenCalledTimes(3);
|
||||
expect(mockNow).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('logs render times for both mount and update', () => {
|
||||
@@ -422,19 +421,19 @@ describe('Profiler', () => {
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<React.unstable_Profiler id="test" onRender={callback}>
|
||||
<AdvanceTime byAmount={10}>
|
||||
<AdvanceTime byAmount={10} shouldComponentUpdate={false} />
|
||||
<AdvanceTime byAmount={13} shouldComponentUpdate={false} />
|
||||
</AdvanceTime>
|
||||
</React.unstable_Profiler>,
|
||||
);
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
advanceTimeBy(30); // 25 -> 55
|
||||
advanceTimeBy(30); // 28 -> 58
|
||||
|
||||
renderer.update(
|
||||
<React.unstable_Profiler id="test" onRender={callback}>
|
||||
<AdvanceTime byAmount={10}>
|
||||
<AdvanceTime byAmount={10} shouldComponentUpdate={false} />
|
||||
<AdvanceTime byAmount={4}>
|
||||
<AdvanceTime byAmount={7} shouldComponentUpdate={false} />
|
||||
</AdvanceTime>
|
||||
</React.unstable_Profiler>,
|
||||
);
|
||||
@@ -444,16 +443,16 @@ describe('Profiler', () => {
|
||||
const [mountCall, updateCall] = callback.mock.calls;
|
||||
|
||||
expect(mountCall[1]).toBe('mount');
|
||||
expect(mountCall[2]).toBe(20); // actual time
|
||||
expect(mountCall[3]).toBe(20); // base time
|
||||
expect(mountCall[2]).toBe(23); // actual time
|
||||
expect(mountCall[3]).toBe(23); // base time
|
||||
expect(mountCall[4]).toBe(5); // start time
|
||||
expect(mountCall[5]).toBe(25); // commit time
|
||||
expect(mountCall[5]).toBe(28); // commit time
|
||||
|
||||
expect(updateCall[1]).toBe('update');
|
||||
expect(updateCall[2]).toBe(10); // actual time
|
||||
expect(updateCall[3]).toBe(20); // base time
|
||||
expect(updateCall[4]).toBe(55); // start time
|
||||
expect(updateCall[5]).toBe(65); // commit time
|
||||
expect(updateCall[2]).toBe(4); // actual time
|
||||
expect(updateCall[3]).toBe(17); // base time
|
||||
expect(updateCall[4]).toBe(58); // start time
|
||||
expect(updateCall[5]).toBe(62); // commit time
|
||||
});
|
||||
|
||||
it('includes time spent in render phase lifecycles', () => {
|
||||
@@ -809,7 +808,7 @@ describe('Profiler', () => {
|
||||
]);
|
||||
|
||||
// The actual time should include only the most recent render (37ms),
|
||||
// Because this lets us avoid a lot of commit phase reset complexity.
|
||||
// Because this greatly simplifies the commit phase logic.
|
||||
// The base time should include the more recent times for the SecondComponent subtree,
|
||||
// As well as the original times for the FirstComponent subtree.
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
@@ -856,7 +855,7 @@ describe('Profiler', () => {
|
||||
const callback = jest.fn();
|
||||
|
||||
const ThrowsError = () => {
|
||||
advanceTimeBy(10);
|
||||
advanceTimeBy(3);
|
||||
throw Error('expected error');
|
||||
};
|
||||
|
||||
@@ -880,7 +879,7 @@ describe('Profiler', () => {
|
||||
ReactTestRenderer.create(
|
||||
<React.unstable_Profiler id="test" onRender={callback}>
|
||||
<ErrorBoundary>
|
||||
<AdvanceTime byAmount={5} />
|
||||
<AdvanceTime byAmount={9} />
|
||||
<ThrowsError />
|
||||
</ErrorBoundary>
|
||||
</React.unstable_Profiler>,
|
||||
@@ -891,18 +890,19 @@ describe('Profiler', () => {
|
||||
// Callbacks bubble (reverse order).
|
||||
let [mountCall, updateCall] = callback.mock.calls;
|
||||
|
||||
// The initial mount only includes the ErrorBoundary (which takes 2ms)
|
||||
// The initial mount only includes the ErrorBoundary (which takes 2)
|
||||
// But it spends time rendering all of the failed subtree also.
|
||||
expect(mountCall[1]).toBe('mount');
|
||||
// actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
|
||||
// If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed.
|
||||
expect(mountCall[2]).toBe(flagEnabled && __DEV__ ? 27 : 17);
|
||||
// actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
|
||||
// We don't count the time spent in replaying the failed unit of work (ThrowsError)
|
||||
expect(mountCall[2]).toBe(14);
|
||||
// base time includes: 2 (ErrorBoundary)
|
||||
expect(mountCall[3]).toBe(2);
|
||||
// start time
|
||||
expect(mountCall[4]).toBe(5);
|
||||
// commit time
|
||||
expect(mountCall[5]).toBe(flagEnabled && __DEV__ ? 32 : 22);
|
||||
// commit time: 5 initially + 14 of work
|
||||
// Add an additional 3 (ThrowsError) if we replaced the failed work
|
||||
expect(mountCall[5]).toBe(__DEV__ && flagEnabled ? 22 : 19);
|
||||
|
||||
// The update includes the ErrorBoundary and its fallback child
|
||||
expect(updateCall[1]).toBe('update');
|
||||
@@ -911,9 +911,10 @@ describe('Profiler', () => {
|
||||
// base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
|
||||
expect(updateCall[3]).toBe(22);
|
||||
// start time
|
||||
expect(updateCall[4]).toBe(flagEnabled && __DEV__ ? 32 : 22);
|
||||
// commit time
|
||||
expect(updateCall[5]).toBe(flagEnabled && __DEV__ ? 54 : 44);
|
||||
expect(updateCall[4]).toBe(__DEV__ && flagEnabled ? 22 : 19);
|
||||
// commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
|
||||
// Add an additional 3 (ThrowsError) if we replaced the failed work
|
||||
expect(updateCall[5]).toBe(__DEV__ && flagEnabled ? 44 : 41);
|
||||
});
|
||||
|
||||
it('should accumulate actual time after an error handled by getDerivedStateFromCatch()', () => {
|
||||
@@ -960,14 +961,14 @@ describe('Profiler', () => {
|
||||
expect(mountCall[1]).toBe('mount');
|
||||
// actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
|
||||
// Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
|
||||
// If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed.
|
||||
expect(mountCall[2]).toBe(flagEnabled && __DEV__ ? 49 : 39);
|
||||
// We don't count the time spent in replaying the failed unit of work (ThrowsError)
|
||||
expect(mountCall[2]).toBe(39);
|
||||
// base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
|
||||
expect(mountCall[3]).toBe(22);
|
||||
// start time
|
||||
expect(mountCall[4]).toBe(5);
|
||||
// commit time
|
||||
expect(mountCall[5]).toBe(flagEnabled && __DEV__ ? 54 : 44);
|
||||
expect(mountCall[5]).toBe(__DEV__ && flagEnabled ? 54 : 44);
|
||||
});
|
||||
|
||||
it('should reset the fiber stack correct after a "complete" phase error', () => {
|
||||
|
||||
Reference in New Issue
Block a user