Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ProfileRoot component for collecting new time metrics #12745

Merged
merged 72 commits into from
May 10, 2018
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
a3b92fc
Added ProfileMode and enableProfileModeMetrics feature flag
Apr 30, 2018
cb2e802
Added more tests. Fixed premature bailout cases.
May 1, 2018
5af43eb
Convert ProfileMode to new, non-Mode based ProfileRoot component type
May 1, 2018
808c5e7
Renamed "profile mode" in a few places to "profile root"
May 1, 2018
65a33f9
Added some more (failing) tests
May 1, 2018
afb88c3
Updated TODO comments
May 1, 2018
e812f14
Added ReactProfileTimer. Addressed many TODOs. Most tests passing loc…
May 2, 2018
a0f3500
Don't update base time for bailouts
May 2, 2018
732ec35
Added some comments and renamed a few methods
May 2, 2018
0e86e9a
Added DEV error checking for unexpected base render timer start/stopping
May 3, 2018
4e987db
Added error handling behavior (and test)
May 3, 2018
8d86fad
Cleaned up and improved tests
May 4, 2018
1c2ae58
Added test with replayFailedUnitOfWorkWithInvokeGuardedCallback enabled
May 4, 2018
f88b562
Renamed a few tests (nits)
May 4, 2018
0942eb8
Cleaned up snapshot tests
May 4, 2018
2e1a4e3
Don't include time between frames in actual time measurements
May 4, 2018
004940c
Added addl dev checks for emtpy stack, and improved pause/resume beha…
May 4, 2018
9983cc0
Added label prop to ProfileRoot component name
May 4, 2018
91af221
Added tests for shallow renderer and toTree/findByType
May 4, 2018
9f0c4d4
Improved pause/resume time calculation to not require traversing stack
May 4, 2018
643399b
Pop ProfileRoot from timer stack on interruption
May 4, 2018
f609d87
Simplified API for timer pause/resume
May 4, 2018
899529f
Test naming nit picks
May 4, 2018
1ea1562
Removed dead code
May 4, 2018
ebc4e99
Tidied up comments and formatting a bit
May 4, 2018
909b19a
Added a test for getDerivedStateFromCatch()
May 4, 2018
2d9bc67
Reanmed ProfileRoot label attribute to id
May 4, 2018
8b44f88
Fixed test
May 4, 2018
4743d65
Added timing test coverage for lifecycles
May 4, 2018
0431206
Fixed prod bundle test with __DEV__ conditional
May 4, 2018
aabb36f
Replace unnecssary stateNode Object wrapper with number
May 4, 2018
52df41c
Renamed ProfileRoot -> Profiler
May 4, 2018
f3e0fcf
Renamed callback prop -> onRender
May 4, 2018
e4ff3d2
Renamed ReactProfileRoot-test -> ReactProfiler-test
May 4, 2018
20eff9a
Renamed snapshot
May 4, 2018
6f803d3
Moved bubbling of base times from ReactFiberCompleteWork -> ReactFibe…
May 4, 2018
3ff9121
Removed unnecessary CommitProfile effect tag in favor of Update
May 4, 2018
6c9b1e2
Bailout on Profiler if memoized and pending props are equal
May 5, 2018
081a83b
Fixed props memoization typo for Profiler component
May 5, 2018
bae55f4
Replaced onRender.call with onRender()
May 5, 2018
0a4b44d
Small tweaks based on Seb's feedback
May 6, 2018
d1c0ad1
Removed unnecessary conditionals
May 7, 2018
417c9b4
Updated ReactProfileTimer to use HostConfig now() timer
May 7, 2018
1f132db
Fixed funky stack cursor Flow typing
May 7, 2018
7ea2277
Combined expiration and base render time bubbling into one loop
May 7, 2018
13dae8b
Moved render timing check to be better colocated with uer timing check
May 7, 2018
ae8b527
Wrapped timer call in enableProfileModeMetrics conditional
May 7, 2018
305db33
Don't prevent bailout unnecessarily for Profiler component
May 7, 2018
b8782e5
Fixed Flow type for selfBaseTime and treeBaseTime in Fiber
May 7, 2018
4e54ff6
Refactored base render timer logic a bit to remove redundant check
May 7, 2018
45cd176
Reset totalElapsedPauseTime after commit (at the end of commitRoot)
May 7, 2018
283652a
Refactored actual render timer to use shared ReactFiberStack
May 7, 2018
f834d67
Removed unnecessary function call
May 7, 2018
2977521
Fixed Flow type for ReactFiber selfBaseTime/treeBaseTime
May 7, 2018
abf999d
Added an additional pause/resume test
May 7, 2018
f551e4d
Renamed ReactProfileTimer -> ReactProfilerTimer
May 8, 2018
2d909ae
Moved all timer functions inside of createProfilerTimer factory
May 8, 2018
9ad72e2
Removed ReactFiberStack from ProfilerTimer. Kept DEV only push/pop wa…
May 8, 2018
4923a9d
Removed unnecessary else conditinoal in scheduler
May 8, 2018
5e14a49
Destructured ProfilerTimer methods to avoid unnecessary property access
May 8, 2018
25fcd3a
Converted Profiler stateNode from number to wrapper Object
May 8, 2018
120a6d7
Removed source of potential false positive from test
May 8, 2018
bde491e
Added a test for change to id prop
May 8, 2018
c5e44c2
Expanded update interrupt test to be more thorough
May 9, 2018
8485467
TestRenderer unstable_flushSync returns yielded values (so you can as…
May 9, 2018
da2686d
Added a better interrupt test case
May 9, 2018
48f113c
Prettier
May 9, 2018
d55372c
Tidied up tests with more explicit flush/yield expectations and inlin…
May 9, 2018
fd731b4
Renamed getComponentName() output from ProfileMode -> Profiler
May 10, 2018
68b0553
Update snapshot
May 10, 2018
71034a7
Conditionally return from createProfilerTimer based on feature flag
May 10, 2018
e3a2562
Renamed flag enableProfileModeMetrics -> enableProfilerTiming and a f…
May 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
REACT_CALL_TYPE,
REACT_RETURN_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
Expand Down Expand Up @@ -811,6 +812,7 @@ class ReactDOMServerRenderer {
switch (elementType) {
case REACT_STRICT_MODE_TYPE:
case REACT_ASYNC_MODE_TYPE:
case REACT_PROFILER_TYPE:
case REACT_FRAGMENT_TYPE: {
const nextChildren = toArray(
((nextChild: any): ReactElement).props.children,
Expand Down
6 changes: 6 additions & 0 deletions packages/react-is/src/ReactIs.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
} from 'shared/ReactSymbols';
Expand All @@ -32,6 +33,7 @@ export function typeOf(object: any) {
switch (type) {
case REACT_ASYNC_MODE_TYPE:
case REACT_FRAGMENT_TYPE:
case REACT_PROFILER_TYPE:
case REACT_STRICT_MODE_TYPE:
return type;
default:
Expand Down Expand Up @@ -60,6 +62,7 @@ export const ContextProvider = REACT_PROVIDER_TYPE;
export const Element = REACT_ELEMENT_TYPE;
export const ForwardRef = REACT_FORWARD_REF_TYPE;
export const Fragment = REACT_FRAGMENT_TYPE;
export const Profiler = REACT_PROFILER_TYPE;
export const Portal = REACT_PORTAL_TYPE;
export const StrictMode = REACT_STRICT_MODE_TYPE;

Expand Down Expand Up @@ -87,6 +90,9 @@ export function isForwardRef(object: any) {
export function isFragment(object: any) {
return typeOf(object) === REACT_FRAGMENT_TYPE;
}
export function isProfiler(object: any) {
return typeOf(object) === REACT_PROFILER_TYPE;
}
export function isPortal(object: any) {
return typeOf(object) === REACT_PORTAL_TYPE;
}
Expand Down
14 changes: 14 additions & 0 deletions packages/react-is/src/__tests__/ReactIs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,18 @@ describe('ReactIs', () => {
expect(ReactIs.isStrictMode(<React.unstable_AsyncMode />)).toBe(false);
expect(ReactIs.isStrictMode(<div />)).toBe(false);
});

it('should identify profile root', () => {
expect(
ReactIs.typeOf(<React.unstable_Profiler id="foo" onRender={jest.fn()} />),
).toBe(ReactIs.Profiler);
expect(
ReactIs.isProfiler(
<React.unstable_Profiler id="foo" onRender={jest.fn()} />,
),
).toBe(true);
expect(ReactIs.isProfiler({type: ReactIs.unstable_Profiler})).toBe(false);
expect(ReactIs.isProfiler(<React.unstable_AsyncMode />)).toBe(false);
expect(ReactIs.isProfiler(<div />)).toBe(false);
});
});
59 changes: 58 additions & 1 deletion packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {UpdateQueue} from './ReactUpdateQueue';

import invariant from 'fbjs/lib/invariant';
import {enableProfileModeMetrics} from 'shared/ReactFeatureFlags';
import {NoEffect} from 'shared/ReactTypeOfSideEffect';
import {
IndeterminateComponent,
Expand All @@ -30,17 +31,19 @@ import {
Mode,
ContextProvider,
ContextConsumer,
Profiler,
} from 'shared/ReactTypeOfWork';
import getComponentName from 'shared/getComponentName';

import {NoWork} from './ReactFiberExpirationTime';
import {NoContext, AsyncMode, StrictMode} from './ReactTypeOfMode';
import {NoContext, AsyncMode, ProfileMode, StrictMode} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_RETURN_TYPE,
REACT_CALL_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_ASYNC_MODE_TYPE,
Expand Down Expand Up @@ -150,6 +153,10 @@ export type Fiber = {|
// memory if we need to.
alternate: Fiber | null,

// Profiling metrics
selfBaseTime?: number,
treeBaseTime?: number,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. We should always assign them and they should always a consistent type value. E.g. 0.

Making them optional is a huge no, no since it might break the hidden class of the fiber.

Copy link
Contributor Author

@bvaughn bvaughn May 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how to type them- given that we only assign these values if the feature flag is enabled. (We don't want to add them to every fiber if the flag isn't even enabled- right?)

Copy link
Contributor Author

@bvaughn bvaughn May 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, they are always either on or off. It's based on the feature flag. This will not break the hidden class of a fiber, since this is a bundle-wide thing.

  if (enableProfileModeMetrics) {
    this.selfBaseTime = 0;
    this.treeBaseTime = 0;
  }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I see. I suppose we do that with DEV only too. Seems odd that Flow doesn't force you to check for their existence everywhere then? We shouldn't need to check if they're undefined/null but seems like Flow would want us to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I expected that also.


// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.
Expand Down Expand Up @@ -204,6 +211,11 @@ function FiberNode(

this.alternate = null;

if (enableProfileModeMetrics) {
this.selfBaseTime = 0;
this.treeBaseTime = 0;
}

if (__DEV__) {
this._debugID = debugCounter++;
this._debugSource = null;
Expand Down Expand Up @@ -298,6 +310,11 @@ export function createWorkInProgress(
workInProgress.index = current.index;
workInProgress.ref = current.ref;

if (enableProfileModeMetrics) {
workInProgress.selfBaseTime = current.selfBaseTime;
workInProgress.treeBaseTime = current.treeBaseTime;
}

return workInProgress;
}

Expand Down Expand Up @@ -343,6 +360,13 @@ export function createFiberFromElement(
fiberTag = Mode;
mode |= StrictMode;
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfileMode(
pendingProps,
mode,
expirationTime,
key,
);
case REACT_CALL_TYPE:
fiberTag = CallComponent;
break;
Expand Down Expand Up @@ -440,6 +464,35 @@ export function createFiberFromFragment(
return fiber;
}

export function createFiberFromProfileMode(
pendingProps: any,
mode: TypeOfMode,
expirationTime: ExpirationTime,
key: null | string,
): Fiber {
if (__DEV__) {
if (
typeof pendingProps.id !== 'string' ||
typeof pendingProps.onRender !== 'function'
) {
invariant(
false,
'ProfileMode must specify an "id" string and "onRender" function as props',
);
}
}

const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
fiber.type = REACT_PROFILER_TYPE;
fiber.expirationTime = expirationTime;
fiber.stateNode = {
duration: 0,
startTime: 0,
};

return fiber;
}

export function createFiberFromText(
content: string,
mode: TypeOfMode,
Expand Down Expand Up @@ -509,6 +562,10 @@ export function assignFiberPropertiesInDEV(
target.lastEffect = source.lastEffect;
target.expirationTime = source.expirationTime;
target.alternate = source.alternate;
if (enableProfileModeMetrics) {
target.selfBaseTime = source.selfBaseTime;
target.treeBaseTime = source.treeBaseTime;
}
target._debugID = source._debugID;
target._debugSource = source._debugSource;
target._debugOwner = source._debugOwner;
Expand Down
50 changes: 50 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {NewContext} from './ReactFiberNewContext';
import type {HydrationContext} from './ReactFiberHydrationContext';
import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {ProfilerTimer} from './ReactProfilerTimer';
import checkPropTypes from 'prop-types/checkPropTypes';

import {
Expand All @@ -34,6 +35,7 @@ import {
Mode,
ContextProvider,
ContextConsumer,
Profiler,
} from 'shared/ReactTypeOfWork';
import {
NoEffect,
Expand All @@ -42,12 +44,14 @@ import {
ContentReset,
Ref,
DidCapture,
Update,
} from 'shared/ReactTypeOfSideEffect';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import {
enableGetDerivedStateFromCatch,
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
enableProfileModeMetrics,
} from 'shared/ReactFeatureFlags';
import invariant from 'fbjs/lib/invariant';
import getComponentName from 'shared/getComponentName';
Expand Down Expand Up @@ -88,13 +92,19 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
hydrationContext: HydrationContext<C, CX>,
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
profilerTimer: ProfilerTimer,
) {
const {shouldSetTextContent, shouldDeprioritizeSubtree} = config;

const {pushHostContext, pushHostContainer} = hostContext;

const {pushProvider} = newContext;

const {
markActualRenderTimeStarted,
stopBaseRenderTimerIfRunning,
} = profilerTimer;

const {
getMaskedContext,
getUnmaskedContext,
Expand Down Expand Up @@ -215,6 +225,25 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
return workInProgress.child;
}

function updateProfiler(current, workInProgress) {
const nextProps = workInProgress.pendingProps;
if (enableProfileModeMetrics) {
// Start render timer here and push start time onto queue
markActualRenderTimeStarted(workInProgress);

// Let the "complete" phase know to stop the timer,
// And the scheduler to record the measured time.
workInProgress.effectTag |= Update;
}
if (workInProgress.memoizedProps === nextProps) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}

function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
Expand Down Expand Up @@ -344,6 +373,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
// the new API.
// TODO: Warn in a future release.
nextChildren = null;

if (enableProfileModeMetrics) {
stopBaseRenderTimerIfRunning();
}
} else {
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('render');
Expand Down Expand Up @@ -1054,6 +1087,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
): Fiber | null {
cancelWorkTimer(workInProgress);

if (enableProfileModeMetrics) {
// Don't update "base" render times for bailouts.
stopBaseRenderTimerIfRunning();
}

// TODO: We should ideally be able to bail out early if the children have no
// more work to do. However, since we don't have a separation of this
// Fiber's priority and its children yet - we don't know without doing lots
Expand All @@ -1075,6 +1113,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
function bailoutOnLowPriority(current, workInProgress) {
cancelWorkTimer(workInProgress);

if (enableProfileModeMetrics) {
// Don't update "base" render times for bailouts.
stopBaseRenderTimerIfRunning();
}

// TODO: Handle HostComponent tags here as well and call pushHostContext()?
// See PR 8590 discussion for context
switch (workInProgress.tag) {
Expand All @@ -1093,6 +1136,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
case ContextProvider:
pushProvider(workInProgress);
break;
case Profiler:
if (enableProfileModeMetrics) {
markActualRenderTimeStarted(workInProgress);
}
break;
}
// TODO: What if this is currently in progress?
// How can that happen? How is this not being cloned?
Expand Down Expand Up @@ -1173,6 +1221,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
return updateFragment(current, workInProgress);
case Mode:
return updateMode(current, workInProgress);
case Profiler:
return updateProfiler(current, workInProgress);
case ContextProvider:
return updateContextProvider(
current,
Expand Down
26 changes: 24 additions & 2 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
enableMutatingReconciler,
enableNoopReconciler,
enablePersistentReconciler,
enableProfileModeMetrics,
} from 'shared/ReactFeatureFlags';
import {
ClassComponent,
Expand All @@ -25,13 +26,14 @@ import {
HostText,
HostPortal,
CallComponent,
Profiler,
} from 'shared/ReactTypeOfWork';
import ReactErrorUtils from 'shared/ReactErrorUtils';
import {
Placement,
Update,
ContentReset,
Placement,
Snapshot,
Update,
} from 'shared/ReactTypeOfSideEffect';
import {commitUpdateQueue} from './ReactUpdateQueue';
import invariant from 'fbjs/lib/invariant';
Expand Down Expand Up @@ -308,6 +310,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
// We have no life-cycles associated with portals.
return;
}
case Profiler: {
// We have no life-cycles associated with Profiler.
return;
}
default: {
invariant(
false,
Expand Down Expand Up @@ -814,6 +820,22 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
case HostRoot: {
return;
}
case Profiler: {
if (enableProfileModeMetrics) {
const onRender = finishedWork.memoizedProps.onRender;
onRender(
finishedWork.memoizedProps.id,
current === null ? 'mount' : 'update',
finishedWork.stateNode.duration,
finishedWork.treeBaseTime,
);

// Reset actualTime after successful commit.
// By default, we append to this time to account for errors and pauses.
finishedWork.stateNode.duration = 0;
}
return;
}
default: {
invariant(
false,
Expand Down
Loading