[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:
Sebastian Markbåge
2024-09-17 15:12:16 -04:00
committed by GitHub
parent 7b56a54298
commit 4549be0f84
7 changed files with 273 additions and 376 deletions
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
});