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
Commits
File filter...
Filter file types
Jump to file or symbol
Failed to load files and symbols.
+1,451 −53
Diff settings

Always

Just for now

@@ -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';
@@ -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,
@@ -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';
@@ -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:
@@ -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;

@@ -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;
}
@@ -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);
});
});
@@ -15,6 +15,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {UpdateQueue} from './ReactUpdateQueue';

import invariant from 'fbjs/lib/invariant';
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
import {NoEffect} from 'shared/ReactTypeOfSideEffect';
import {
IndeterminateComponent,
@@ -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,
@@ -150,6 +153,10 @@ export type Fiber = {|
// memory if we need to.
alternate: Fiber | null,

// Profiling metrics
selfBaseTime?: number,
treeBaseTime?: number,

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

this.alternate = null;

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

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

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

return workInProgress;
}

@@ -343,6 +360,8 @@ export function createFiberFromElement(
fiberTag = Mode;
mode |= StrictMode;
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_CALL_TYPE:
fiberTag = CallComponent;
break;
@@ -440,6 +459,35 @@ export function createFiberFromFragment(
return fiber;
}

export function createFiberFromProfiler(
pendingProps: any,
mode: TypeOfMode,
expirationTime: ExpirationTime,
key: null | string,
): Fiber {
if (__DEV__) {
if (
typeof pendingProps.id !== 'string' ||
typeof pendingProps.onRender !== 'function'
) {
invariant(
false,
'Profiler 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,
@@ -509,6 +557,10 @@ export function assignFiberPropertiesInDEV(
target.lastEffect = source.lastEffect;
target.expirationTime = source.expirationTime;
target.alternate = source.alternate;
if (enableProfilerTimer) {
target.selfBaseTime = source.selfBaseTime;
target.treeBaseTime = source.treeBaseTime;
}
target._debugID = source._debugID;
target._debugSource = source._debugSource;
target._debugOwner = source._debugOwner;
@@ -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 {
@@ -34,6 +35,7 @@ import {
Mode,
ContextProvider,
ContextConsumer,
Profiler,
} from 'shared/ReactTypeOfWork';
import {
NoEffect,
@@ -42,12 +44,14 @@ import {
ContentReset,
Ref,
DidCapture,
Update,
} from 'shared/ReactTypeOfSideEffect';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import {
enableGetDerivedStateFromCatch,
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import invariant from 'fbjs/lib/invariant';
import getComponentName from 'shared/getComponentName';
@@ -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,
@@ -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 (enableProfilerTimer) {
// 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 (
@@ -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 (enableProfilerTimer) {
stopBaseRenderTimerIfRunning();
}
} else {
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('render');
@@ -1054,6 +1087,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
): Fiber | null {
cancelWorkTimer(workInProgress);

if (enableProfilerTimer) {
// 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
@@ -1075,6 +1113,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
function bailoutOnLowPriority(current, workInProgress) {
cancelWorkTimer(workInProgress);

if (enableProfilerTimer) {
// 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) {
@@ -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 (enableProfilerTimer) {
markActualRenderTimeStarted(workInProgress);
}
break;
}
// TODO: What if this is currently in progress?
// How can that happen? How is this not being cloned?
@@ -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,
@@ -17,6 +17,7 @@ import {
enableMutatingReconciler,
enableNoopReconciler,
enablePersistentReconciler,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import {
ClassComponent,
@@ -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';
@@ -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,
@@ -814,6 +820,22 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
case HostRoot: {
return;
}
case Profiler: {
if (enableProfilerTimer) {
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,
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.