Skip to content

Commit 26157b4

Browse files
author
Brian Vaughn
committed
Implemented Profiler onCommit() and onPostCommit() hooks
1 parent f8aad4e commit 26157b4

File tree

6 files changed

+1497
-49
lines changed

6 files changed

+1497
-49
lines changed

packages/react-reconciler/src/ReactFiber.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -815,13 +815,8 @@ function createFiberFromProfiler(
815815
key: null | string,
816816
): Fiber {
817817
if (__DEV__) {
818-
if (
819-
typeof pendingProps.id !== 'string' ||
820-
typeof pendingProps.onRender !== 'function'
821-
) {
822-
console.error(
823-
'Profiler must specify an "id" string and "onRender" function as props',
824-
);
818+
if (typeof pendingProps.id !== 'string') {
819+
console.error('Profiler must specify an "id" as a prop');
825820
}
826821
}
827822

@@ -831,6 +826,13 @@ function createFiberFromProfiler(
831826
fiber.type = REACT_PROFILER_TYPE;
832827
fiber.expirationTime = expirationTime;
833828

829+
if (enableProfilerTimer) {
830+
fiber.stateNode = {
831+
effectDuration: 0,
832+
passiveEffectDuration: 0,
833+
};
834+
}
835+
834836
return fiber;
835837
}
836838

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,12 @@ function updateProfiler(
577577
) {
578578
if (enableProfilerTimer) {
579579
workInProgress.effectTag |= Update;
580+
581+
// Reset effect durations for the next eventual effect phase.
582+
// These are reset during render to allow the DevTools commit hook a chance to read them,
583+
const stateNode = workInProgress.stateNode;
584+
stateNode.effectDuration = 0;
585+
stateNode.passiveEffectDuration = 0;
580586
}
581587
const nextProps = workInProgress.pendingProps;
582588
const nextChildren = nextProps.children;
@@ -2974,6 +2980,12 @@ function beginWork(
29742980
if (hasChildWork) {
29752981
workInProgress.effectTag |= Update;
29762982
}
2983+
2984+
// Reset effect durations for the next eventual effect phase.
2985+
// These are reset during render to allow the DevTools commit hook a chance to read them,
2986+
const stateNode = workInProgress.stateNode;
2987+
stateNode.effectDuration = 0;
2988+
stateNode.passiveEffectDuration = 0;
29772989
}
29782990
break;
29792991
case SuspenseComponent: {

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 204 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,14 @@ import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
7575
import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
7676
import {logCapturedError} from './ReactFiberErrorLogger';
7777
import {resolveDefaultProps} from './ReactFiberLazyComponent';
78-
import {getCommitTime} from './ReactProfilerTimer';
78+
import {
79+
getCommitTime,
80+
recordLayoutEffectDuration,
81+
recordPassiveEffectDuration,
82+
startLayoutEffectTimer,
83+
startPassiveEffectTimer,
84+
} from './ReactProfilerTimer';
85+
import {ProfileMode} from './ReactTypeOfMode';
7986
import {commitUpdateQueue} from './ReactUpdateQueue';
8087
import {
8188
getPublicInstance,
@@ -401,11 +408,18 @@ export function commitPassiveHookEffects(finishedWork: Fiber): void {
401408
case ForwardRef:
402409
case SimpleMemoComponent:
403410
case Chunk: {
404-
// TODO (#17945) We should call all passive destroy functions (for all fibers)
405-
// before calling any create functions. The current approach only serializes
406-
// these for a single fiber.
407-
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
408-
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
411+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
412+
try {
413+
startPassiveEffectTimer();
414+
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
415+
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
416+
} finally {
417+
recordPassiveEffectDuration(finishedWork);
418+
}
419+
} else {
420+
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
421+
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
422+
}
409423
break;
410424
}
411425
default:
@@ -421,7 +435,16 @@ export function commitPassiveHookUnmountEffects(finishedWork: Fiber): void {
421435
case ForwardRef:
422436
case SimpleMemoComponent:
423437
case Chunk: {
424-
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
438+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
439+
try {
440+
startPassiveEffectTimer();
441+
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
442+
} finally {
443+
recordPassiveEffectDuration(finishedWork);
444+
}
445+
} else {
446+
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
447+
}
425448
break;
426449
}
427450
default:
@@ -437,7 +460,16 @@ export function commitPassiveHookMountEffects(finishedWork: Fiber): void {
437460
case ForwardRef:
438461
case SimpleMemoComponent:
439462
case Chunk: {
440-
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
463+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
464+
try {
465+
startPassiveEffectTimer();
466+
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
467+
} finally {
468+
recordPassiveEffectDuration(finishedWork);
469+
}
470+
} else {
471+
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
472+
}
441473
break;
442474
}
443475
default:
@@ -446,6 +478,61 @@ export function commitPassiveHookMountEffects(finishedWork: Fiber): void {
446478
}
447479
}
448480

481+
export function commitPassiveEffectDurations(
482+
finishedRoot: FiberRoot,
483+
finishedWork: Fiber,
484+
): void {
485+
if (enableProfilerTimer) {
486+
// Only Profilers with work in their subtree will have an Update effect scheduled.
487+
if ((finishedWork.effectTag & Update) !== NoEffect) {
488+
switch (finishedWork.tag) {
489+
case Profiler: {
490+
const {passiveEffectDuration} = finishedWork.stateNode;
491+
const {id, onPostCommit} = finishedWork.memoizedProps;
492+
493+
// This value will still reflect the previous commit phase.
494+
// It does not get reset until the start of the next commit phase.
495+
const commitTime = getCommitTime();
496+
497+
if (typeof onPostCommit === 'function') {
498+
if (enableSchedulerTracing) {
499+
onPostCommit(
500+
id,
501+
finishedWork.alternate === null ? 'mount' : 'update',
502+
passiveEffectDuration,
503+
commitTime,
504+
finishedRoot.memoizedInteractions,
505+
);
506+
} else {
507+
onPostCommit(
508+
id,
509+
finishedWork.alternate === null ? 'mount' : 'update',
510+
passiveEffectDuration,
511+
commitTime,
512+
);
513+
}
514+
}
515+
516+
// Bubble times to the next nearest ancestor Profiler.
517+
// After we process that Profiler, we'll bubble further up.
518+
let parentFiber = finishedWork.return;
519+
while (parentFiber !== null) {
520+
if (parentFiber.tag === Profiler) {
521+
const parentStateNode = parentFiber.stateNode;
522+
parentStateNode.passiveEffectDuration += passiveEffectDuration;
523+
break;
524+
}
525+
parentFiber = parentFiber.return;
526+
}
527+
break;
528+
}
529+
default:
530+
break;
531+
}
532+
}
533+
}
534+
}
535+
449536
function commitLifeCycles(
450537
finishedRoot: FiberRoot,
451538
current: Fiber | null,
@@ -461,7 +548,16 @@ function commitLifeCycles(
461548
// This is done to prevent sibling component effects from interfering with each other,
462549
// e.g. a destroy function in one component should never override a ref set
463550
// by a create function in another component during the same commit.
464-
commitHookEffectList(NoHookEffect, HookLayout, finishedWork);
551+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
552+
try {
553+
startLayoutEffectTimer();
554+
commitHookEffectList(NoHookEffect, HookLayout, finishedWork);
555+
} finally {
556+
recordLayoutEffectDuration(finishedWork);
557+
}
558+
} else {
559+
commitHookEffectList(NoHookEffect, HookLayout, finishedWork);
560+
}
465561
return;
466562
}
467563
case ClassComponent: {
@@ -499,7 +595,16 @@ function commitLifeCycles(
499595
}
500596
}
501597
}
502-
instance.componentDidMount();
598+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
599+
try {
600+
startLayoutEffectTimer();
601+
instance.componentDidMount();
602+
} finally {
603+
recordLayoutEffectDuration(finishedWork);
604+
}
605+
} else {
606+
instance.componentDidMount();
607+
}
503608
stopPhaseTimer();
504609
} else {
505610
const prevProps =
@@ -538,11 +643,24 @@ function commitLifeCycles(
538643
}
539644
}
540645
}
541-
instance.componentDidUpdate(
542-
prevProps,
543-
prevState,
544-
instance.__reactInternalSnapshotBeforeUpdate,
545-
);
646+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
647+
try {
648+
startLayoutEffectTimer();
649+
instance.componentDidUpdate(
650+
prevProps,
651+
prevState,
652+
instance.__reactInternalSnapshotBeforeUpdate,
653+
);
654+
} finally {
655+
recordLayoutEffectDuration(finishedWork);
656+
}
657+
} else {
658+
instance.componentDidUpdate(
659+
prevProps,
660+
prevState,
661+
instance.__reactInternalSnapshotBeforeUpdate,
662+
);
663+
}
546664
stopPhaseTimer();
547665
}
548666
}
@@ -635,7 +753,10 @@ function commitLifeCycles(
635753
}
636754
case Profiler: {
637755
if (enableProfilerTimer) {
638-
const onRender = finishedWork.memoizedProps.onRender;
756+
const {onCommit, onRender} = finishedWork.memoizedProps;
757+
const {effectDuration} = finishedWork.stateNode;
758+
759+
const commitTime = getCommitTime();
639760

640761
if (typeof onRender === 'function') {
641762
if (enableSchedulerTracing) {
@@ -645,7 +766,7 @@ function commitLifeCycles(
645766
finishedWork.actualDuration,
646767
finishedWork.treeBaseDuration,
647768
finishedWork.actualStartTime,
648-
getCommitTime(),
769+
commitTime,
649770
finishedRoot.memoizedInteractions,
650771
);
651772
} else {
@@ -655,10 +776,41 @@ function commitLifeCycles(
655776
finishedWork.actualDuration,
656777
finishedWork.treeBaseDuration,
657778
finishedWork.actualStartTime,
658-
getCommitTime(),
779+
commitTime,
780+
);
781+
}
782+
}
783+
784+
if (typeof onCommit === 'function') {
785+
if (enableSchedulerTracing) {
786+
onCommit(
787+
finishedWork.memoizedProps.id,
788+
current === null ? 'mount' : 'update',
789+
effectDuration,
790+
commitTime,
791+
finishedRoot.memoizedInteractions,
792+
);
793+
} else {
794+
onCommit(
795+
finishedWork.memoizedProps.id,
796+
current === null ? 'mount' : 'update',
797+
effectDuration,
798+
commitTime,
659799
);
660800
}
661801
}
802+
803+
// Propagate layout effect durations to the next nearest Profiler ancestor.
804+
// Do not reset these values until the next render so DevTools has a chance to read them first.
805+
let parentFiber = finishedWork.return;
806+
while (parentFiber !== null) {
807+
if (parentFiber.tag === Profiler) {
808+
const parentStateNode = parentFiber.stateNode;
809+
parentStateNode.effectDuration += effectDuration;
810+
break;
811+
}
812+
parentFiber = parentFiber.return;
813+
}
662814
}
663815
return;
664816
}
@@ -806,7 +958,13 @@ function commitUnmount(
806958
if ((tag & HookPassive) !== NoHookEffect) {
807959
enqueuePendingPassiveEffectDestroyFn(destroy);
808960
} else {
809-
safelyCallDestroy(current, destroy);
961+
if (enableProfilerTimer && current.mode & ProfileMode) {
962+
startLayoutEffectTimer();
963+
safelyCallDestroy(current, destroy);
964+
recordLayoutEffectDuration(current);
965+
} else {
966+
safelyCallDestroy(current, destroy);
967+
}
810968
}
811969
}
812970
effect = effect.next;
@@ -847,7 +1005,13 @@ function commitUnmount(
8471005
safelyDetachRef(current);
8481006
const instance = current.stateNode;
8491007
if (typeof instance.componentWillUnmount === 'function') {
850-
safelyCallComponentWillUnmount(current, instance);
1008+
if (enableProfilerTimer && current.mode & ProfileMode) {
1009+
startLayoutEffectTimer();
1010+
safelyCallComponentWillUnmount(current, instance);
1011+
recordLayoutEffectDuration(current);
1012+
} else {
1013+
safelyCallComponentWillUnmount(current, instance);
1014+
}
8511015
}
8521016
return;
8531017
}
@@ -1347,7 +1511,16 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
13471511
// This prevents sibling component effects from interfering with each other,
13481512
// e.g. a destroy function in one component should never override a ref set
13491513
// by a create function in another component during the same commit.
1350-
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
1514+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
1515+
try {
1516+
startLayoutEffectTimer();
1517+
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
1518+
} finally {
1519+
recordLayoutEffectDuration(finishedWork);
1520+
}
1521+
} else {
1522+
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
1523+
}
13511524
return;
13521525
}
13531526
case Profiler: {
@@ -1390,7 +1563,16 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
13901563
// This prevents sibling component effects from interfering with each other,
13911564
// e.g. a destroy function in one component should never override a ref set
13921565
// by a create function in another component during the same commit.
1393-
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
1566+
if (enableProfilerTimer && finishedWork.mode & ProfileMode) {
1567+
try {
1568+
startLayoutEffectTimer();
1569+
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
1570+
} finally {
1571+
recordLayoutEffectDuration(finishedWork);
1572+
}
1573+
} else {
1574+
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
1575+
}
13941576
return;
13951577
}
13961578
case ClassComponent: {

0 commit comments

Comments
 (0)