mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Fiber] Optimize enableProfilerCommitHooks by Collecting Elapsed Effect Duration in Module Scope (#30981)
Stacked on #30979. The problem with the previous approach is that it recursively walked the tree up to propagate the resulting time from recording a layout effect. Instead, we keep a running count of the effect duration on the module scope. Then we reset it when entering a nested Profiler and then we add its elapsed count when we exit the Profiler. This also fixes a bug where we weren't previously including unmount times for some detached trees since they couldn't bubble up to find the profiler.
This commit is contained in:
+4
-4
@@ -1033,8 +1033,8 @@ function updateProfiler(
|
||||
// Reset effect durations for the next eventual effect phase.
|
||||
// These are reset during render to allow the DevTools commit hook a chance to read them,
|
||||
const stateNode = workInProgress.stateNode;
|
||||
stateNode.effectDuration = 0;
|
||||
stateNode.passiveEffectDuration = 0;
|
||||
stateNode.effectDuration = -0;
|
||||
stateNode.passiveEffectDuration = -0;
|
||||
}
|
||||
}
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
@@ -3711,8 +3711,8 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
||||
// Reset effect durations for the next eventual effect phase.
|
||||
// These are reset during render to allow the DevTools commit hook a chance to read them,
|
||||
const stateNode = workInProgress.stateNode;
|
||||
stateNode.effectDuration = 0;
|
||||
stateNode.passiveEffectDuration = 0;
|
||||
stateNode.effectDuration = -0;
|
||||
stateNode.passiveEffectDuration = -0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
+48
-23
@@ -31,10 +31,8 @@ import {NoFlags} from './ReactFiberFlags';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {resolveClassComponentProps} from './ReactFiberClassComponent';
|
||||
import {
|
||||
recordLayoutEffectDuration,
|
||||
startLayoutEffectTimer,
|
||||
recordPassiveEffectDuration,
|
||||
startPassiveEffectTimer,
|
||||
recordEffectDuration,
|
||||
startEffectTimer,
|
||||
isCurrentUpdateNested,
|
||||
} from './ReactProfilerTimer';
|
||||
import {NoMode, ProfileMode} from './ReactTypeOfMode';
|
||||
@@ -91,14 +89,41 @@ export function commitHookLayoutEffects(
|
||||
// e.g. a destroy function in one component should never override a ref set
|
||||
// by a create function in another component during the same commit.
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
commitHookEffectListMount(hookFlags, finishedWork);
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
recordEffectDuration(finishedWork);
|
||||
} else {
|
||||
commitHookEffectListMount(hookFlags, finishedWork);
|
||||
}
|
||||
}
|
||||
|
||||
export function commitHookLayoutUnmountEffects(
|
||||
finishedWork: Fiber,
|
||||
nearestMountedAncestor: null | Fiber,
|
||||
hookFlags: HookFlags,
|
||||
) {
|
||||
// Layout effects are destroyed during the mutation phase so that all
|
||||
// destroy functions for all fibers are called before any create functions.
|
||||
// This prevents sibling component effects from interfering with each other,
|
||||
// e.g. a destroy function in one component should never override a ref set
|
||||
// by a create function in another component during the same commit.
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
hookFlags,
|
||||
finishedWork,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
recordEffectDuration(finishedWork);
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
hookFlags,
|
||||
finishedWork,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function commitHookEffectListMount(
|
||||
flags: HookFlags,
|
||||
finishedWork: Fiber,
|
||||
@@ -265,9 +290,9 @@ export function commitHookPassiveMountEffects(
|
||||
hookFlags: HookFlags,
|
||||
) {
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startPassiveEffectTimer();
|
||||
startEffectTimer();
|
||||
commitHookEffectListMount(hookFlags, finishedWork);
|
||||
recordPassiveEffectDuration(finishedWork);
|
||||
recordEffectDuration(finishedWork);
|
||||
} else {
|
||||
commitHookEffectListMount(hookFlags, finishedWork);
|
||||
}
|
||||
@@ -279,13 +304,13 @@ export function commitHookPassiveUnmountEffects(
|
||||
hookFlags: HookFlags,
|
||||
) {
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startPassiveEffectTimer();
|
||||
startEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
hookFlags,
|
||||
finishedWork,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
recordPassiveEffectDuration(finishedWork);
|
||||
recordEffectDuration(finishedWork);
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
hookFlags,
|
||||
@@ -333,7 +358,7 @@ export function commitClassLayoutLifecycles(
|
||||
}
|
||||
}
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
finishedWork,
|
||||
@@ -348,7 +373,7 @@ export function commitClassLayoutLifecycles(
|
||||
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
||||
}
|
||||
}
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
recordEffectDuration(finishedWork);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
@@ -404,7 +429,7 @@ export function commitClassLayoutLifecycles(
|
||||
}
|
||||
}
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
finishedWork,
|
||||
@@ -426,7 +451,7 @@ export function commitClassLayoutLifecycles(
|
||||
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
||||
}
|
||||
}
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
recordEffectDuration(finishedWork);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
@@ -679,7 +704,7 @@ export function safelyCallComponentWillUnmount(
|
||||
);
|
||||
instance.state = current.memoizedState;
|
||||
if (shouldProfile(current)) {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
current,
|
||||
@@ -695,7 +720,7 @@ export function safelyCallComponentWillUnmount(
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
||||
}
|
||||
}
|
||||
recordLayoutEffectDuration(current);
|
||||
recordEffectDuration(current);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
@@ -736,10 +761,10 @@ function commitAttachRef(finishedWork: Fiber) {
|
||||
if (typeof ref === 'function') {
|
||||
if (shouldProfile(finishedWork)) {
|
||||
try {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
finishedWork.refCleanup = ref(instanceToUse);
|
||||
} finally {
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
recordEffectDuration(finishedWork);
|
||||
}
|
||||
} else {
|
||||
finishedWork.refCleanup = ref(instanceToUse);
|
||||
@@ -793,14 +818,14 @@ export function safelyDetachRef(
|
||||
try {
|
||||
if (shouldProfile(current)) {
|
||||
try {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(current, refCleanup);
|
||||
} else {
|
||||
refCleanup();
|
||||
}
|
||||
} finally {
|
||||
recordLayoutEffectDuration(current);
|
||||
recordEffectDuration(current);
|
||||
}
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
@@ -823,14 +848,14 @@ export function safelyDetachRef(
|
||||
try {
|
||||
if (shouldProfile(current)) {
|
||||
try {
|
||||
startLayoutEffectTimer();
|
||||
startEffectTimer();
|
||||
if (__DEV__) {
|
||||
(runWithFiberInDEV(current, ref, null): void);
|
||||
} else {
|
||||
ref(null);
|
||||
}
|
||||
} finally {
|
||||
recordLayoutEffectDuration(current);
|
||||
recordEffectDuration(current);
|
||||
}
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
@@ -849,7 +874,7 @@ export function safelyDetachRef(
|
||||
}
|
||||
}
|
||||
|
||||
export function safelyCallDestroy(
|
||||
function safelyCallDestroy(
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
destroy: () => void,
|
||||
|
||||
+165
-265
@@ -44,7 +44,6 @@ import {
|
||||
enablePersistedModeClonedFlag,
|
||||
enableProfilerTimer,
|
||||
enableProfilerCommitHooks,
|
||||
enableSchedulingProfiler,
|
||||
enableSuspenseCallback,
|
||||
enableScopeAPI,
|
||||
enableUpdaterTracking,
|
||||
@@ -101,9 +100,10 @@ import {
|
||||
} from './ReactFiberFlags';
|
||||
import {
|
||||
getCommitTime,
|
||||
recordLayoutEffectDuration,
|
||||
startLayoutEffectTimer,
|
||||
getCompleteTime,
|
||||
pushNestedEffectDurations,
|
||||
popNestedEffectDurations,
|
||||
bubbleNestedEffectDurations,
|
||||
} from './ReactProfilerTimer';
|
||||
import {logComponentRender} from './ReactFiberPerformanceTrack';
|
||||
import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
|
||||
@@ -145,22 +145,15 @@ import {
|
||||
addMarkerProgressCallbackToPendingTransition,
|
||||
addMarkerIncompleteCallbackToPendingTransition,
|
||||
addMarkerCompleteCallbackToPendingTransition,
|
||||
setIsRunningInsertionEffect,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {
|
||||
NoFlags as NoHookEffect,
|
||||
HasEffect as HookHasEffect,
|
||||
Layout as HookLayout,
|
||||
Insertion as HookInsertion,
|
||||
Passive as HookPassive,
|
||||
} from './ReactHookEffectTags';
|
||||
import {doesFiberContain} from './ReactFiberTreeReflection';
|
||||
import {
|
||||
isDevToolsPresent,
|
||||
markComponentLayoutEffectUnmountStarted,
|
||||
markComponentLayoutEffectUnmountStopped,
|
||||
onCommitUnmount,
|
||||
} from './ReactFiberDevToolsHook';
|
||||
import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';
|
||||
import {releaseCache, retainCache} from './ReactFiberCacheComponent';
|
||||
import {clearTransitionsForLanes} from './ReactFiberLane';
|
||||
import {
|
||||
@@ -176,6 +169,7 @@ import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop';
|
||||
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
|
||||
import {
|
||||
commitHookLayoutEffects,
|
||||
commitHookLayoutUnmountEffects,
|
||||
commitHookEffectListMount,
|
||||
commitHookEffectListUnmount,
|
||||
commitHookPassiveMountEffects,
|
||||
@@ -188,7 +182,6 @@ import {
|
||||
safelyCallComponentWillUnmount,
|
||||
safelyAttachRef,
|
||||
safelyDetachRef,
|
||||
safelyCallDestroy,
|
||||
commitProfilerUpdate,
|
||||
commitProfilerPostCommit,
|
||||
commitRootCallbacks,
|
||||
@@ -226,14 +219,6 @@ let nextEffect: Fiber | null = null;
|
||||
let inProgressLanes: Lanes | null = null;
|
||||
let inProgressRoot: FiberRoot | null = null;
|
||||
|
||||
function shouldProfile(current: Fiber): boolean {
|
||||
return (
|
||||
enableProfilerTimer &&
|
||||
enableProfilerCommitHooks &&
|
||||
(current.mode & ProfileMode) !== NoMode
|
||||
);
|
||||
}
|
||||
|
||||
let focusedInstanceHandle: null | Fiber = null;
|
||||
let shouldFireAfterActiveInstanceBlur: boolean = false;
|
||||
|
||||
@@ -434,6 +419,7 @@ function commitLayoutEffectOnFiber(
|
||||
break;
|
||||
}
|
||||
case HostRoot: {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
recursivelyTraverseLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
@@ -442,6 +428,10 @@ function commitLayoutEffectOnFiber(
|
||||
if (flags & Callback) {
|
||||
commitRootCallbacks(finishedWork);
|
||||
}
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
finishedRoot.effectDuration +=
|
||||
popNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HostHoistable: {
|
||||
@@ -481,39 +471,38 @@ function commitLayoutEffectOnFiber(
|
||||
break;
|
||||
}
|
||||
case Profiler: {
|
||||
recursivelyTraverseLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
committedLanes,
|
||||
);
|
||||
// TODO: Should this fire inside an offscreen tree? Or should it wait to
|
||||
// fire when the tree becomes visible again.
|
||||
if (flags & Update) {
|
||||
const {effectDuration} = finishedWork.stateNode;
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
|
||||
recursivelyTraverseLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
committedLanes,
|
||||
);
|
||||
|
||||
const profilerInstance = finishedWork.stateNode;
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
// Propagate layout effect durations to the next nearest Profiler ancestor.
|
||||
// Do not reset these values until the next render so DevTools has a chance to read them first.
|
||||
profilerInstance.effectDuration +=
|
||||
bubbleNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
|
||||
commitProfilerUpdate(
|
||||
finishedWork,
|
||||
current,
|
||||
getCommitTime(),
|
||||
effectDuration,
|
||||
profilerInstance.effectDuration,
|
||||
);
|
||||
} else {
|
||||
recursivelyTraverseLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
committedLanes,
|
||||
);
|
||||
|
||||
// Propagate layout effect durations to the next nearest Profiler ancestor.
|
||||
// Do not reset these values until the next render so DevTools has a chance to read them first.
|
||||
let parentFiber = finishedWork.return;
|
||||
outer: while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
root.effectDuration += effectDuration;
|
||||
break outer;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
parentStateNode.effectDuration += effectDuration;
|
||||
break outer;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1262,132 +1251,24 @@ function commitDeletionEffectsOnFiber(
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
if (enableHiddenSubtreeInsertionEffectCleanup) {
|
||||
// When deleting a fiber, we may need to destroy insertion or layout effects.
|
||||
// Insertion effects are not destroyed on hidden, only when destroyed, so now
|
||||
// we need to destroy them. Layout effects are destroyed when hidden, so
|
||||
// we only need to destroy them if the tree is visible.
|
||||
const updateQueue: FunctionComponentUpdateQueue | null =
|
||||
(deletedFiber.updateQueue: any);
|
||||
if (updateQueue !== null) {
|
||||
const lastEffect = updateQueue.lastEffect;
|
||||
if (lastEffect !== null) {
|
||||
const firstEffect = lastEffect.next;
|
||||
|
||||
let effect = firstEffect;
|
||||
do {
|
||||
const tag = effect.tag;
|
||||
const inst = effect.inst;
|
||||
const destroy = inst.destroy;
|
||||
if (destroy !== undefined) {
|
||||
if ((tag & HookInsertion) !== NoHookEffect) {
|
||||
// TODO: add insertion effect marks and profiling.
|
||||
if (__DEV__) {
|
||||
setIsRunningInsertionEffect(true);
|
||||
}
|
||||
|
||||
inst.destroy = undefined;
|
||||
safelyCallDestroy(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
setIsRunningInsertionEffect(false);
|
||||
}
|
||||
} else if (
|
||||
!offscreenSubtreeWasHidden &&
|
||||
(tag & HookLayout) !== NoHookEffect
|
||||
) {
|
||||
// Offscreen fibers already unmounted their layout effects.
|
||||
// We only need to destroy layout effects for visible trees.
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentLayoutEffectUnmountStarted(deletedFiber);
|
||||
}
|
||||
|
||||
if (shouldProfile(deletedFiber)) {
|
||||
startLayoutEffectTimer();
|
||||
inst.destroy = undefined;
|
||||
safelyCallDestroy(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
recordLayoutEffectDuration(deletedFiber);
|
||||
} else {
|
||||
inst.destroy = undefined;
|
||||
safelyCallDestroy(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentLayoutEffectUnmountStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
effect = effect.next;
|
||||
} while (effect !== firstEffect);
|
||||
}
|
||||
}
|
||||
} else if (!offscreenSubtreeWasHidden) {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null =
|
||||
(deletedFiber.updateQueue: any);
|
||||
if (updateQueue !== null) {
|
||||
const lastEffect = updateQueue.lastEffect;
|
||||
if (lastEffect !== null) {
|
||||
const firstEffect = lastEffect.next;
|
||||
|
||||
let effect = firstEffect;
|
||||
do {
|
||||
const tag = effect.tag;
|
||||
const inst = effect.inst;
|
||||
const destroy = inst.destroy;
|
||||
if (destroy !== undefined) {
|
||||
if ((tag & HookInsertion) !== NoHookEffect) {
|
||||
inst.destroy = undefined;
|
||||
safelyCallDestroy(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
} else if ((tag & HookLayout) !== NoHookEffect) {
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentLayoutEffectUnmountStarted(deletedFiber);
|
||||
}
|
||||
|
||||
if (shouldProfile(deletedFiber)) {
|
||||
startLayoutEffectTimer();
|
||||
inst.destroy = undefined;
|
||||
safelyCallDestroy(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
recordLayoutEffectDuration(deletedFiber);
|
||||
} else {
|
||||
inst.destroy = undefined;
|
||||
safelyCallDestroy(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentLayoutEffectUnmountStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
effect = effect.next;
|
||||
} while (effect !== firstEffect);
|
||||
}
|
||||
}
|
||||
if (
|
||||
enableHiddenSubtreeInsertionEffectCleanup ||
|
||||
!offscreenSubtreeWasHidden
|
||||
) {
|
||||
// TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.
|
||||
commitHookEffectListUnmount(
|
||||
HookInsertion,
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
}
|
||||
if (!offscreenSubtreeWasHidden) {
|
||||
commitHookLayoutUnmountEffects(
|
||||
deletedFiber,
|
||||
nearestMountedAncestor,
|
||||
HookLayout,
|
||||
);
|
||||
}
|
||||
|
||||
recursivelyTraverseDeletionEffects(
|
||||
finishedRoot,
|
||||
nearestMountedAncestor,
|
||||
@@ -1709,27 +1590,13 @@ function commitMutationEffectsOnFiber(
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
// TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.
|
||||
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
|
||||
// Layout effects are destroyed during the mutation phase so that all
|
||||
// destroy functions for all fibers are called before any create functions.
|
||||
// This prevents sibling component effects from interfering with each other,
|
||||
// e.g. a destroy function in one component should never override a ref set
|
||||
// by a create function in another component during the same commit.
|
||||
if (shouldProfile(finishedWork)) {
|
||||
startLayoutEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
}
|
||||
commitHookLayoutUnmountEffects(
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
HookLayout | HookHasEffect,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1917,6 +1784,8 @@ function commitMutationEffectsOnFiber(
|
||||
return;
|
||||
}
|
||||
case HostRoot: {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
|
||||
if (supportsResources) {
|
||||
prepareToCommitHoistables();
|
||||
|
||||
@@ -1960,6 +1829,10 @@ function commitMutationEffectsOnFiber(
|
||||
recursivelyResetForms(finishedWork);
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
root.effectDuration += popNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case HostPortal: {
|
||||
@@ -1987,6 +1860,21 @@ function commitMutationEffectsOnFiber(
|
||||
}
|
||||
return;
|
||||
}
|
||||
case Profiler: {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
|
||||
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
|
||||
commitReconciliationEffects(finishedWork);
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
const profilerInstance = finishedWork.stateNode;
|
||||
// Propagate layout effect durations to the next nearest Profiler ancestor.
|
||||
// Do not reset these values until the next render so DevTools has a chance to read them first.
|
||||
profilerInstance.effectDuration +=
|
||||
bubbleNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
|
||||
commitReconciliationEffects(finishedWork);
|
||||
@@ -2247,25 +2135,11 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
// TODO (Offscreen) Check: flags & LayoutStatic
|
||||
if (shouldProfile(finishedWork)) {
|
||||
try {
|
||||
startLayoutEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
} finally {
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
}
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
}
|
||||
|
||||
commitHookLayoutUnmountEffects(
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
HookLayout,
|
||||
);
|
||||
recursivelyTraverseDisappearLayoutEffects(finishedWork);
|
||||
break;
|
||||
}
|
||||
@@ -2395,38 +2269,37 @@ export function reappearLayoutEffects(
|
||||
break;
|
||||
}
|
||||
case Profiler: {
|
||||
recursivelyTraverseReappearLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
includeWorkInProgressEffects,
|
||||
);
|
||||
// TODO: Figure out how Profiler updates should work with Offscreen
|
||||
if (includeWorkInProgressEffects && flags & Update) {
|
||||
const {effectDuration} = finishedWork.stateNode;
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
|
||||
recursivelyTraverseReappearLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
includeWorkInProgressEffects,
|
||||
);
|
||||
|
||||
const profilerInstance = finishedWork.stateNode;
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
// Propagate layout effect durations to the next nearest Profiler ancestor.
|
||||
// Do not reset these values until the next render so DevTools has a chance to read them first.
|
||||
profilerInstance.effectDuration +=
|
||||
bubbleNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
|
||||
commitProfilerUpdate(
|
||||
finishedWork,
|
||||
current,
|
||||
getCommitTime(),
|
||||
effectDuration,
|
||||
profilerInstance.effectDuration,
|
||||
);
|
||||
} else {
|
||||
recursivelyTraverseReappearLayoutEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
includeWorkInProgressEffects,
|
||||
);
|
||||
|
||||
// Propagate layout effect durations to the next nearest Profiler ancestor.
|
||||
// Do not reset these values until the next render so DevTools has a chance to read them first.
|
||||
let parentFiber = finishedWork.return;
|
||||
outer: while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
root.effectDuration += effectDuration;
|
||||
break outer;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
parentStateNode.effectDuration += effectDuration;
|
||||
break outer;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2745,6 +2618,7 @@ function commitPassiveMountOnFiber(
|
||||
break;
|
||||
}
|
||||
case HostRoot: {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
recursivelyTraversePassiveMountEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
@@ -2800,48 +2674,50 @@ function commitPassiveMountOnFiber(
|
||||
clearTransitionsForLanes(finishedRoot, committedLanes);
|
||||
}
|
||||
}
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
finishedRoot.passiveEffectDuration +=
|
||||
popNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Profiler: {
|
||||
recursivelyTraversePassiveMountEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
committedLanes,
|
||||
committedTransitions,
|
||||
endTime,
|
||||
);
|
||||
|
||||
// Only Profilers with work in their subtree will have a Passive effect scheduled.
|
||||
if (flags & Passive) {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
|
||||
recursivelyTraversePassiveMountEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
committedLanes,
|
||||
committedTransitions,
|
||||
endTime,
|
||||
);
|
||||
|
||||
const profilerInstance = finishedWork.stateNode;
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
const {passiveEffectDuration} = finishedWork.stateNode;
|
||||
|
||||
commitProfilerPostCommit(
|
||||
finishedWork,
|
||||
finishedWork.alternate,
|
||||
// This value will still reflect the previous commit phase.
|
||||
// It does not get reset until the start of the next commit phase.
|
||||
getCommitTime(),
|
||||
passiveEffectDuration,
|
||||
);
|
||||
|
||||
// Bubble times to the next nearest ancestor Profiler.
|
||||
// After we process that Profiler, we'll bubble further up.
|
||||
let parentFiber = finishedWork.return;
|
||||
outer: while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
root.passiveEffectDuration += passiveEffectDuration;
|
||||
break outer;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
parentStateNode.passiveEffectDuration += passiveEffectDuration;
|
||||
break outer;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
profilerInstance.passiveEffectDuration +=
|
||||
bubbleNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
|
||||
commitProfilerPostCommit(
|
||||
finishedWork,
|
||||
finishedWork.alternate,
|
||||
// This value will still reflect the previous commit phase.
|
||||
// It does not get reset until the start of the next commit phase.
|
||||
getCommitTime(),
|
||||
profilerInstance.passiveEffectDuration,
|
||||
);
|
||||
} else {
|
||||
recursivelyTraversePassiveMountEffects(
|
||||
finishedRoot,
|
||||
finishedWork,
|
||||
committedLanes,
|
||||
committedTransitions,
|
||||
endTime,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3427,6 +3303,30 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HostRoot: {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
recursivelyTraversePassiveUnmountEffects(finishedWork);
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
const finishedRoot: FiberRoot = finishedWork.stateNode;
|
||||
finishedRoot.passiveEffectDuration +=
|
||||
popNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Profiler: {
|
||||
const prevEffectDuration = pushNestedEffectDurations();
|
||||
|
||||
recursivelyTraversePassiveUnmountEffects(finishedWork);
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
const profilerInstance = finishedWork.stateNode;
|
||||
// Propagate layout effect durations to the next nearest Profiler ancestor.
|
||||
// Do not reset these values until the next render so DevTools has a chance to read them first.
|
||||
profilerInstance.passiveEffectDuration +=
|
||||
bubbleNestedEffectDurations(prevEffectDuration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OffscreenComponent: {
|
||||
const instance: OffscreenInstance = finishedWork.stateNode;
|
||||
const nextState: OffscreenState | null = finishedWork.memoizedState;
|
||||
|
||||
+2
-2
@@ -112,8 +112,8 @@ function FiberRootNode(
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
this.effectDuration = 0;
|
||||
this.passiveEffectDuration = 0;
|
||||
this.effectDuration = -0;
|
||||
this.passiveEffectDuration = -0;
|
||||
}
|
||||
|
||||
if (enableUpdaterTracking) {
|
||||
|
||||
+6
-1
@@ -373,6 +373,11 @@ type TransitionTracingOnlyFiberRootProperties = {
|
||||
incompleteTransitions: Map<Transition, TracingMarkerInstance>,
|
||||
};
|
||||
|
||||
type ProfilerCommitHooksOnlyFiberRootProperties = {
|
||||
effectDuration: number,
|
||||
passiveEffectDuration: number,
|
||||
};
|
||||
|
||||
// Exported FiberRoot type includes all properties,
|
||||
// To avoid requiring potentially error-prone :any casts throughout the project.
|
||||
// The types are defined separately within this file to ensure they stay in sync.
|
||||
@@ -381,7 +386,7 @@ export type FiberRoot = {
|
||||
...SuspenseCallbackOnlyFiberRootProperties,
|
||||
...UpdaterTrackingOnlyFiberRootProperties,
|
||||
...TransitionTracingOnlyFiberRootProperties,
|
||||
...
|
||||
...ProfilerCommitHooksOnlyFiberRootProperties,
|
||||
};
|
||||
|
||||
type BasicStateAction<S> = (S => S) | S;
|
||||
|
||||
+44
-73
@@ -14,7 +14,6 @@ import {
|
||||
enableProfilerNestedUpdatePhase,
|
||||
enableProfilerTimer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {HostRoot, Profiler} from './ReactWorkTags';
|
||||
|
||||
// Intentionally not named imports because Rollup would use dynamic dispatch for
|
||||
// CommonJS interop named imports.
|
||||
@@ -35,11 +34,38 @@ export type ProfilerTimer = {
|
||||
...
|
||||
};
|
||||
|
||||
let completeTime: number = 0;
|
||||
let commitTime: number = 0;
|
||||
let layoutEffectStartTime: number = -1;
|
||||
let profilerStartTime: number = -1;
|
||||
let passiveEffectStartTime: number = -1;
|
||||
let completeTime: number = -0;
|
||||
let commitTime: number = -0;
|
||||
let profilerStartTime: number = -1.1;
|
||||
let profilerEffectDuration: number = -0;
|
||||
|
||||
function pushNestedEffectDurations(): number {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return 0;
|
||||
}
|
||||
const prevEffectDuration = profilerEffectDuration;
|
||||
profilerEffectDuration = 0; // Reset counter.
|
||||
return prevEffectDuration;
|
||||
}
|
||||
|
||||
function popNestedEffectDurations(prevEffectDuration: number): number {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return 0;
|
||||
}
|
||||
const elapsedTime = profilerEffectDuration;
|
||||
profilerEffectDuration = prevEffectDuration;
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
// Like pop but it also adds the current elapsed time to the parent scope.
|
||||
function bubbleNestedEffectDurations(prevEffectDuration: number): number {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return 0;
|
||||
}
|
||||
const elapsedTime = profilerEffectDuration;
|
||||
profilerEffectDuration += prevEffectDuration;
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
|
||||
@@ -153,83 +179,27 @@ function stopProfilerTimerIfRunningAndRecordIncompleteDuration(
|
||||
}
|
||||
}
|
||||
|
||||
function recordLayoutEffectDuration(fiber: Fiber): void {
|
||||
function recordEffectDuration(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (layoutEffectStartTime >= 0) {
|
||||
const elapsedTime = now() - layoutEffectStartTime;
|
||||
if (profilerStartTime >= 0) {
|
||||
const elapsedTime = now() - profilerStartTime;
|
||||
|
||||
layoutEffectStartTime = -1;
|
||||
profilerStartTime = -1;
|
||||
|
||||
// Store duration on the next nearest Profiler ancestor
|
||||
// Or the root (for the DevTools Profiler to read)
|
||||
let parentFiber = fiber.return;
|
||||
while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
root.effectDuration += elapsedTime;
|
||||
return;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
parentStateNode.effectDuration += elapsedTime;
|
||||
return;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
profilerEffectDuration += elapsedTime;
|
||||
}
|
||||
}
|
||||
|
||||
function recordPassiveEffectDuration(fiber: Fiber): void {
|
||||
function startEffectTimer(): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (passiveEffectStartTime >= 0) {
|
||||
const elapsedTime = now() - passiveEffectStartTime;
|
||||
|
||||
passiveEffectStartTime = -1;
|
||||
|
||||
// Store duration on the next nearest Profiler ancestor
|
||||
// Or the root (for the DevTools Profiler to read)
|
||||
let parentFiber = fiber.return;
|
||||
while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
if (root !== null) {
|
||||
root.passiveEffectDuration += elapsedTime;
|
||||
}
|
||||
return;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
if (parentStateNode !== null) {
|
||||
// Detached fibers have their state node cleared out.
|
||||
// In this case, the return pointer is also cleared out,
|
||||
// so we won't be able to report the time spent in this Profiler's subtree.
|
||||
parentStateNode.passiveEffectDuration += elapsedTime;
|
||||
}
|
||||
return;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startLayoutEffectTimer(): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
layoutEffectStartTime = now();
|
||||
}
|
||||
|
||||
function startPassiveEffectTimer(): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
passiveEffectStartTime = now();
|
||||
profilerStartTime = now();
|
||||
}
|
||||
|
||||
function transferActualDuration(fiber: Fiber): void {
|
||||
@@ -251,15 +221,16 @@ export {
|
||||
recordCommitTime,
|
||||
isCurrentUpdateNested,
|
||||
markNestedUpdateScheduled,
|
||||
recordLayoutEffectDuration,
|
||||
recordPassiveEffectDuration,
|
||||
recordEffectDuration,
|
||||
resetNestedUpdateFlag,
|
||||
startLayoutEffectTimer,
|
||||
startPassiveEffectTimer,
|
||||
startEffectTimer,
|
||||
startProfilerTimer,
|
||||
stopProfilerTimerIfRunning,
|
||||
stopProfilerTimerIfRunningAndRecordDuration,
|
||||
stopProfilerTimerIfRunningAndRecordIncompleteDuration,
|
||||
syncNestedUpdateFlag,
|
||||
transferActualDuration,
|
||||
pushNestedEffectDurations,
|
||||
popNestedEffectDurations,
|
||||
bubbleNestedEffectDurations,
|
||||
};
|
||||
|
||||
@@ -1643,7 +1643,7 @@ describe(`onCommit`, () => {
|
||||
expect(call).toHaveLength(4);
|
||||
expect(call[0]).toBe('root-update');
|
||||
expect(call[1]).toBe('update');
|
||||
expect(call[2]).toBe(1100); // durations
|
||||
expect(call[2]).toBe(11100); // durations
|
||||
expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
|
||||
});
|
||||
|
||||
@@ -1952,11 +1952,7 @@ describe(`onPostCommit`, () => {
|
||||
expect(call).toHaveLength(4);
|
||||
expect(call[0]).toBe('unmount-test');
|
||||
expect(call[1]).toBe('update');
|
||||
// TODO (bvaughn) The duration reported below should be 10100, but is 0
|
||||
// by the time the passive effect is flushed its parent Fiber pointer is gone.
|
||||
// If we refactor to preserve the unmounted Fiber tree we could fix this.
|
||||
// The current implementation would require too much extra overhead to track this.
|
||||
expect(call[2]).toBe(0); // durations
|
||||
expect(call[2]).toBe(10100); // durations
|
||||
expect(call[3]).toBe(12030); // commit start time (before mutations or effects)
|
||||
});
|
||||
|
||||
@@ -2085,7 +2081,7 @@ describe(`onPostCommit`, () => {
|
||||
expect(call).toHaveLength(4);
|
||||
expect(call[0]).toBe('root-update');
|
||||
expect(call[1]).toBe('update');
|
||||
expect(call[2]).toBe(1100); // durations
|
||||
expect(call[2]).toBe(11100); // durations
|
||||
expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
|
||||
});
|
||||
|
||||
@@ -2300,7 +2296,7 @@ describe(`onPostCommit`, () => {
|
||||
expect(call).toHaveLength(4);
|
||||
expect(call[0]).toBe('root');
|
||||
expect(call[1]).toBe('update');
|
||||
expect(call[2]).toBe(100000000); // durations
|
||||
expect(call[2]).toBe(100001000); // durations
|
||||
// The commit time varies because the above duration time varies
|
||||
expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user