From a3b92fcfb2e7213d8d1192a3630aa6809279db82 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 30 Apr 2018 10:18:55 -0700 Subject: [PATCH 01/72] Added ProfileMode and enableProfileModeMetrics feature flag Also added basic tests to make sure mode renders children. --- .../src/server/ReactPartialRenderer.js | 2 + packages/react-is/src/ReactIs.js | 6 ++ packages/react-reconciler/src/ReactFiber.js | 7 +- .../react-reconciler/src/ReactTypeOfMode.js | 1 + .../ReactIncrementalPerf-test.internal.js | 18 +++-- packages/react/src/React.js | 4 +- .../ReactProfileMode-test.internal.js | 77 +++++++++++++++++++ .../ReactProfileMode-test.internal.js.snap | 65 ++++++++++++++++ packages/shared/ReactFeatureFlags.js | 3 + packages/shared/ReactSymbols.js | 3 + .../ReactFeatureFlags.native-fabric-fb.js | 1 + .../ReactFeatureFlags.native-fabric-oss.js | 2 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.persistent.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + packages/shared/isValidElementType.js | 8 +- 18 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 packages/react/src/__tests__/ReactProfileMode-test.internal.js create mode 100644 packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index ec26eba2c1d..f46bdea23d2 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -34,6 +34,7 @@ import { REACT_CALL_TYPE, REACT_RETURN_TYPE, REACT_PORTAL_TYPE, + REACT_PROFILE_MODE_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_PROFILE_MODE_TYPE: case REACT_FRAGMENT_TYPE: { const nextChildren = toArray( ((nextChild: any): ReactElement).props.children, diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js index 4418759a9b2..e1f3133bfea 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -16,6 +16,7 @@ import { REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, REACT_PORTAL_TYPE, + REACT_PROFILE_MODE_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_PROFILE_MODE_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 ProfileMode = REACT_PROFILE_MODE_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 isProfileMode(object: any) { + return typeOf(object) === REACT_PROFILE_MODE_TYPE; +} export function isPortal(object: any) { return typeOf(object) === REACT_PORTAL_TYPE; } diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 393cecb5276..e2a7d14ffff 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -34,13 +34,14 @@ import { 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_PROFILE_MODE_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, REACT_ASYNC_MODE_TYPE, @@ -343,6 +344,10 @@ export function createFiberFromElement( fiberTag = Mode; mode |= StrictMode; break; + case REACT_PROFILE_MODE_TYPE: + fiberTag = Mode; + mode |= ProfileMode; + break; case REACT_CALL_TYPE: fiberTag = CallComponent; break; diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js index e93b4d984de..85fe55bf4c0 100644 --- a/packages/react-reconciler/src/ReactTypeOfMode.js +++ b/packages/react-reconciler/src/ReactTypeOfMode.js @@ -12,3 +12,4 @@ export type TypeOfMode = number; export const NoContext = 0b00; export const AsyncMode = 0b01; export const StrictMode = 0b10; +export const ProfileMode = 0b11; diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index d5dc2b6f6ab..e4f19b0f984 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -187,15 +187,17 @@ describe('ReactDebugFiberPerf', () => { expect(getFlameChart()).toMatchSnapshot(); }); - it('does not include StrictMode or AsyncMode components in measurements', () => { + it('does not include AsyncMode, StrictMode or ProfileMode components in measurements', () => { ReactNoop.render( - - - - - - - , + + + + + + + + + , ); addComment('Mount'); ReactNoop.flush(); diff --git a/packages/react/src/React.js b/packages/react/src/React.js index d3a52cf61c4..b02baef181b 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -8,9 +8,10 @@ import assign from 'object-assign'; import ReactVersion from 'shared/ReactVersion'; import { + REACT_ASYNC_MODE_TYPE, REACT_FRAGMENT_TYPE, + REACT_PROFILE_MODE_TYPE, REACT_STRICT_MODE_TYPE, - REACT_ASYNC_MODE_TYPE, } from 'shared/ReactSymbols'; import {Component, PureComponent} from './ReactBaseClasses'; @@ -49,6 +50,7 @@ const React = { forwardRef, Fragment: REACT_FRAGMENT_TYPE, + ProfileMode: REACT_PROFILE_MODE_TYPE, StrictMode: REACT_STRICT_MODE_TYPE, unstable_AsyncMode: REACT_ASYNC_MODE_TYPE, diff --git a/packages/react/src/__tests__/ReactProfileMode-test.internal.js b/packages/react/src/__tests__/ReactProfileMode-test.internal.js new file mode 100644 index 00000000000..43140cfbdab --- /dev/null +++ b/packages/react/src/__tests__/ReactProfileMode-test.internal.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactFeatureFlags; +let ReactTestRenderer; + +describe('ReactProfileMode', () => { + [true, false].forEach(enabled => { + describe(`enableProfileModeMetrics feature flag ${ + enabled ? 'enabled' : 'disabled' + }`, () => { + beforeEach(() => { + jest.resetModules(); + + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableProfileModeMetrics = enabled; + React = require('react'); + ReactTestRenderer = require('react-test-renderer'); + }); + + it('should support an empty mode', () => { + expect( + ReactTestRenderer.create().toJSON(), + ).toMatchSnapshot(); + expect( + ReactTestRenderer.create( +
+ +
, + ).toJSON(), + ).toMatchSnapshot(); + }); + + it('should render children', () => { + const ProfiledComponent = ({name}) => {name}; + const renderer = ReactTestRenderer.create( +
+ Hi + + there + + +
, + ); + expect(renderer.toJSON()).toMatchSnapshot(); + }); + + it('should support nested ProfileModes', () => { + const ProfiledComponent = ({name}) =>
Hi, {name}
; + class ExtraProfiledComponent extends React.Component { + render() { + return Hi, {this.props.name}; + } + } + const renderer = ReactTestRenderer.create( + + + + + Now with extra profile strength! + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap new file mode 100644 index 00000000000..48e067d66e9 --- /dev/null +++ b/packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should render children 1`] = ` +
+ Hi + + there + + + ProfileMode + +
+`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should support an empty mode 1`] = `null`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should support an empty mode 2`] = `
`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should support nested ProfileModes 1`] = ` +Array [ +
+ Hi, + Brian +
, + + Hi, + Brian + , + + Now with extra profile strength! + , +] +`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should render children 1`] = ` +
+ Hi + + there + + + ProfileMode + +
+`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should support an empty mode 1`] = `null`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should support an empty mode 2`] = `
`; + +exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should support nested ProfileModes 1`] = ` +Array [ +
+ Hi, + Brian +
, + + Hi, + Brian + , + + Now with extra profile strength! + , +] +`; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 955668d13e4..ff6e980dc8f 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -37,6 +37,9 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; // Warn about deprecated, async-unsafe lifecycles; relates to RFC #6: export const warnAboutDeprecatedLifecycles = false; +// Gather advanced timing metrics for ProfileMode subtrees. +export const enableProfileModeMetrics = false; + // Only used in www builds. export function addUserTimingListener() { invariant(false, 'Not implemented.'); diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 12e0fdddad9..d9d072c72c8 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -27,6 +27,9 @@ export const REACT_FRAGMENT_TYPE = hasSymbol export const REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc; +export const REACT_PROFILE_MODE_TYPE = hasSymbol + ? Symbol.for('react.profile_mode') + : 0xeacc; export const REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js index e2fd4379995..d8cb0786b78 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js @@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__; export const enableGetDerivedStateFromCatch = false; export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; +export const enableProfileModeMetrics = __DEV__; // React Fabric uses persistent reconciler. export const enableMutatingReconciler = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js index c6a4862d85f..907b8169366 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js @@ -1,4 +1,5 @@ /** +export const enableProfileModeMetrics = false; * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the @@ -18,6 +19,7 @@ export const enableUserTimingAPI = __DEV__; export const enableGetDerivedStateFromCatch = false; export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; +export const enableProfileModeMetrics = false; // React Fabric uses persistent reconciler. export const enableMutatingReconciler = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 3026a69fc6c..ad0e80fe81e 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -19,6 +19,7 @@ export const { debugRenderPhaseSideEffectsForStrictMode, warnAboutDeprecatedLifecycles, replayFailedUnitOfWorkWithInvokeGuardedCallback, + enableProfileModeMetrics, } = require('ReactFeatureFlags'); // The rest of the flags are static for better dead code elimination. diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index a238ca16885..d13f28cb1fa 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -21,6 +21,7 @@ export const enablePersistentReconciler = false; export const enableUserTimingAPI = __DEV__; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; export const warnAboutDeprecatedLifecycles = false; +export const enableProfileModeMetrics = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index 57d7c6bf531..2aadef28616 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -18,6 +18,7 @@ export const enableUserTimingAPI = __DEV__; export const enableGetDerivedStateFromCatch = false; export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; +export const enableProfileModeMetrics = false; // react-reconciler/persistent entry point // uses a persistent reconciler. diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 000f950a4f7..290af34d3bb 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -21,6 +21,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableMutatingReconciler = true; export const enableNoopReconciler = false; export const enablePersistentReconciler = false; +export const enableProfileModeMetrics = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index df3560c6fba..ab097040be3 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -17,6 +17,7 @@ export const { enableGetDerivedStateFromCatch, replayFailedUnitOfWorkWithInvokeGuardedCallback, warnAboutDeprecatedLifecycles, + enableProfileModeMetrics, } = require('ReactFeatureFlags'); // The rest of the flags are static for better dead code elimination. diff --git a/packages/shared/isValidElementType.js b/packages/shared/isValidElementType.js index 7a333c8c11a..a04dbc07103 100644 --- a/packages/shared/isValidElementType.js +++ b/packages/shared/isValidElementType.js @@ -8,12 +8,13 @@ */ import { - REACT_FRAGMENT_TYPE, REACT_ASYNC_MODE_TYPE, - REACT_STRICT_MODE_TYPE, - REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, REACT_FORWARD_REF_TYPE, + REACT_FRAGMENT_TYPE, + REACT_PROFILE_MODE_TYPE, + REACT_PROVIDER_TYPE, + REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; export default function isValidElementType(type: mixed) { @@ -23,6 +24,7 @@ export default function isValidElementType(type: mixed) { // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. type === REACT_FRAGMENT_TYPE || type === REACT_ASYNC_MODE_TYPE || + type === REACT_PROFILE_MODE_TYPE || type === REACT_STRICT_MODE_TYPE || (typeof type === 'object' && type !== null && From cb2e80258883f9120c2ca0ba748ead781f36b55d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 1 May 2018 10:44:03 -0700 Subject: [PATCH 02/72] Added more tests. Fixed premature bailout cases. --- packages/react-reconciler/src/ReactFiber.js | 22 +++ .../src/ReactFiberBeginWork.js | 69 ++++++- .../src/ReactFiberCommitWork.js | 42 +++- .../src/ReactFiberScheduler.js | 6 + .../ReactProfileMode-test.internal.js | 184 +++++++++++++++++- packages/shared/ReactTypeOfSideEffect.js | 29 +-- packages/shared/getComponentName.js | 15 +- 7 files changed, 342 insertions(+), 25 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index e2a7d14ffff..f0194f3c749 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -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, @@ -151,6 +152,10 @@ export type Fiber = {| // memory if we need to. alternate: Fiber | null, + // Profiling metrics + selfBaseTime: number | null, + descendantsBaseTime: number | null, + // Conceptual aliases // workInProgress : Fiber -> alternate The alternate used for reuse happens // to be the same as work in progress. @@ -190,6 +195,7 @@ function FiberNode( this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; + this.memoizedState = null; this.mode = mode; @@ -205,6 +211,11 @@ function FiberNode( this.alternate = null; + if (enableProfileModeMetrics) { + this.selfBaseTime = null; + this.descendantsBaseTime = null; + } + if (__DEV__) { this._debugID = debugCounter++; this._debugSource = null; @@ -345,6 +356,17 @@ export function createFiberFromElement( mode |= StrictMode; break; case REACT_PROFILE_MODE_TYPE: + if (__DEV__) { + if ( + typeof element.props.label !== 'string' || + typeof element.props.callback !== 'function' + ) { + invariant( + false, + 'ProfileMode must specify a label string and callback function', + ); + } + } fiberTag = Mode; mode |= ProfileMode; break; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 5aa7ffc446c..b2718cb75cb 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -42,12 +42,15 @@ import { ContentReset, Ref, DidCapture, + CommitProfile, } from 'shared/ReactTypeOfSideEffect'; +import {REACT_PROFILE_MODE_TYPE} from 'shared/ReactSymbols'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; import { enableGetDerivedStateFromCatch, debugRenderPhaseSideEffects, debugRenderPhaseSideEffectsForStrictMode, + enableProfileModeMetrics, } from 'shared/ReactFeatureFlags'; import invariant from 'fbjs/lib/invariant'; import getComponentName from 'shared/getComponentName'; @@ -65,7 +68,7 @@ import { } from './ReactChildFiber'; import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; -import {AsyncMode, StrictMode} from './ReactTypeOfMode'; +import {AsyncMode, ProfileMode, StrictMode} from './ReactTypeOfMode'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -200,10 +203,23 @@ export default function( } function updateMode(current, workInProgress) { + if (enableProfileModeMetrics) { + if (workInProgress.type === ProfileMode) { + // TODO (bvaughn) (actual) Start/resume timer for "actual" time + } + } const nextChildren = workInProgress.pendingProps.children; if (hasLegacyContextChanged()) { // Normally we can bail out on props equality but if context has changed // we don't do the bailout and we have to reuse existing props instead. + } else if (workInProgress.type === REACT_PROFILE_MODE_TYPE) { + // Don't bail out early for ProfileMode, + // Because we always want to re-measure the subtree. + if (enableProfileModeMetrics) { + // TODO (bvaughn) (render) Stop timer for "actual" time + // TODO (bvaughn) (actual) Calculate sum of children's "base" time + workInProgress.effectTag |= CommitProfile; + } } else if ( nextChildren === null || workInProgress.memoizedProps === nextChildren @@ -230,6 +246,11 @@ export default function( const fn = workInProgress.type; const nextProps = workInProgress.pendingProps; + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) Start tracking "base" time + } + } if (hasLegacyContextChanged()) { // Normally we can bail out on props equality but if context has changed // we don't do the bailout and we have to reuse existing props instead. @@ -258,6 +279,13 @@ export default function( workInProgress.effectTag |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren); memoizeProps(workInProgress, nextProps); + + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) Stop and record "base" time + } + } + return workInProgress.child; } @@ -266,6 +294,12 @@ export default function( workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) Start tracking "base" time + } + } + // Push context providers early to prevent context stack mismatches. // During mounting we don't know the child context yet as the instance doesn't exist. // We will invalidate the child context in finishClassComponent() right after rendering. @@ -296,6 +330,13 @@ export default function( renderExpirationTime, ); } + + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) Pass base start-time to finishClassComponent() + } + } + return finishClassComponent( current, workInProgress, @@ -361,6 +402,12 @@ export default function( } } + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) (base) Update "base" time + } + } + // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; if (didCaptureError) { @@ -568,6 +615,12 @@ export default function( let value; + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) (base) Start tracking "base" time + } + } + if (__DEV__) { if (fn.prototype && typeof fn.prototype.render === 'function') { const componentName = getComponentName(workInProgress) || 'Unknown'; @@ -620,6 +673,13 @@ export default function( const hasContext = pushLegacyContextProvider(workInProgress); adoptClassInstance(workInProgress, value); mountClassInstance(workInProgress, renderExpirationTime); + + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) Pass base start-time to finishClassComponent() + } + } + return finishClassComponent( current, workInProgress, @@ -683,6 +743,13 @@ export default function( } reconcileChildren(current, workInProgress, value); memoizeProps(workInProgress, props); + + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) (base) Stop and update "base" time? + } + } + return workInProgress.child; } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index a3754902975..d45e30384aa 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -17,6 +17,7 @@ import { enableMutatingReconciler, enableNoopReconciler, enablePersistentReconciler, + enableProfileModeMetrics, } from 'shared/ReactFeatureFlags'; import { ClassComponent, @@ -25,14 +26,16 @@ import { HostText, HostPortal, CallComponent, + Mode, } from 'shared/ReactTypeOfWork'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import { - Placement, - Update, ContentReset, + Placement, Snapshot, + Update, } from 'shared/ReactTypeOfSideEffect'; +import {REACT_PROFILE_MODE_TYPE} from 'shared/ReactSymbols'; import {commitUpdateQueue} from './ReactUpdateQueue'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -511,6 +514,7 @@ export default function( commitBeforeMutationLifeCycles, commitAttachRef, commitDetachRef, + commitProfileWork, }; } else if (persistence) { invariant(false, 'Persistent reconciler is disabled.'); @@ -764,6 +768,39 @@ export default function( detachFiber(current); } + function commitProfileWork(finishedWork: Fiber): void { + switch (finishedWork.tag) { + case Mode: { + // TODO (bvaughn) This feels like the wrong type check approach + if (finishedWork.type === REACT_PROFILE_MODE_TYPE) { + if (enableProfileModeMetrics) { + finishedWork.pendingProps.callback.call( + null, + finishedWork.pendingProps.label, + 'mount', + 1, // TODO (bvaughn) Use actual times + 1, // TODO (bvaughn) Use actual times + ); + } + } else { + invariant( + false, + 'This unit of work tag should not have side-effects. This error is ' + + 'likely caused by a bug in React. Please file an issue.', + ); + } + break; + } + default: { + invariant( + false, + 'This unit of work tag should not have side-effects. This error is ' + + 'likely caused by a bug in React. Please file an issue.', + ); + } + } + } + function commitWork(current: Fiber | null, finishedWork: Fiber): void { switch (finishedWork.tag) { case ClassComponent: { @@ -838,6 +875,7 @@ export default function( commitLifeCycles, commitAttachRef, commitDetachRef, + commitProfileWork, }; } else { invariant(false, 'Mutating reconciler is disabled.'); diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 1a9846b0bdb..0a6079546b3 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -31,6 +31,7 @@ import { Ref, Incomplete, HostEffectMask, + CommitProfile, } from 'shared/ReactTypeOfSideEffect'; import { HostRoot, @@ -211,6 +212,7 @@ export default function( commitLifeCycles, commitAttachRef, commitDetachRef, + commitProfileWork, } = ReactFiberCommitWork( config, onCommitPhaseError, @@ -357,6 +359,10 @@ export default function( } } + if (effectTag & CommitProfile) { + commitProfileWork(nextEffect); + } + // The following switch statement is only concerned about placement, // updates, and deletions. To avoid needing to add a case for every // possible bitmap value, we remove the secondary effects from the diff --git a/packages/react/src/__tests__/ReactProfileMode-test.internal.js b/packages/react/src/__tests__/ReactProfileMode-test.internal.js index 43140cfbdab..072040c3265 100644 --- a/packages/react/src/__tests__/ReactProfileMode-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileMode-test.internal.js @@ -27,14 +27,28 @@ describe('ReactProfileMode', () => { ReactTestRenderer = require('react-test-renderer'); }); + // This will throw in production too, + // But the test is only interested in verifying the DEV error message. + if (__DEV__) { + it('should warn about invalid mode', () => { + expect(() => { + ReactTestRenderer.create(); + }).toThrow( + 'ProfileMode must specify a label string and callback function', + ); + }); + } + it('should support an empty mode', () => { expect( - ReactTestRenderer.create().toJSON(), + ReactTestRenderer.create( + {}} />, + ).toJSON(), ).toMatchSnapshot(); expect( ReactTestRenderer.create(
- + {}} />
, ).toJSON(), ).toMatchSnapshot(); @@ -45,7 +59,7 @@ describe('ReactProfileMode', () => { const renderer = ReactTestRenderer.create(
Hi - + {}}> there @@ -62,9 +76,9 @@ describe('ReactProfileMode', () => { } } const renderer = ReactTestRenderer.create( - + {}}> - + {}}> Now with extra profile strength! @@ -74,4 +88,164 @@ describe('ReactProfileMode', () => { }); }); }); + + describe('render timings', () => { + beforeEach(() => { + jest.resetModules(); + + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableProfileModeMetrics = true; + React = require('react'); + ReactTestRenderer = require('react-test-renderer'); + }); + + it('logs render times for mount and update', () => { + const callback = jest.fn(); + + class ClassComponent extends React.Component { + render() { + return null; + } + } + + const FunctionalComponent = props => props.children; + const Yield = ({value}) => { + renderer.unstable_yield(value); + return null; + }; + + const renderer = ReactTestRenderer.create( + + + +
+ + + + + + + +
+
+ +
, + { + unstable_isAsync: true, + }, + ); + + // Times are logged until a render is committed. + renderer.unstable_flushThrough(['first']); + expect(callback).toHaveBeenCalledTimes(0); + expect(renderer.unstable_flushAll()).toEqual(['last']); + expect(callback).toHaveBeenCalledTimes(3); + + // Callbacks bubble (reverse order). + const [innerCall, middleCall, outerCall] = callback.mock.calls; + + expect(innerCall).toHaveLength(4); + expect(innerCall[0]).toBe('inner'); + expect(innerCall[1]).toBe('mount'); + expect(innerCall[2]).toBeGreaterThan(0); // "actual" time + expect(innerCall[3]).toBeGreaterThan(0); // "base" time + + expect(middleCall).toHaveLength(4); + expect(middleCall[0]).toBe('middle'); + expect(middleCall[1]).toBe('mount'); + expect(middleCall[2]).toBeGreaterThan(0); // "actual" time + expect(middleCall[3]).toBeGreaterThan(0); // "base" time + + expect(outerCall).toHaveLength(4); + expect(outerCall[0]).toBe('outer'); + expect(outerCall[1]).toBe('mount'); + expect(outerCall[2]).toBeGreaterThan(0); // "actual" time + expect(outerCall[3]).toBeGreaterThan(0); // "base" time + + callback.mockReset(); + + renderer.update( + + + +
+ + + + + + + +
+
+ +
, + ); + + // Times are logged until a render is committed. + renderer.unstable_flushThrough(['first']); + expect(callback).toHaveBeenCalledTimes(0); + expect(renderer.unstable_flushAll()).toEqual(['last']); + expect(callback).toHaveBeenCalledTimes(3); + }); + + it('does not log times if sCU prevents a re-render', () => { + const callback = jest.fn(); + + let instance; + class Updater extends React.Component { + state = {}; + render() { + instance = this; + return this.props.children; + } + } + + class Pure extends React.PureComponent { + render() { + return this.props.children; + } + } + + const renderer = ReactTestRenderer.create( + +
+ + + + + +
+ + + + + +
+
, + ); + + // All profile callbacks are called for initial render + expect(callback).toHaveBeenCalledTimes(4); + + callback.mockReset(); + + renderer.unstable_flushSync(() => { + instance.setState({ + count: 1, + }); + }); + + // Only call profile updates for paths that have re-rendered + // Since "blocked" is beneath a pure compoent, it isn't called + expect(callback).toHaveBeenCalledTimes(3); + expect(callback.mock.calls[0][0]).toBe('inner'); + expect(callback.mock.calls[1][0]).toBe('middle'); + expect(callback.mock.calls[2][0]).toBe('outer'); + }); + + // TODO (bvaughn) Test updates only callback for committed modes + + // TODO (bvaughn) Test nested updates work (outer update is greater value than inner) + }); }); diff --git a/packages/shared/ReactTypeOfSideEffect.js b/packages/shared/ReactTypeOfSideEffect.js index 27d6aa6090e..048fa3d9204 100644 --- a/packages/shared/ReactTypeOfSideEffect.js +++ b/packages/shared/ReactTypeOfSideEffect.js @@ -10,22 +10,23 @@ export type TypeOfSideEffect = number; // Don't change these two values. They're used by React Dev Tools. -export const NoEffect = /* */ 0b00000000000; -export const PerformedWork = /* */ 0b00000000001; +export const NoEffect = /* */ 0b000000000000; +export const PerformedWork = /* */ 0b000000000001; // You can change the rest (and add more). -export const Placement = /* */ 0b00000000010; -export const Update = /* */ 0b00000000100; -export const PlacementAndUpdate = /* */ 0b00000000110; -export const Deletion = /* */ 0b00000001000; -export const ContentReset = /* */ 0b00000010000; -export const Callback = /* */ 0b00000100000; -export const DidCapture = /* */ 0b00001000000; -export const Ref = /* */ 0b00010000000; -export const Snapshot = /* */ 0b00100000000; +export const Placement = /* */ 0b000000000010; +export const Update = /* */ 0b000000000100; +export const PlacementAndUpdate = /* */ 0b000000000110; +export const Deletion = /* */ 0b000000001000; +export const ContentReset = /* */ 0b000000010000; +export const Callback = /* */ 0b000000100000; +export const DidCapture = /* */ 0b000001000000; +export const Ref = /* */ 0b000010000000; +export const Snapshot = /* */ 0b000100000000; // Union of all host effects -export const HostEffectMask = /* */ 0b00111111111; +export const HostEffectMask = /* */ 0b000111111111; -export const Incomplete = /* */ 0b01000000000; -export const ShouldCapture = /* */ 0b10000000000; +export const Incomplete = /* */ 0b001000000000; +export const ShouldCapture = /* */ 0b010000000000; +export const CommitProfile = /* */ 0b100000000000; diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index 23e10d1a73a..17fb8082c89 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -10,11 +10,14 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber'; import { + REACT_ASYNC_MODE_TYPE, REACT_CALL_TYPE, + REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, REACT_RETURN_TYPE, REACT_PORTAL_TYPE, - REACT_FORWARD_REF_TYPE, + REACT_PROFILE_MODE_TYPE, + REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; function getComponentName(fiber: Fiber): string | null { @@ -26,14 +29,20 @@ function getComponentName(fiber: Fiber): string | null { return type; } switch (type) { + case REACT_ASYNC_MODE_TYPE: + return 'AsyncMode'; + case REACT_CALL_TYPE: + return 'ReactCall'; case REACT_FRAGMENT_TYPE: return 'ReactFragment'; case REACT_PORTAL_TYPE: return 'ReactPortal'; - case REACT_CALL_TYPE: - return 'ReactCall'; + case REACT_PROFILE_MODE_TYPE: + return 'ProfileMode'; case REACT_RETURN_TYPE: return 'ReactReturn'; + case REACT_STRICT_MODE_TYPE: + return 'StrictMode'; } if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { From 5af43eb566feaa419358813591c7d31473f9a992 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 1 May 2018 10:52:40 -0700 Subject: [PATCH 03/72] Convert ProfileMode to new, non-Mode based ProfileRoot component type --- packages/react-reconciler/src/ReactFiber.js | 3 +- .../src/ReactFiberBeginWork.js | 34 +++++++++++-------- .../src/ReactFiberCommitWork.js | 28 +++++---------- .../src/ReactFiberCompleteWork.js | 3 ++ packages/shared/ReactTypeOfWork.js | 4 ++- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index f0194f3c749..dde6b55d780 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -31,6 +31,7 @@ import { Mode, ContextProvider, ContextConsumer, + ProfileRoot, } from 'shared/ReactTypeOfWork'; import getComponentName from 'shared/getComponentName'; @@ -367,7 +368,7 @@ export function createFiberFromElement( ); } } - fiberTag = Mode; + fiberTag = ProfileRoot; mode |= ProfileMode; break; case REACT_CALL_TYPE: diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index b2718cb75cb..934d0dd7253 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -34,6 +34,7 @@ import { Mode, ContextProvider, ContextConsumer, + ProfileRoot, } from 'shared/ReactTypeOfWork'; import { NoEffect, @@ -44,7 +45,6 @@ import { DidCapture, CommitProfile, } from 'shared/ReactTypeOfSideEffect'; -import {REACT_PROFILE_MODE_TYPE} from 'shared/ReactSymbols'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; import { enableGetDerivedStateFromCatch, @@ -203,23 +203,10 @@ export default function( } function updateMode(current, workInProgress) { - if (enableProfileModeMetrics) { - if (workInProgress.type === ProfileMode) { - // TODO (bvaughn) (actual) Start/resume timer for "actual" time - } - } const nextChildren = workInProgress.pendingProps.children; if (hasLegacyContextChanged()) { // Normally we can bail out on props equality but if context has changed // we don't do the bailout and we have to reuse existing props instead. - } else if (workInProgress.type === REACT_PROFILE_MODE_TYPE) { - // Don't bail out early for ProfileMode, - // Because we always want to re-measure the subtree. - if (enableProfileModeMetrics) { - // TODO (bvaughn) (render) Stop timer for "actual" time - // TODO (bvaughn) (actual) Calculate sum of children's "base" time - workInProgress.effectTag |= CommitProfile; - } } else if ( nextChildren === null || workInProgress.memoizedProps === nextChildren @@ -231,6 +218,23 @@ export default function( return workInProgress.child; } + function updateProfileRoot(current, workInProgress) { + if (enableProfileModeMetrics) { + workInProgress.effectTag |= CommitProfile; + + // TODO (bvaughn) (render) Stop/resume timer for "actual" time + // TODO (bvaughn) (actual) Calculate sum of children's "base" time + } + + // Don't bail out early for ProfileMode, + // Because we always want to re-measure the subtree + + const nextChildren = workInProgress.pendingProps.children; + reconcileChildren(current, workInProgress, nextChildren); + memoizeProps(workInProgress, nextChildren); + return workInProgress.child; + } + function markRef(current: Fiber | null, workInProgress: Fiber) { const ref = workInProgress.ref; if ( @@ -1240,6 +1244,8 @@ export default function( return updateFragment(current, workInProgress); case Mode: return updateMode(current, workInProgress); + case ProfileRoot: + return updateProfileRoot(current, workInProgress); case ContextProvider: return updateContextProvider( current, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index d45e30384aa..68d25586558 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -26,7 +26,7 @@ import { HostText, HostPortal, CallComponent, - Mode, + ProfileRoot, } from 'shared/ReactTypeOfWork'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import { @@ -35,7 +35,6 @@ import { Snapshot, Update, } from 'shared/ReactTypeOfSideEffect'; -import {REACT_PROFILE_MODE_TYPE} from 'shared/ReactSymbols'; import {commitUpdateQueue} from './ReactUpdateQueue'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -770,23 +769,14 @@ export default function( function commitProfileWork(finishedWork: Fiber): void { switch (finishedWork.tag) { - case Mode: { - // TODO (bvaughn) This feels like the wrong type check approach - if (finishedWork.type === REACT_PROFILE_MODE_TYPE) { - if (enableProfileModeMetrics) { - finishedWork.pendingProps.callback.call( - null, - finishedWork.pendingProps.label, - 'mount', - 1, // TODO (bvaughn) Use actual times - 1, // TODO (bvaughn) Use actual times - ); - } - } else { - invariant( - false, - 'This unit of work tag should not have side-effects. This error is ' + - 'likely caused by a bug in React. Please file an issue.', + case ProfileRoot: { + if (enableProfileModeMetrics) { + finishedWork.pendingProps.callback.call( + null, + finishedWork.pendingProps.label, + 'mount', + 1, // TODO (bvaughn) Use actual times + 1, // TODO (bvaughn) Use actual times ); } break; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index aec3bc17c6f..4ab6f65eeff 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -37,6 +37,7 @@ import { ForwardRef, Fragment, Mode, + ProfileRoot, } from 'shared/ReactTypeOfWork'; import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect'; import invariant from 'fbjs/lib/invariant'; @@ -591,6 +592,8 @@ export default function( return null; case Mode: return null; + case ProfileRoot: + return null; case HostPortal: popHostContainer(workInProgress); updateHostContainer(workInProgress); diff --git a/packages/shared/ReactTypeOfWork.js b/packages/shared/ReactTypeOfWork.js index 573b75aabc0..b36394c419e 100644 --- a/packages/shared/ReactTypeOfWork.js +++ b/packages/shared/ReactTypeOfWork.js @@ -22,7 +22,8 @@ export type TypeOfWork = | 11 | 12 | 13 - | 14; + | 14 + | 15; export const IndeterminateComponent = 0; // Before we know whether it is functional or class export const FunctionalComponent = 1; @@ -39,3 +40,4 @@ export const Mode = 11; export const ContextConsumer = 12; export const ContextProvider = 13; export const ForwardRef = 14; +export const ProfileRoot = 15; From 808c5e732d2d1ed9507801a401a2f5ac6408bc1e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 1 May 2018 11:02:03 -0700 Subject: [PATCH 04/72] Renamed "profile mode" in a few places to "profile root" --- .../src/ReactFiberCommitWork.js | 4 +- packages/react/src/React.js | 2 +- ...l.js => ReactProfileRoot-test.internal.js} | 62 +++++++++--------- .../ReactProfileMode-test.internal.js.snap | 65 ------------------- .../ReactProfileRoot-test.internal.js.snap | 65 +++++++++++++++++++ 5 files changed, 100 insertions(+), 98 deletions(-) rename packages/react/src/__tests__/{ReactProfileMode-test.internal.js => ReactProfileRoot-test.internal.js} (77%) delete mode 100644 packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap create mode 100644 packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 68d25586558..4ac00bf745a 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -775,8 +775,8 @@ export default function( null, finishedWork.pendingProps.label, 'mount', - 1, // TODO (bvaughn) Use actual times - 1, // TODO (bvaughn) Use actual times + 1, // TODO (bvaughn) Use real "actual" times + 1, // TODO (bvaughn) Use real "base" times ); } break; diff --git a/packages/react/src/React.js b/packages/react/src/React.js index b02baef181b..ddc373ea62d 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -50,9 +50,9 @@ const React = { forwardRef, Fragment: REACT_FRAGMENT_TYPE, - ProfileMode: REACT_PROFILE_MODE_TYPE, StrictMode: REACT_STRICT_MODE_TYPE, unstable_AsyncMode: REACT_ASYNC_MODE_TYPE, + unstable_ProfileRoot: REACT_PROFILE_MODE_TYPE, createElement: __DEV__ ? createElementWithValidation : createElement, cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, diff --git a/packages/react/src/__tests__/ReactProfileMode-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js similarity index 77% rename from packages/react/src/__tests__/ReactProfileMode-test.internal.js rename to packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 072040c3265..04bc32d0012 100644 --- a/packages/react/src/__tests__/ReactProfileMode-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -13,7 +13,7 @@ let React; let ReactFeatureFlags; let ReactTestRenderer; -describe('ReactProfileMode', () => { +describe('ProfileRoot', () => { [true, false].forEach(enabled => { describe(`enableProfileModeMetrics feature flag ${ enabled ? 'enabled' : 'disabled' @@ -32,7 +32,7 @@ describe('ReactProfileMode', () => { if (__DEV__) { it('should warn about invalid mode', () => { expect(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(); }).toThrow( 'ProfileMode must specify a label string and callback function', ); @@ -42,13 +42,13 @@ describe('ReactProfileMode', () => { it('should support an empty mode', () => { expect( ReactTestRenderer.create( - {}} />, + {}} />, ).toJSON(), ).toMatchSnapshot(); expect( ReactTestRenderer.create(
- {}} /> + {}} />
, ).toJSON(), ).toMatchSnapshot(); @@ -59,10 +59,10 @@ describe('ReactProfileMode', () => { const renderer = ReactTestRenderer.create(
Hi - {}}> + {}}> there - +
, ); expect(renderer.toJSON()).toMatchSnapshot(); @@ -76,13 +76,13 @@ describe('ReactProfileMode', () => { } } const renderer = ReactTestRenderer.create( - {}}> + {}}> - {}}> + {}}> Now with extra profile strength! - - , +
+
, ); expect(renderer.toJSON()).toMatchSnapshot(); }); @@ -117,17 +117,17 @@ describe('ReactProfileMode', () => { const renderer = ReactTestRenderer.create( - +
- + - + - + - +
-
+
, { @@ -167,17 +167,17 @@ describe('ReactProfileMode', () => { renderer.update( - +
- + - + - + - +
-
+
, ); @@ -208,21 +208,23 @@ describe('ReactProfileMode', () => { } const renderer = ReactTestRenderer.create( - +
- + - + - +
- + - + - +
-
, +
, ); // All profile callbacks are called for initial render diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap deleted file mode 100644 index 48e067d66e9..00000000000 --- a/packages/react/src/__tests__/__snapshots__/ReactProfileMode-test.internal.js.snap +++ /dev/null @@ -1,65 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should render children 1`] = ` -
- Hi - - there - - - ProfileMode - -
-`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should support an empty mode 1`] = `null`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should support an empty mode 2`] = `
`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag disabled should support nested ProfileModes 1`] = ` -Array [ -
- Hi, - Brian -
, - - Hi, - Brian - , - - Now with extra profile strength! - , -] -`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should render children 1`] = ` -
- Hi - - there - - - ProfileMode - -
-`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should support an empty mode 1`] = `null`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should support an empty mode 2`] = `
`; - -exports[`ReactProfileMode enableProfileModeMetrics feature flag enabled should support nested ProfileModes 1`] = ` -Array [ -
- Hi, - Brian -
, - - Hi, - Brian - , - - Now with extra profile strength! - , -] -`; diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap new file mode 100644 index 00000000000..2d15220464f --- /dev/null +++ b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should render children 1`] = ` +
+ Hi + + there + + + ProfileMode + +
+`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty mode 1`] = `null`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty mode 2`] = `
`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support nested ProfileModes 1`] = ` +Array [ +
+ Hi, + Brian +
, + + Hi, + Brian + , + + Now with extra profile strength! + , +] +`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should render children 1`] = ` +
+ Hi + + there + + + ProfileMode + +
+`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty mode 1`] = `null`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty mode 2`] = `
`; + +exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support nested ProfileModes 1`] = ` +Array [ +
+ Hi, + Brian +
, + + Hi, + Brian + , + + Now with extra profile strength! + , +] +`; From 65a33f9347648f788630954430da93c9f45ee522 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 1 May 2018 14:02:57 -0700 Subject: [PATCH 05/72] Added some more (failing) tests --- .../ReactProfileRoot-test.internal.js | 173 ++++++++++++++++-- 1 file changed, 153 insertions(+), 20 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 04bc32d0012..bde12366074 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -95,6 +95,7 @@ describe('ProfileRoot', () => { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableProfileModeMetrics = true; + ReactFeatureFlags.enableSuspense = true; React = require('react'); ReactTestRenderer = require('react-test-renderer'); }); @@ -189,7 +190,7 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(3); }); - it('does not log times if sCU prevents a re-render', () => { + it('does not log update times for descendents of sCU false', () => { const callback = jest.fn(); let instance; @@ -209,26 +210,20 @@ describe('ProfileRoot', () => { const renderer = ReactTestRenderer.create( -
+ - + - - -
- - +
- + -
+ , ); // All profile callbacks are called for initial render - expect(callback).toHaveBeenCalledTimes(4); + expect(callback).toHaveBeenCalledTimes(3); callback.mockReset(); @@ -239,15 +234,153 @@ describe('ProfileRoot', () => { }); // Only call profile updates for paths that have re-rendered - // Since "blocked" is beneath a pure compoent, it isn't called - expect(callback).toHaveBeenCalledTimes(3); - expect(callback.mock.calls[0][0]).toBe('inner'); - expect(callback.mock.calls[1][0]).toBe('middle'); - expect(callback.mock.calls[2][0]).toBe('outer'); + // Since "inner" is beneath a pure compoent, it isn't called + expect(callback).toHaveBeenCalledTimes(2); + expect(callback.mock.calls[0][0]).toBe('middle'); + expect(callback.mock.calls[1][0]).toBe('outer'); + }); + + it('includes render times of nested ProfileRoots in their parent times', () => { + const callback = jest.fn(); + + ReactTestRenderer.create( + +
+ +
+ +
+
, + ); + + expect(callback).toHaveBeenCalledTimes(2); + + // Callbacks bubble (reverse order). + const [childCall, parentCall] = callback.mock.calls; + expect(childCall[0]).toBe('child'); + expect(parentCall[0]).toBe('parent'); + + // Parent times should include child times + expect(parentCall[2]).toBeGreaterThan(childCall[2]); // "actual" time + expect(parentCall[3]).toBeGreaterThan(childCall[3]); // "base" time }); - // TODO (bvaughn) Test updates only callback for committed modes + it('record a decrease in "actual" time and no change in "base" time when sCU memoization is used', () => { + const callback = jest.fn(); + + class Pure extends React.PureComponent { + render() { + return this.props.children; + } + } + + const renderer = ReactTestRenderer.create( + + +
+ + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + renderer.update( + + +
+ + , + ); - // TODO (bvaughn) Test nested updates work (outer update is greater value than inner) + expect(callback).toHaveBeenCalledTimes(2); + expect(callback.mock.calls[0][2]).toBeLessThan(callback.mock.calls[0][2]); // "actual" time + expect(callback.mock.calls[0][3]).toEqual(callback.mock.calls[0][3]); // "base" time + }); + + // TODO (bvaughn) Revisit these tests and maybe rewrite them better + describe('interruptions', () => { + let dateNow, setMaxElapsedTime; + beforeEach(() => { + dateNow = Date.now; + global.Date.now = () => Math.min(dateNow(), maxDateNow); + let maxDateNow = 0; + setMaxElapsedTime = function(duration) { + maxDateNow = dateNow() + duration; + }; + }); + afterEach(() => { + global.Date.now = dateNow; + }); + + it('should resume/accumulate "actual" time after a scheduling interruptions', () => { + const callback = jest.fn(); + + const Yield = ({value}) => { + renderer.unstable_yield(value); + return null; + }; + + setMaxElapsedTime(2); + + // Render partially, but run out of time before completing. + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isAsync: true}, + ); + renderer.unstable_flushThrough(['first']); + + expect(callback).toHaveBeenCalledTimes(0); + setMaxElapsedTime(1); + + // Resume/restart render. + renderer.unstable_flushAll(); + + // Verify that logged times include both durations above. + expect(callback).toHaveBeenCalledTimes(1); + expect(callback.mock.calls[0][2]).toBeGreaterThan(1); // "actual" time + expect(callback.mock.calls[0][3]).toBeGreaterThan(1); // "base" time + }); + + it('should resume/accumulate "actual" time after a higher priority interruption', () => { + const callback = jest.fn(); + + const Yield = ({value}) => { + renderer.unstable_yield(value); + return null; + }; + + setMaxElapsedTime(2); + + // Render partially, but don't complete + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isAsync: true}, + ); + renderer.unstable_flushThrough(['first']); + + expect(callback).toHaveBeenCalledTimes(0); + setMaxElapsedTime(1); + + // Interrupt with higher priority work + renderer.unstable_flushSync(() => { + renderer.update( + + + , + ); + }); + + // Verify that logged times include both durations above. + expect(callback).toHaveBeenCalledTimes(1); + expect(callback.mock.calls[0][2]).toBeGreaterThan(1); // "actual" time + expect(callback.mock.calls[0][3]).toBeGreaterThan(1); // "base" time + }); + }); }); }); From afb88c3fe9b78ff20ad85da538acb63ad138a568 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 1 May 2018 15:15:18 -0700 Subject: [PATCH 06/72] Updated TODO comments --- .../src/ReactFiberBeginWork.js | 52 +------------------ .../src/ReactFiberCommitWork.js | 4 +- .../src/ReactFiberCompleteWork.js | 9 ++++ .../src/ReactFiberScheduler.js | 5 ++ .../react-reconciler/src/ReactTypeOfMode.js | 8 +-- packages/react-reconciler/src/now.js | 24 +++++++++ 6 files changed, 46 insertions(+), 56 deletions(-) create mode 100644 packages/react-reconciler/src/now.js diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 934d0dd7253..a1e2a01278e 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -68,7 +68,7 @@ import { } from './ReactChildFiber'; import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; -import {AsyncMode, ProfileMode, StrictMode} from './ReactTypeOfMode'; +import {AsyncMode, StrictMode} from './ReactTypeOfMode'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -222,8 +222,7 @@ export default function( if (enableProfileModeMetrics) { workInProgress.effectTag |= CommitProfile; - // TODO (bvaughn) (render) Stop/resume timer for "actual" time - // TODO (bvaughn) (actual) Calculate sum of children's "base" time + // TODO (bvaughn) (actual) Start render timer here } // Don't bail out early for ProfileMode, @@ -250,11 +249,6 @@ export default function( const fn = workInProgress.type; const nextProps = workInProgress.pendingProps; - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) Start tracking "base" time - } - } if (hasLegacyContextChanged()) { // Normally we can bail out on props equality but if context has changed // we don't do the bailout and we have to reuse existing props instead. @@ -284,12 +278,6 @@ export default function( reconcileChildren(current, workInProgress, nextChildren); memoizeProps(workInProgress, nextProps); - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) Stop and record "base" time - } - } - return workInProgress.child; } @@ -298,12 +286,6 @@ export default function( workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) Start tracking "base" time - } - } - // Push context providers early to prevent context stack mismatches. // During mounting we don't know the child context yet as the instance doesn't exist. // We will invalidate the child context in finishClassComponent() right after rendering. @@ -335,12 +317,6 @@ export default function( ); } - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) Pass base start-time to finishClassComponent() - } - } - return finishClassComponent( current, workInProgress, @@ -406,12 +382,6 @@ export default function( } } - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) (base) Update "base" time - } - } - // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; if (didCaptureError) { @@ -619,12 +589,6 @@ export default function( let value; - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) (base) Start tracking "base" time - } - } - if (__DEV__) { if (fn.prototype && typeof fn.prototype.render === 'function') { const componentName = getComponentName(workInProgress) || 'Unknown'; @@ -678,12 +642,6 @@ export default function( adoptClassInstance(workInProgress, value); mountClassInstance(workInProgress, renderExpirationTime); - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) Pass base start-time to finishClassComponent() - } - } - return finishClassComponent( current, workInProgress, @@ -748,12 +706,6 @@ export default function( reconcileChildren(current, workInProgress, value); memoizeProps(workInProgress, props); - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) (base) Stop and update "base" time? - } - } - return workInProgress.child; } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 4ac00bf745a..f2c177dda5d 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -775,8 +775,8 @@ export default function( null, finishedWork.pendingProps.label, 'mount', - 1, // TODO (bvaughn) Use real "actual" times - 1, // TODO (bvaughn) Use real "base" times + 1, // TODO (bvaughn) Use real "actual" times (from stateNode) + 1, // TODO (bvaughn) Use real "base" times (from fiber) ); } break; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 4ab6f65eeff..b719a91704d 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -20,6 +20,7 @@ import { enableMutatingReconciler, enablePersistentReconciler, enableNoopReconciler, + enableProfileModeMetrics, } from 'shared/ReactFeatureFlags'; import { IndeterminateComponent, @@ -190,6 +191,13 @@ export default function( } } + function updateProfileRoot(workInProgress: Fiber) { + if (enableProfileModeMetrics) { + // TODO (bvaughn) (actual) Stop render timer here; store on stateNode + // TODO (bvaughn) (base) Sum child base times and store on Fiber + } + } + let updateHostContainer; let updateHostComponent; let updateHostText; @@ -593,6 +601,7 @@ export default function( case Mode: return null; case ProfileRoot: + updateProfileRoot(workInProgress); return null; case HostPortal: popHostContainer(workInProgress); diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 0a6079546b3..4fecff44ed1 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -359,6 +359,7 @@ export default function( } } + // TODO (bvaughn) Move this somewhere else? It isn't a hose effect. if (effectTag & CommitProfile) { commitProfileWork(nextEffect); } @@ -881,7 +882,11 @@ export default function( workInProgress, ); } + + // TODO (bvaughn) (base) Start "base" time here let next = beginWork(current, workInProgress, nextRenderExpirationTime); + // TODO (bvaughn) (base) Stop "base" time here; store on Fiber + if (__DEV__) { ReactDebugCurrentFiber.resetCurrentFiber(); if (isReplayingFailedUnitOfWork) { diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js index 85fe55bf4c0..4bf9da2ec8f 100644 --- a/packages/react-reconciler/src/ReactTypeOfMode.js +++ b/packages/react-reconciler/src/ReactTypeOfMode.js @@ -9,7 +9,7 @@ export type TypeOfMode = number; -export const NoContext = 0b00; -export const AsyncMode = 0b01; -export const StrictMode = 0b10; -export const ProfileMode = 0b11; +export const NoContext = 0b000; +export const AsyncMode = 0b001; +export const StrictMode = 0b010; +export const ProfileMode = 0b100; diff --git a/packages/react-reconciler/src/now.js b/packages/react-reconciler/src/now.js new file mode 100644 index 00000000000..871d0484012 --- /dev/null +++ b/packages/react-reconciler/src/now.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +const hasNativePerformanceNow = + typeof performance === 'object' && typeof performance.now === 'function'; + +let now; +if (hasNativePerformanceNow) { + now = function() { + return performance.now(); + }; +} else { + now = function() { + return Date.now(); + }; +} + +export default now; From e812f1428a9b346e83cca37da627135556156b86 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 2 May 2018 13:31:27 -0700 Subject: [PATCH 07/72] Added ReactProfileTimer. Addressed many TODOs. Most tests passing locally. --- packages/react-reconciler/src/ReactFiber.js | 13 +- .../src/ReactFiberBeginWork.js | 7 +- .../src/ReactFiberCommitWork.js | 6 +- .../src/ReactFiberCompleteWork.js | 30 +++- .../src/ReactFiberScheduler.js | 30 ++-- .../react-reconciler/src/ReactProfileTimer.js | 44 ++++++ packages/react-reconciler/src/now.js | 24 ---- .../ReactProfileRoot-test.internal.js | 129 ++++++++++-------- 8 files changed, 180 insertions(+), 103 deletions(-) create mode 100644 packages/react-reconciler/src/ReactProfileTimer.js delete mode 100644 packages/react-reconciler/src/now.js diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index dde6b55d780..943130b1043 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -155,7 +155,7 @@ export type Fiber = {| // Profiling metrics selfBaseTime: number | null, - descendantsBaseTime: number | null, + treeBaseTime: number | null, // Conceptual aliases // workInProgress : Fiber -> alternate The alternate used for reuse happens @@ -214,7 +214,7 @@ function FiberNode( if (enableProfileModeMetrics) { this.selfBaseTime = null; - this.descendantsBaseTime = null; + this.treeBaseTime = null; } if (__DEV__) { @@ -311,6 +311,11 @@ export function createWorkInProgress( workInProgress.index = current.index; workInProgress.ref = current.ref; + if (enableProfileModeMetrics) { + workInProgress.selfBaseTime = current.selfBaseTime; + workInProgress.treeBaseTime = current.treeBaseTime; + } + return workInProgress; } @@ -537,6 +542,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; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index a1e2a01278e..aa69e567599 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -69,6 +69,7 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; +import {startRenderTimer} from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -220,9 +221,11 @@ export default function( function updateProfileRoot(current, workInProgress) { if (enableProfileModeMetrics) { - workInProgress.effectTag |= CommitProfile; + // Start render timer here and push start time onto queue + startRenderTimer(workInProgress); - // TODO (bvaughn) (actual) Start render timer here + // Let the "complete" phase know to stop the timer + workInProgress.effectTag |= CommitProfile; } // Don't bail out early for ProfileMode, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index f2c177dda5d..5029fbb1eb9 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -774,9 +774,9 @@ export default function( finishedWork.pendingProps.callback.call( null, finishedWork.pendingProps.label, - 'mount', - 1, // TODO (bvaughn) Use real "actual" times (from stateNode) - 1, // TODO (bvaughn) Use real "base" times (from fiber) + finishedWork.alternate === null ? 'mount' : 'update', + finishedWork.stateNode, + finishedWork.treeBaseTime, ); } break; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index b719a91704d..cb2575815e3 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -40,7 +40,14 @@ import { Mode, ProfileRoot, } from 'shared/ReactTypeOfWork'; -import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect'; +import {ProfileMode} from './ReactTypeOfMode'; +import { + CommitProfile, + Placement, + Ref, + Update, +} from 'shared/ReactTypeOfSideEffect'; +import {stopRenderTimer} from './ReactProfileTimer'; import invariant from 'fbjs/lib/invariant'; import {reconcileChildFibers} from './ReactChildFiber'; @@ -193,8 +200,11 @@ export default function( function updateProfileRoot(workInProgress: Fiber) { if (enableProfileModeMetrics) { - // TODO (bvaughn) (actual) Stop render timer here; store on stateNode - // TODO (bvaughn) (base) Sum child base times and store on Fiber + if (workInProgress.effectTag & CommitProfile) { + // Stop render timer and store the elapsed time as stateNode. + // Commit work will read from this value and pass it along to the callback. + workInProgress.stateNode = stopRenderTimer(workInProgress); + } } } @@ -413,6 +423,20 @@ export default function( renderExpirationTime: ExpirationTime, ): Fiber | null { const newProps = workInProgress.pendingProps; + + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // Bubble up "base" render times if we're within a ProfileRoot + let treeBaseTime = workInProgress.selfBaseTime; + let child = workInProgress.child; + while (child !== null) { + treeBaseTime += child.treeBaseTime; + child = child.sibling; + } + workInProgress.treeBaseTime = treeBaseTime; + } + } + switch (workInProgress.tag) { case FunctionalComponent: return null; diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 4fecff44ed1..5f2eaaa42ff 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -41,10 +41,12 @@ import { HostPortal, } from 'shared/ReactTypeOfWork'; import { + enableProfileModeMetrics, enableUserTimingAPI, - warnAboutDeprecatedLifecycles, replayFailedUnitOfWorkWithInvokeGuardedCallback, + warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; +import {startRenderTimer, stopRenderTimer} from './ReactProfileTimer'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -159,6 +161,14 @@ if (__DEV__) { export default function( config: HostConfig, ) { + const { + now, + scheduleDeferredCallback, + cancelDeferredCallback, + prepareForCommit, + resetAfterCommit, + } = config; + const stack = ReactFiberStack(); const hostContext = ReactFiberHostContext(config, stack); const legacyContext = ReactFiberLegacyContext(stack); @@ -221,13 +231,6 @@ export default function( markLegacyErrorBoundaryAsFailed, recalculateCurrentTime, ); - const { - now, - scheduleDeferredCallback, - cancelDeferredCallback, - prepareForCommit, - resetAfterCommit, - } = config; // Represents the current time in ms. const originalStartTimeMs = now(); @@ -883,9 +886,14 @@ export default function( ); } - // TODO (bvaughn) (base) Start "base" time here - let next = beginWork(current, workInProgress, nextRenderExpirationTime); - // TODO (bvaughn) (base) Stop "base" time here; store on Fiber + let next; + if (enableProfileModeMetrics) { + startRenderTimer(workInProgress); + next = beginWork(current, workInProgress, nextRenderExpirationTime); + workInProgress.selfBaseTime = stopRenderTimer(workInProgress); + } else { + next = beginWork(current, workInProgress, nextRenderExpirationTime); + } if (__DEV__) { ReactDebugCurrentFiber.resetCurrentFiber(); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js new file mode 100644 index 00000000000..43e17d49e43 --- /dev/null +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Fiber} from './ReactFiber'; +import type {StackCursor} from './ReactFiberStack'; + +import ReactFiberStack from './ReactFiberStack'; + +const hasNativePerformanceNow = + typeof performance === 'object' && typeof performance.now === 'function'; + +let now; +if (hasNativePerformanceNow) { + now = function() { + return performance.now(); + }; +} else { + now = function() { + return Date.now(); + }; +} + +const {createCursor, push, pop} = ReactFiberStack(); + +let renderTimeStackCursor: StackCursor = createCursor(0); + +export function startRenderTimer(fiber: Fiber): void { + push(renderTimeStackCursor, now(), fiber); +} + +export function stopRenderTimer(fiber: Fiber): number { + const startTime = renderTimeStackCursor.current; + pop(renderTimeStackCursor, fiber); + const stopTime = now(); + return stopTime - startTime; +} + +// TODO (bvaughn) Support pausing and resuming the timer diff --git a/packages/react-reconciler/src/now.js b/packages/react-reconciler/src/now.js deleted file mode 100644 index 871d0484012..00000000000 --- a/packages/react-reconciler/src/now.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -const hasNativePerformanceNow = - typeof performance === 'object' && typeof performance.now === 'function'; - -let now; -if (hasNativePerformanceNow) { - now = function() { - return performance.now(); - }; -} else { - now = function() { - return Date.now(); - }; -} - -export default now; diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index bde12366074..fe11da60e46 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -90,27 +90,51 @@ describe('ProfileRoot', () => { }); describe('render timings', () => { + let AdvanceTime; + let advanceTimeBy; + beforeEach(() => { jest.resetModules(); + let currentTime = 0; + global.performance = { + now: () => { + return currentTime; + }, + }; + advanceTimeBy = amount => { + currentTime += amount; + }; + + // Import after polyfill ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableProfileModeMetrics = true; - ReactFeatureFlags.enableSuspense = true; React = require('react'); ReactTestRenderer = require('react-test-renderer'); + + AdvanceTime = class extends React.Component { + static defaultProps = { + shouldComponentUpdate: true, + }; + shouldComponentUpdate(nextProps) { + return nextProps.shouldComponentUpdate; + } + render() { + // Simulate time passing when this component is rendered + advanceTimeBy(this.props.amount || 10); + return this.props.children || null; + } + }; + }); + afterEach(() => { + delete global.performance; }); it('logs render times for mount and update', () => { const callback = jest.fn(); - class ClassComponent extends React.Component { - render() { - return null; - } - } - - const FunctionalComponent = props => props.children; const Yield = ({value}) => { + advanceTimeBy(10); renderer.unstable_yield(value); return null; }; @@ -119,15 +143,15 @@ describe('ProfileRoot', () => { -
+ - + - + - + -
+
, @@ -143,7 +167,7 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(3); // Callbacks bubble (reverse order). - const [innerCall, middleCall, outerCall] = callback.mock.calls; + let [innerCall, middleCall, outerCall] = callback.mock.calls; expect(innerCall).toHaveLength(4); expect(innerCall[0]).toBe('inner'); @@ -169,15 +193,15 @@ describe('ProfileRoot', () => { -
+ - + - + - + -
+
, @@ -188,6 +212,14 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(0); expect(renderer.unstable_flushAll()).toEqual(['last']); expect(callback).toHaveBeenCalledTimes(3); + [innerCall, middleCall, outerCall] = callback.mock.calls; + expect(innerCall).toHaveLength(4); + expect(innerCall[0]).toBe('inner'); + expect(innerCall[1]).toBe('update'); + expect(middleCall[0]).toBe('middle'); + expect(middleCall[1]).toBe('update'); + expect(outerCall[0]).toBe('outer'); + expect(outerCall[1]).toBe('update'); }); it('does not log update times for descendents of sCU false', () => { @@ -245,11 +277,11 @@ describe('ProfileRoot', () => { ReactTestRenderer.create( -
+ -
+ -
+
, ); @@ -268,17 +300,13 @@ describe('ProfileRoot', () => { it('record a decrease in "actual" time and no change in "base" time when sCU memoization is used', () => { const callback = jest.fn(); - class Pure extends React.PureComponent { - render() { - return this.props.children; - } - } - const renderer = ReactTestRenderer.create( - -
- + + + + + , ); @@ -286,9 +314,11 @@ describe('ProfileRoot', () => { renderer.update( - -
- + + + + + , ); @@ -298,30 +328,16 @@ describe('ProfileRoot', () => { }); // TODO (bvaughn) Revisit these tests and maybe rewrite them better - describe('interruptions', () => { - let dateNow, setMaxElapsedTime; - beforeEach(() => { - dateNow = Date.now; - global.Date.now = () => Math.min(dateNow(), maxDateNow); - let maxDateNow = 0; - setMaxElapsedTime = function(duration) { - maxDateNow = dateNow() + duration; - }; - }); - afterEach(() => { - global.Date.now = dateNow; - }); - + describe('interrupted render timings', () => { it('should resume/accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); const Yield = ({value}) => { + advanceTimeBy(10); renderer.unstable_yield(value); return null; }; - setMaxElapsedTime(2); - // Render partially, but run out of time before completing. const renderer = ReactTestRenderer.create( @@ -333,27 +349,25 @@ describe('ProfileRoot', () => { renderer.unstable_flushThrough(['first']); expect(callback).toHaveBeenCalledTimes(0); - setMaxElapsedTime(1); // Resume/restart render. renderer.unstable_flushAll(); // Verify that logged times include both durations above. expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBeGreaterThan(1); // "actual" time - expect(callback.mock.calls[0][3]).toBeGreaterThan(1); // "base" time + expect(callback.mock.calls[0][2]).toBeGreaterThan(4); // "actual" time + expect(callback.mock.calls[0][3]).toBeGreaterThan(4); // "base" time }); it('should resume/accumulate "actual" time after a higher priority interruption', () => { const callback = jest.fn(); const Yield = ({value}) => { + advanceTimeBy(10); renderer.unstable_yield(value); return null; }; - setMaxElapsedTime(2); - // Render partially, but don't complete const renderer = ReactTestRenderer.create( @@ -365,7 +379,6 @@ describe('ProfileRoot', () => { renderer.unstable_flushThrough(['first']); expect(callback).toHaveBeenCalledTimes(0); - setMaxElapsedTime(1); // Interrupt with higher priority work renderer.unstable_flushSync(() => { @@ -378,8 +391,8 @@ describe('ProfileRoot', () => { // Verify that logged times include both durations above. expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBeGreaterThan(1); // "actual" time - expect(callback.mock.calls[0][3]).toBeGreaterThan(1); // "base" time + expect(callback.mock.calls[0][2]).toBeGreaterThan(3); // "actual" time + expect(callback.mock.calls[0][3]).toBeGreaterThan(3); // "base" time }); }); }); From a0f35008b37beab6b66e68e0097be031df9a492a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 2 May 2018 14:55:43 -0700 Subject: [PATCH 08/72] Don't update base time for bailouts --- .../src/ReactFiberBeginWork.js | 7 +++- .../src/ReactFiberScheduler.js | 13 +++++-- .../react-reconciler/src/ReactProfileTimer.js | 38 ++++++++++++++++--- .../ReactProfileRoot-test.internal.js | 34 +++++++++-------- 4 files changed, 67 insertions(+), 25 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index aa69e567599..82248f5573d 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -69,7 +69,7 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import {startRenderTimer} from './ReactProfileTimer'; +import {cancelBaseTimer, startRenderTimer} from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -1080,6 +1080,11 @@ export default function( ): Fiber | null { cancelWorkTimer(workInProgress); + if (enableProfileModeMetrics) { + // Don't update "base" render times for bailouts. + cancelBaseTimer(); + } + // 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 diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 5f2eaaa42ff..e746132bede 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -46,7 +46,11 @@ import { replayFailedUnitOfWorkWithInvokeGuardedCallback, warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; -import {startRenderTimer, stopRenderTimer} from './ReactProfileTimer'; +import { + isBaseTimerRunning, + startBaseTimer, + stopBaseTimer, +} from './ReactProfileTimer'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -888,9 +892,12 @@ export default function( let next; if (enableProfileModeMetrics) { - startRenderTimer(workInProgress); + startBaseTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); - workInProgress.selfBaseTime = stopRenderTimer(workInProgress); + if (isBaseTimerRunning()) { + workInProgress.selfBaseTime = stopBaseTimer(); + } else { + } } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); } diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 43e17d49e43..1378b7ad6c2 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -34,11 +34,39 @@ export function startRenderTimer(fiber: Fiber): void { push(renderTimeStackCursor, now(), fiber); } -export function stopRenderTimer(fiber: Fiber): number { - const startTime = renderTimeStackCursor.current; +export function stopRenderTimer(fiber: Fiber): number | null { + const maybeStartTime = renderTimeStackCursor.current; + pop(renderTimeStackCursor, fiber); - const stopTime = now(); - return stopTime - startTime; + + if (maybeStartTime === null) { + return null; + } else { + return now() - maybeStartTime; + } } -// TODO (bvaughn) Support pausing and resuming the timer +/** + * The "base" render time is the duration of the “begin” phase of work for a particular fiber. + * This time is measured and stored on each fiber. + * The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase. + * If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated. + */ + +let baseStartTime: number | null = null; + +export function cancelBaseTimer(): void { + baseStartTime = null; +} + +export function isBaseTimerRunning(): boolean { + return baseStartTime !== null; +} + +export function startBaseTimer(): void { + baseStartTime = now(); +} + +export function stopBaseTimer(): number | null { + return baseStartTime === null ? null : now() - baseStartTime; +} diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index fe11da60e46..29e2ac81e80 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -297,15 +297,13 @@ describe('ProfileRoot', () => { expect(parentCall[3]).toBeGreaterThan(childCall[3]); // "base" time }); - it('record a decrease in "actual" time and no change in "base" time when sCU memoization is used', () => { + it('records a decrease in "actual" time and no change in "base" time when sCU memoization is used', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - - - + , ); @@ -315,16 +313,20 @@ describe('ProfileRoot', () => { renderer.update( - - - + , ); expect(callback).toHaveBeenCalledTimes(2); - expect(callback.mock.calls[0][2]).toBeLessThan(callback.mock.calls[0][2]); // "actual" time - expect(callback.mock.calls[0][3]).toEqual(callback.mock.calls[0][3]); // "base" time + + const [mountCall, updateCall] = callback.mock.calls; + + expect(mountCall[1]).toBe('mount'); + expect(updateCall[1]).toBe('update'); + + expect(updateCall[2]).toBeLessThan(mountCall[2]); // "actual" time + expect(updateCall[3]).toEqual(mountCall[3]); // "base" time }); // TODO (bvaughn) Revisit these tests and maybe rewrite them better @@ -362,8 +364,8 @@ describe('ProfileRoot', () => { it('should resume/accumulate "actual" time after a higher priority interruption', () => { const callback = jest.fn(); - const Yield = ({value}) => { - advanceTimeBy(10); + const Yield = ({renderTime, value}) => { + advanceTimeBy(renderTime); renderer.unstable_yield(value); return null; }; @@ -371,8 +373,8 @@ describe('ProfileRoot', () => { // Render partially, but don't complete const renderer = ReactTestRenderer.create( - - + + , {unstable_isAsync: true}, ); @@ -384,15 +386,15 @@ describe('ProfileRoot', () => { renderer.unstable_flushSync(() => { renderer.update( - + , ); }); // Verify that logged times include both durations above. expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBeGreaterThan(3); // "actual" time - expect(callback.mock.calls[0][3]).toBeGreaterThan(3); // "base" time + expect(callback.mock.calls[0][2]).toBe(15); // "actual" time + expect(callback.mock.calls[0][3]).toBe(15); // "base" time }); }); }); From 732ec350bdc2750afea5fd6fbaf3e6f39bc5e29e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 2 May 2018 15:13:17 -0700 Subject: [PATCH 09/72] Added some comments and renamed a few methods --- .../src/ReactFiberBeginWork.js | 16 ++++++---- .../src/ReactFiberCompleteWork.js | 6 ++-- .../src/ReactFiberScheduler.js | 29 +++++++++---------- .../react-reconciler/src/ReactProfileTimer.js | 27 +++++++++-------- .../ReactProfileRoot-test.internal.js | 1 - .../ReactFeatureFlags.native-fabric-oss.js | 1 - 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 82248f5573d..5fe5d70388d 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -69,7 +69,10 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import {cancelBaseTimer, startRenderTimer} from './ReactProfileTimer'; +import { + cancelBaseRenderTimer, + startActualRenderTimer, +} from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -222,14 +225,15 @@ export default function( function updateProfileRoot(current, workInProgress) { if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue - startRenderTimer(workInProgress); + startActualRenderTimer(workInProgress); - // Let the "complete" phase know to stop the timer + // Let the "complete" phase know to stop the timer, + // And the scheduler to record the measured time. workInProgress.effectTag |= CommitProfile; } - // Don't bail out early for ProfileMode, - // Because we always want to re-measure the subtree + // Never bail out early for ProfileRoots. + // We always want to re-measure the subtree. const nextChildren = workInProgress.pendingProps.children; reconcileChildren(current, workInProgress, nextChildren); @@ -1082,7 +1086,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - cancelBaseTimer(); + cancelBaseRenderTimer(); } // TODO: We should ideally be able to bail out early if the children have no diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index cb2575815e3..e58e7a1f87e 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -47,7 +47,7 @@ import { Ref, Update, } from 'shared/ReactTypeOfSideEffect'; -import {stopRenderTimer} from './ReactProfileTimer'; +import {stopActualRenderTimer} from './ReactProfileTimer'; import invariant from 'fbjs/lib/invariant'; import {reconcileChildFibers} from './ReactChildFiber'; @@ -202,8 +202,8 @@ export default function( if (enableProfileModeMetrics) { if (workInProgress.effectTag & CommitProfile) { // Stop render timer and store the elapsed time as stateNode. - // Commit work will read from this value and pass it along to the callback. - workInProgress.stateNode = stopRenderTimer(workInProgress); + // The "commit" phase reads this value and passes it along to the callback. + workInProgress.stateNode = stopActualRenderTimer(workInProgress); } } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index e746132bede..3211ed6b37d 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -47,9 +47,9 @@ import { warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; import { - isBaseTimerRunning, - startBaseTimer, - stopBaseTimer, + isBaseRenderTimerRunning, + startBaseRenderTimer, + stopBaseRenderTimer, } from './ReactProfileTimer'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; @@ -165,14 +165,6 @@ if (__DEV__) { export default function( config: HostConfig, ) { - const { - now, - scheduleDeferredCallback, - cancelDeferredCallback, - prepareForCommit, - resetAfterCommit, - } = config; - const stack = ReactFiberStack(); const hostContext = ReactFiberHostContext(config, stack); const legacyContext = ReactFiberLegacyContext(stack); @@ -235,6 +227,13 @@ export default function( markLegacyErrorBoundaryAsFailed, recalculateCurrentTime, ); + const { + now, + scheduleDeferredCallback, + cancelDeferredCallback, + prepareForCommit, + resetAfterCommit, + } = config; // Represents the current time in ms. const originalStartTimeMs = now(); @@ -892,11 +891,11 @@ export default function( let next; if (enableProfileModeMetrics) { - startBaseTimer(); + startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); - if (isBaseTimerRunning()) { - workInProgress.selfBaseTime = stopBaseTimer(); - } else { + if (isBaseRenderTimerRunning()) { + // Update "base" time if the render wasn't bailed out on. + workInProgress.selfBaseTime = stopBaseRenderTimer(); } } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 1378b7ad6c2..acc194f33f4 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -26,24 +26,27 @@ if (hasNativePerformanceNow) { }; } +/** + * The "actual" render time is total time required to render the descendants of a ProfileRoot component. + * This time is stored as a stack, since ProfileRoots can be nested. + * This time is started during the "begin" phase and stopped during the "complete" phase. + * It is paused (and accumulated) in the event of an interruption or an aborted render. + */ + const {createCursor, push, pop} = ReactFiberStack(); let renderTimeStackCursor: StackCursor = createCursor(0); -export function startRenderTimer(fiber: Fiber): void { +export function startActualRenderTimer(fiber: Fiber): void { push(renderTimeStackCursor, now(), fiber); } -export function stopRenderTimer(fiber: Fiber): number | null { - const maybeStartTime = renderTimeStackCursor.current; +export function stopActualRenderTimer(fiber: Fiber): number { + const startTime = renderTimeStackCursor.current; pop(renderTimeStackCursor, fiber); - if (maybeStartTime === null) { - return null; - } else { - return now() - maybeStartTime; - } + return now() - startTime; } /** @@ -55,18 +58,18 @@ export function stopRenderTimer(fiber: Fiber): number | null { let baseStartTime: number | null = null; -export function cancelBaseTimer(): void { +export function cancelBaseRenderTimer(): void { baseStartTime = null; } -export function isBaseTimerRunning(): boolean { +export function isBaseRenderTimerRunning(): boolean { return baseStartTime !== null; } -export function startBaseTimer(): void { +export function startBaseRenderTimer(): void { baseStartTime = now(); } -export function stopBaseTimer(): number | null { +export function stopBaseRenderTimer(): number | null { return baseStartTime === null ? null : now() - baseStartTime; } diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 29e2ac81e80..4fe2e046f2f 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -329,7 +329,6 @@ describe('ProfileRoot', () => { expect(updateCall[3]).toEqual(mountCall[3]); // "base" time }); - // TODO (bvaughn) Revisit these tests and maybe rewrite them better describe('interrupted render timings', () => { it('should resume/accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js index 907b8169366..5eabc978444 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js @@ -1,5 +1,4 @@ /** -export const enableProfileModeMetrics = false; * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the From 0e86e9a14afd7a6361faacb0668b8554d127ad09 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 2 May 2018 17:48:31 -0700 Subject: [PATCH 10/72] Added DEV error checking for unexpected base render timer start/stopping --- .../src/ReactFiberBeginWork.js | 12 +++--- .../src/ReactFiberScheduler.js | 10 ++++- .../react-reconciler/src/ReactProfileTimer.js | 42 +++++++++++++++++-- .../ReactProfileRoot-test.internal.js | 4 ++ 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 5fe5d70388d..beb6a070a18 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -69,10 +69,7 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import { - cancelBaseRenderTimer, - startActualRenderTimer, -} from './ReactProfileTimer'; +import {stopBaseRenderTimer, startActualRenderTimer} from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -1086,7 +1083,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - cancelBaseRenderTimer(); + stopBaseRenderTimer(); } // TODO: We should ideally be able to bail out early if the children have no @@ -1110,6 +1107,11 @@ export default function( function bailoutOnLowPriority(current, workInProgress) { cancelWorkTimer(workInProgress); + if (enableProfileModeMetrics) { + // Don't update "base" render times for bailouts. + stopBaseRenderTimer(); + } + // TODO: Handle HostComponent tags here as well and call pushHostContext()? // See PR 8590 discussion for context switch (workInProgress.tag) { diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 3211ed6b37d..2016ce004df 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -47,6 +47,7 @@ import { warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; import { + getElapsedBaseRenderTime, isBaseRenderTimerRunning, startBaseRenderTimer, stopBaseRenderTimer, @@ -282,6 +283,12 @@ export default function( error: mixed, isAsync: boolean, ) => { + if (enableProfileModeMetrics) { + // Stop "base" render timer in the event of an error. + // It will be restarted when we replay the failed work. + stopBaseRenderTimer(); + } + // Restore the original state of the work-in-progress assignFiberPropertiesInDEV( failedUnitOfWork, @@ -895,7 +902,8 @@ export default function( next = beginWork(current, workInProgress, nextRenderExpirationTime); if (isBaseRenderTimerRunning()) { // Update "base" time if the render wasn't bailed out on. - workInProgress.selfBaseTime = stopBaseRenderTimer(); + workInProgress.selfBaseTime = getElapsedBaseRenderTime(); + stopBaseRenderTimer(); } } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index acc194f33f4..36653f0b17c 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -10,6 +10,7 @@ import type {Fiber} from './ReactFiber'; import type {StackCursor} from './ReactFiberStack'; +import warning from 'fbjs/lib/warning'; import ReactFiberStack from './ReactFiberStack'; const hasNativePerformanceNow = @@ -58,8 +59,19 @@ export function stopActualRenderTimer(fiber: Fiber): number { let baseStartTime: number | null = null; -export function cancelBaseRenderTimer(): void { - baseStartTime = null; +export function getElapsedBaseRenderTime(): number { + if (__DEV__) { + if (baseStartTime === null) { + warning( + false, + 'Cannot read elapsed time when base timer is not running. ' + + 'This error is likely caused by a bug in React. ' + + 'Please file an issue.', + ); + } + } + + return baseStartTime === null ? 0 : now() - baseStartTime; } export function isBaseRenderTimerRunning(): boolean { @@ -67,9 +79,31 @@ export function isBaseRenderTimerRunning(): boolean { } export function startBaseRenderTimer(): void { + if (__DEV__) { + if (baseStartTime !== null) { + warning( + false, + 'Cannot start base timer that is already running. ' + + 'This error is likely caused by a bug in React. ' + + 'Please file an issue.', + ); + } + } + baseStartTime = now(); } -export function stopBaseRenderTimer(): number | null { - return baseStartTime === null ? null : now() - baseStartTime; +export function stopBaseRenderTimer(): void { + if (__DEV__) { + if (baseStartTime === null) { + warning( + false, + 'Cannot stop a base timer is not running. ' + + 'This error is likely caused by a bug in React. ' + + 'Please file an issue.', + ); + } + } + + baseStartTime = null; } diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 4fe2e046f2f..45d48a398d9 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -394,6 +394,10 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); expect(callback.mock.calls[0][2]).toBe(15); // "actual" time expect(callback.mock.calls[0][3]).toBe(15); // "base" time + + // Verify no more unexpected callbacks from low priority work + renderer.unstable_flushAll(); + expect(callback).toHaveBeenCalledTimes(1); }); }); }); From 4e987dbcb34d03294f5a5250b0b516c4807f3464 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 3 May 2018 15:57:42 -0700 Subject: [PATCH 11/72] Added error handling behavior (and test) --- packages/react-reconciler/src/ReactFiber.js | 50 +++++++++++----- .../src/ReactFiberBeginWork.js | 5 ++ .../src/ReactFiberCommitWork.js | 3 + .../src/ReactFiberCompleteWork.js | 3 +- .../src/ReactFiberScheduler.js | 25 ++++++-- .../src/ReactFiberUnwindWork.js | 25 +++++++- .../react-reconciler/src/ReactProfileTimer.js | 29 +++++++++ .../ReactProfileRoot-test.internal.js | 60 ++++++++++++++++++- 8 files changed, 174 insertions(+), 26 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 943130b1043..d267aadd739 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -213,8 +213,8 @@ function FiberNode( this.alternate = null; if (enableProfileModeMetrics) { - this.selfBaseTime = null; - this.treeBaseTime = null; + this.selfBaseTime = 0; + this.treeBaseTime = 0; } if (__DEV__) { @@ -362,20 +362,12 @@ export function createFiberFromElement( mode |= StrictMode; break; case REACT_PROFILE_MODE_TYPE: - if (__DEV__) { - if ( - typeof element.props.label !== 'string' || - typeof element.props.callback !== 'function' - ) { - invariant( - false, - 'ProfileMode must specify a label string and callback function', - ); - } - } - fiberTag = ProfileRoot; - mode |= ProfileMode; - break; + return createFiberFromProfileMode( + pendingProps, + mode, + expirationTime, + key, + ); case REACT_CALL_TYPE: fiberTag = CallComponent; break; @@ -473,6 +465,32 @@ export function createFiberFromFragment( return fiber; } +export function createFiberFromProfileMode( + pendingProps: any, + mode: TypeOfMode, + expirationTime: ExpirationTime, + key: null | string, +): Fiber { + if (__DEV__) { + if ( + typeof pendingProps.label !== 'string' || + typeof pendingProps.callback !== 'function' + ) { + invariant( + false, + 'ProfileMode must specify a label string and callback function', + ); + } + } + + const fiber = createFiber(ProfileRoot, pendingProps, key, mode | ProfileMode); + fiber.type = REACT_PROFILE_MODE_TYPE; + fiber.expirationTime = expirationTime; + fiber.stateNode = 0; + + return fiber; +} + export function createFiberFromText( content: string, mode: TypeOfMode, diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index beb6a070a18..d3634595d38 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -369,6 +369,11 @@ export default function( // the new API. // TODO: Warn in a future release. nextChildren = null; + + if (enableProfileModeMetrics) { + // Stop "base" render timer in this case to avoid overriding the actual times. + stopBaseRenderTimer(); + } } else { if (__DEV__) { ReactDebugCurrentFiber.setCurrentPhase('render'); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 5029fbb1eb9..64278332954 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -779,6 +779,9 @@ export default function( finishedWork.treeBaseTime, ); } + // Reset actualTime after successful commit. + // By default, we append to this time to account for errors and pauses. + finishedWork.stateNode = 0; break; } default: { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index e58e7a1f87e..9496584200f 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -203,7 +203,8 @@ export default function( if (workInProgress.effectTag & CommitProfile) { // Stop render timer and store the elapsed time as stateNode. // The "commit" phase reads this value and passes it along to the callback. - workInProgress.stateNode = stopActualRenderTimer(workInProgress); + // TODO (bvaughn) Do we need to do this? I don't think so? But we do need to handle frame resumes here. + workInProgress.stateNode += stopActualRenderTimer(workInProgress); } } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 2016ce004df..106e483c282 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -283,12 +283,6 @@ export default function( error: mixed, isAsync: boolean, ) => { - if (enableProfileModeMetrics) { - // Stop "base" render timer in the event of an error. - // It will be restarted when we replay the failed work. - stopBaseRenderTimer(); - } - // Restore the original state of the work-in-progress assignFiberPropertiesInDEV( failedUnitOfWork, @@ -320,6 +314,13 @@ export default function( originalReplayError = null; if (hasCaughtError()) { clearCaughtError(); + + if (enableProfileModeMetrics) { + // Update "base" time if the render wasn't bailed out on. + failedUnitOfWork.selfBaseTime = failedUnitOfWork.treeBaseTime = getElapsedBaseRenderTime(); + // Stop "base" render timer again (after the re-thrown error). + stopBaseRenderTimer(); + } } else { // If the begin phase did not fail the second time, set this pointer // back to the original value. @@ -900,6 +901,12 @@ export default function( if (enableProfileModeMetrics) { startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); + + // TODO (bvaughn) Why do we begin and complete work again for even though we don't render it? + // This second begin/complete is overriding our base times to 0. + // Can we detect this case and not update or rerun the timer? + // Can we stop the time inside (like we do for bailouts)? + if (isBaseRenderTimerRunning()) { // Update "base" time if the render wasn't bailed out on. workInProgress.selfBaseTime = getElapsedBaseRenderTime(); @@ -986,6 +993,12 @@ export default function( try { workLoop(isAsync); } catch (thrownValue) { + if (enableProfileModeMetrics) { + // Stop "base" render timer in the event of an error. + // It will be restarted when we replay the failed work. + stopBaseRenderTimer(); + } + if (nextUnitOfWork === null) { // This is a fatal error. didFatal = true; diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 05a8a1856d0..dcb17ff99f7 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -31,13 +31,17 @@ import { ContextProvider, } from 'shared/ReactTypeOfWork'; import { - NoEffect, DidCapture, Incomplete, + NoEffect, ShouldCapture, } from 'shared/ReactTypeOfSideEffect'; +import {ProfileMode} from './ReactTypeOfMode'; -import {enableGetDerivedStateFromCatch} from 'shared/ReactFeatureFlags'; +import { + enableGetDerivedStateFromCatch, + enableProfileModeMetrics, +} from 'shared/ReactFeatureFlags'; export default function( hostContext: HostContext, @@ -180,6 +184,23 @@ export default function( } function unwindWork(workInProgress: Fiber) { + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // Bubble up "base" render times if we're within a ProfileRoot + let treeBaseTime = workInProgress.selfBaseTime; + let child = workInProgress.child; + while (child !== null) { + treeBaseTime += child.treeBaseTime; + child = child.sibling; + } + workInProgress.treeBaseTime = treeBaseTime; + } + + if (workInProgress.mode & ProfileMode) { + // TODO (bvaughn) Maybe store info on ProfileMode stateNodes about unwind time? + } + } + switch (workInProgress.tag) { case ClassComponent: { popLegacyContextProvider(workInProgress); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 36653f0b17c..b55b04f5135 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -50,6 +50,8 @@ export function stopActualRenderTimer(fiber: Fiber): number { return now() - startTime; } +// TODO (bvaughn) Pause actual timer + /** * The "base" render time is the duration of the “begin” phase of work for a particular fiber. * This time is measured and stored on each fiber. @@ -107,3 +109,30 @@ export function stopBaseRenderTimer(): void { baseStartTime = null; } + +/* TODO For testing use only +export function debugPrintStack(workInProgress: Fiber): void { + let current = workInProgress; + while (current.return !== null) { + current = current.return; + } + let string = ''; + + const recurse = (fiber: Fiber, depth: number) => { + let prefix = ' '; + for (let i = 0; i <= depth; i++) { + prefix += ' '; + } + string += '\n' + prefix + require('shared/getComponentName').default(fiber) + + ' {selfBaseTime: ' + fiber.selfBaseTime + ', treeBaseTime: ' + fiber.treeBaseTime + '}'; + let innerCurrent = fiber.child; + while (innerCurrent !== null) { + recurse(innerCurrent, depth + 1); + innerCurrent = innerCurrent.sibling; + } + }; + + recurse(current, 0); + console.log(' debugPrintStack()' + string); +} +*/ diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 45d48a398d9..a4b5ff65c53 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -109,11 +109,13 @@ describe('ProfileRoot', () => { // Import after polyfill ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableProfileModeMetrics = true; + ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; React = require('react'); ReactTestRenderer = require('react-test-renderer'); AdvanceTime = class extends React.Component { static defaultProps = { + byAmount: 10, shouldComponentUpdate: true, }; shouldComponentUpdate(nextProps) { @@ -121,7 +123,7 @@ describe('ProfileRoot', () => { } render() { // Simulate time passing when this component is rendered - advanceTimeBy(this.props.amount || 10); + advanceTimeBy(this.props.byAmount); return this.props.children || null; } }; @@ -399,6 +401,62 @@ describe('ProfileRoot', () => { renderer.unstable_flushAll(); expect(callback).toHaveBeenCalledTimes(1); }); + + it('should resume/accumulate "actual" time after an ErrorBoundary re-render', () => { + const callback = jest.fn(); + + const ThrowsError = () => { + advanceTimeBy(10); + throw Error('expected error'); + }; + + class ErrorBoundary extends React.Component { + state = {error: null}; + componentDidCatch(error) { + this.setState({error}); + } + render() { + advanceTimeBy(2); + return this.state.error === null ? ( + this.props.children + ) : ( + + ); + } + } + + ReactTestRenderer.create( + + + + + + , + ); + + expect(callback).toHaveBeenCalledTimes(2); + + // Callbacks bubble (reverse order). + let [mountCall, updateCall] = callback.mock.calls; + + // TODO (bvaughn) Maybe add test with replayFailedUnitOfWorkWithInvokeGuardedCallback enabled. + // It would add 10ms to the first "actual" time below. + + // The initial mount only includes the ErrorBoundary (which takes 2ms) + // But it spends time rendering all of the failed subtree also. + expect(mountCall[1]).toBe('mount'); + // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) + expect(mountCall[2]).toBe(17); + // "base" time includes: 2 (ErrorBoundary) + expect(mountCall[3]).toBe(2); + + // The update includes the ErrorBoundary and its fallback child + expect(updateCall[1]).toBe('update'); + // "actual" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(updateCall[2]).toBe(22); + // "base" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(updateCall[3]).toBe(22); + }); }); }); }); From 8d86fadf53c688bdb3a370dd6b2122c7561fcef4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 3 May 2018 18:49:03 -0700 Subject: [PATCH 12/72] Cleaned up and improved tests --- .../ReactProfileRoot-test.internal.js | 211 ++++++++++-------- 1 file changed, 120 insertions(+), 91 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index a4b5ff65c53..65925434fd6 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -108,6 +108,8 @@ describe('ProfileRoot', () => { // Import after polyfill ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.debugRenderPhaseSideEffects = false; + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.enableProfileModeMetrics = true; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; React = require('react'); @@ -132,31 +134,19 @@ describe('ProfileRoot', () => { delete global.performance; }); - it('logs render times for mount and update', () => { + it('does not log render times until commit', () => { const callback = jest.fn(); const Yield = ({value}) => { - advanceTimeBy(10); renderer.unstable_yield(value); return null; }; const renderer = ReactTestRenderer.create( - + - - - - - - - - - - - - , + , { unstable_isAsync: true, }, @@ -166,65 +156,125 @@ describe('ProfileRoot', () => { renderer.unstable_flushThrough(['first']); expect(callback).toHaveBeenCalledTimes(0); expect(renderer.unstable_flushAll()).toEqual(['last']); - expect(callback).toHaveBeenCalledTimes(3); + expect(callback).toHaveBeenCalledTimes(1); + }); - // Callbacks bubble (reverse order). - let [innerCall, middleCall, outerCall] = callback.mock.calls; - - expect(innerCall).toHaveLength(4); - expect(innerCall[0]).toBe('inner'); - expect(innerCall[1]).toBe('mount'); - expect(innerCall[2]).toBeGreaterThan(0); // "actual" time - expect(innerCall[3]).toBeGreaterThan(0); // "base" time - - expect(middleCall).toHaveLength(4); - expect(middleCall[0]).toBe('middle'); - expect(middleCall[1]).toBe('mount'); - expect(middleCall[2]).toBeGreaterThan(0); // "actual" time - expect(middleCall[3]).toBeGreaterThan(0); // "base" time - - expect(outerCall).toHaveLength(4); - expect(outerCall[0]).toBe('outer'); - expect(outerCall[1]).toBe('mount'); - expect(outerCall[2]).toBeGreaterThan(0); // "actual" time - expect(outerCall[3]).toBeGreaterThan(0); // "base" time + it('logs render times for mount and update', () => { + const callback = jest.fn(); + + const renderer = ReactTestRenderer.create( + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + let [call] = callback.mock.calls; + + expect(call).toHaveLength(4); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(10); // "actual" time + expect(call[3]).toBe(10); // "base" time callback.mockReset(); renderer.update( + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + [call] = callback.mock.calls; + + expect(call).toHaveLength(4); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(10); // "actual" time + expect(call[3]).toBe(10); // "base" time + }); + + it('includes render times of nested ProfileRoots in their parent times', () => { + const callback = jest.fn(); + + ReactTestRenderer.create( - - - - - - - - - + + + + - , ); - // Times are logged until a render is committed. - renderer.unstable_flushThrough(['first']); - expect(callback).toHaveBeenCalledTimes(0); - expect(renderer.unstable_flushAll()).toEqual(['last']); - expect(callback).toHaveBeenCalledTimes(3); - [innerCall, middleCall, outerCall] = callback.mock.calls; - expect(innerCall).toHaveLength(4); - expect(innerCall[0]).toBe('inner'); - expect(innerCall[1]).toBe('update'); - expect(middleCall[0]).toBe('middle'); - expect(middleCall[1]).toBe('update'); - expect(outerCall[0]).toBe('outer'); - expect(outerCall[1]).toBe('update'); + expect(callback).toHaveBeenCalledTimes(2); + + // Callbacks bubble (reverse order). + const [childCall, parentCall] = callback.mock.calls; + expect(childCall[0]).toBe('child'); + expect(parentCall[0]).toBe('parent'); + + // Parent times should include child times + expect(childCall[2]).toBe(20); // "actual" time + expect(childCall[3]).toBe(20); // "base" time + expect(parentCall[2]).toBe(30); // "actual" time + expect(parentCall[3]).toBe(30); // "base" time }); - it('does not log update times for descendents of sCU false', () => { + it('tracks sibling ProfileRoots separately', () => { + const callback = jest.fn(); + + ReactTestRenderer.create( + + + + + + + + , + ); + + expect(callback).toHaveBeenCalledTimes(2); + + const [firstCall, secondCall] = callback.mock.calls; + expect(firstCall[0]).toBe('first'); + expect(secondCall[0]).toBe('second'); + + // Parent times should include child times + expect(firstCall[2]).toBe(20); // "actual" time + expect(firstCall[3]).toBe(20); // "base" time + expect(secondCall[2]).toBe(5); // "actual" time + expect(secondCall[3]).toBe(5); // "base" time + }); + + it('does not include time spent outside of profile root', () => { + const callback = jest.fn(); + + ReactTestRenderer.create( + + + + + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + const [call] = callback.mock.calls; + expect(call[0]).toBe('test'); + expect(call[2]).toBe(5); // "actual" time + expect(call[3]).toBe(5); // "base" time + }); + + it('does not call callbacks for descendents of sCU false', () => { const callback = jest.fn(); let instance; @@ -274,31 +324,6 @@ describe('ProfileRoot', () => { expect(callback.mock.calls[1][0]).toBe('outer'); }); - it('includes render times of nested ProfileRoots in their parent times', () => { - const callback = jest.fn(); - - ReactTestRenderer.create( - - - - - - - , - ); - - expect(callback).toHaveBeenCalledTimes(2); - - // Callbacks bubble (reverse order). - const [childCall, parentCall] = callback.mock.calls; - expect(childCall[0]).toBe('child'); - expect(parentCall[0]).toBe('parent'); - - // Parent times should include child times - expect(parentCall[2]).toBeGreaterThan(childCall[2]); // "actual" time - expect(parentCall[3]).toBeGreaterThan(childCall[3]); // "base" time - }); - it('records a decrease in "actual" time and no change in "base" time when sCU memoization is used', () => { const callback = jest.fn(); @@ -325,10 +350,12 @@ describe('ProfileRoot', () => { const [mountCall, updateCall] = callback.mock.calls; expect(mountCall[1]).toBe('mount'); - expect(updateCall[1]).toBe('update'); + expect(mountCall[2]).toBe(20); // "actual" time + expect(mountCall[3]).toBe(20); // "base" time - expect(updateCall[2]).toBeLessThan(mountCall[2]); // "actual" time - expect(updateCall[3]).toEqual(mountCall[3]); // "base" time + expect(updateCall[1]).toBe('update'); + expect(updateCall[2]).toBe(10); // "actual" time + expect(updateCall[3]).toBe(20); // "base" time }); describe('interrupted render timings', () => { @@ -349,17 +376,19 @@ describe('ProfileRoot', () => { , {unstable_isAsync: true}, ); + + // Simulate only enough time to render the first Yield renderer.unstable_flushThrough(['first']); expect(callback).toHaveBeenCalledTimes(0); - // Resume/restart render. + // Resume render for remaining children. renderer.unstable_flushAll(); // Verify that logged times include both durations above. expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBeGreaterThan(4); // "actual" time - expect(callback.mock.calls[0][3]).toBeGreaterThan(4); // "base" time + expect(callback.mock.calls[0][2]).toBe(20); // "actual" time + expect(callback.mock.calls[0][3]).toBe(20); // "base" time }); it('should resume/accumulate "actual" time after a higher priority interruption', () => { From 1c2ae58c971c687fc8fee798d2bfb81dcc9d94a5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 3 May 2018 21:20:16 -0700 Subject: [PATCH 13/72] Added test with replayFailedUnitOfWorkWithInvokeGuardedCallback enabled --- .../src/ReactFiberCompleteWork.js | 3 +- .../src/ReactFiberScheduler.js | 7 +- .../ReactProfileRoot-test.internal.js | 147 ++++++++++-------- 3 files changed, 87 insertions(+), 70 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 9496584200f..e58e7a1f87e 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -203,8 +203,7 @@ export default function( if (workInProgress.effectTag & CommitProfile) { // Stop render timer and store the elapsed time as stateNode. // The "commit" phase reads this value and passes it along to the callback. - // TODO (bvaughn) Do we need to do this? I don't think so? But we do need to handle frame resumes here. - workInProgress.stateNode += stopActualRenderTimer(workInProgress); + workInProgress.stateNode = stopActualRenderTimer(workInProgress); } } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 106e483c282..bb2fa86400b 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -373,7 +373,7 @@ export default function( } } - // TODO (bvaughn) Move this somewhere else? It isn't a hose effect. + // TODO (bvaughn) Move this somewhere else? It isn't a host effect. if (effectTag & CommitProfile) { commitProfileWork(nextEffect); } @@ -902,11 +902,6 @@ export default function( startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); - // TODO (bvaughn) Why do we begin and complete work again for even though we don't render it? - // This second begin/complete is overriding our base times to 0. - // Can we detect this case and not update or rerun the timer? - // Can we stop the time inside (like we do for bailouts)? - if (isBaseRenderTimerRunning()) { // Update "base" time if the render wasn't bailed out on. workInProgress.selfBaseTime = getElapsedBaseRenderTime(); diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 65925434fd6..b8fc891f095 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -13,18 +13,30 @@ let React; let ReactFeatureFlags; let ReactTestRenderer; +function loadModules({ + debugRenderPhaseSideEffects = false, + debugRenderPhaseSideEffectsForStrictMode = false, + enableProfileModeMetrics = true, + replayFailedUnitOfWorkWithInvokeGuardedCallback = false, +} = {}) { + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.debugRenderPhaseSideEffects = debugRenderPhaseSideEffects; + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = debugRenderPhaseSideEffectsForStrictMode; + ReactFeatureFlags.enableProfileModeMetrics = enableProfileModeMetrics; + ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback; + React = require('react'); + ReactTestRenderer = require('react-test-renderer'); +} + describe('ProfileRoot', () => { - [true, false].forEach(enabled => { + [true, false].forEach(enableProfileModeMetrics => { describe(`enableProfileModeMetrics feature flag ${ - enabled ? 'enabled' : 'disabled' + enableProfileModeMetrics ? 'enabled' : 'disabled' }`, () => { beforeEach(() => { jest.resetModules(); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableProfileModeMetrics = enabled; - React = require('react'); - ReactTestRenderer = require('react-test-renderer'); + loadModules({enableProfileModeMetrics}); }); // This will throw in production too, @@ -107,13 +119,7 @@ describe('ProfileRoot', () => { }; // Import after polyfill - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.debugRenderPhaseSideEffects = false; - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; - ReactFeatureFlags.enableProfileModeMetrics = true; - ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; - React = require('react'); - ReactTestRenderer = require('react-test-renderer'); + loadModules(); AdvanceTime = class extends React.Component { static defaultProps = { @@ -431,60 +437,77 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); }); - it('should resume/accumulate "actual" time after an ErrorBoundary re-render', () => { - const callback = jest.fn(); - - const ThrowsError = () => { - advanceTimeBy(10); - throw Error('expected error'); - }; - - class ErrorBoundary extends React.Component { - state = {error: null}; - componentDidCatch(error) { - this.setState({error}); - } - render() { - advanceTimeBy(2); - return this.state.error === null ? ( - this.props.children - ) : ( - + [true, false].forEach(replayFailedUnitOfWorkWithInvokeGuardedCallback => { + describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ + replayFailedUnitOfWorkWithInvokeGuardedCallback + ? 'enabled' + : 'disabled' + }`, () => { + beforeEach(() => { + jest.resetModules(); + + loadModules({replayFailedUnitOfWorkWithInvokeGuardedCallback}); + }); + + it('should resume/accumulate "actual" time after an ErrorBoundary re-render', () => { + const callback = jest.fn(); + + const ThrowsError = () => { + advanceTimeBy(10); + throw Error('expected error'); + }; + + class ErrorBoundary extends React.Component { + state = {error: null}; + componentDidCatch(error) { + this.setState({error}); + } + render() { + advanceTimeBy(2); + return this.state.error === null ? ( + this.props.children + ) : ( + + ); + } + } + + ReactTestRenderer.create( + + + + + + , ); - } - } - - ReactTestRenderer.create( - - - - - - , - ); - - expect(callback).toHaveBeenCalledTimes(2); - // Callbacks bubble (reverse order). - let [mountCall, updateCall] = callback.mock.calls; + expect(callback).toHaveBeenCalledTimes(2); - // TODO (bvaughn) Maybe add test with replayFailedUnitOfWorkWithInvokeGuardedCallback enabled. - // It would add 10ms to the first "actual" time below. + // Callbacks bubble (reverse order). + let [mountCall, updateCall] = callback.mock.calls; - // The initial mount only includes the ErrorBoundary (which takes 2ms) - // But it spends time rendering all of the failed subtree also. - expect(mountCall[1]).toBe('mount'); - // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) - expect(mountCall[2]).toBe(17); - // "base" time includes: 2 (ErrorBoundary) - expect(mountCall[3]).toBe(2); + // TODO (bvaughn) Maybe add test with replayFailedUnitOfWorkWithInvokeGuardedCallback enabled. + // It would add 10ms to the first "actual" time below. - // The update includes the ErrorBoundary and its fallback child - expect(updateCall[1]).toBe('update'); - // "actual" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) - expect(updateCall[2]).toBe(22); - // "base" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) - expect(updateCall[3]).toBe(22); + // The initial mount only includes the ErrorBoundary (which takes 2ms) + // But it spends time rendering all of the failed subtree also. + expect(mountCall[1]).toBe('mount'); + // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) + // If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed. + expect(mountCall[2]).toBe( + replayFailedUnitOfWorkWithInvokeGuardedCallback ? 27 : 17, + ); + // "base" time includes: 2 (ErrorBoundary) + expect(mountCall[3]).toBe(2); + + // The update includes the ErrorBoundary and its fallback child + expect(updateCall[1]).toBe('update'); + // "actual" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(updateCall[2]).toBe(22); + // "base" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(updateCall[3]).toBe(22); + }); + }); }); }); }); From f88b56275461918eaffe2610ee1ca78c4f5e97d4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 3 May 2018 21:32:21 -0700 Subject: [PATCH 14/72] Renamed a few tests (nits) --- .../ReactProfileRoot-test.internal.js | 20 +++++++++---------- .../ReactProfileRoot-test.internal.js.snap | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index b8fc891f095..fc656a1c1db 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -42,7 +42,7 @@ describe('ProfileRoot', () => { // This will throw in production too, // But the test is only interested in verifying the DEV error message. if (__DEV__) { - it('should warn about invalid mode', () => { + it('should warn if required params are missing', () => { expect(() => { ReactTestRenderer.create(); }).toThrow( @@ -51,7 +51,7 @@ describe('ProfileRoot', () => { }); } - it('should support an empty mode', () => { + it('should support an empty ProfileRoot (with no children)', () => { expect( ReactTestRenderer.create( {}} />, @@ -140,7 +140,7 @@ describe('ProfileRoot', () => { delete global.performance; }); - it('does not log render times until commit', () => { + it('does not invoke the callback until the commit phase', () => { const callback = jest.fn(); const Yield = ({value}) => { @@ -165,7 +165,7 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); }); - it('logs render times for mount and update', () => { + it('logs render times for both mount and update', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( @@ -280,7 +280,7 @@ describe('ProfileRoot', () => { expect(call[3]).toBe(5); // "base" time }); - it('does not call callbacks for descendents of sCU false', () => { + it('does not call callbacks after update for descendents of sCU false', () => { const callback = jest.fn(); let instance; @@ -330,7 +330,7 @@ describe('ProfileRoot', () => { expect(callback.mock.calls[1][0]).toBe('outer'); }); - it('records a decrease in "actual" time and no change in "base" time when sCU memoization is used', () => { + it('decreases "actual" time but not "base" time when sCU prevents an update', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( @@ -364,8 +364,8 @@ describe('ProfileRoot', () => { expect(updateCall[3]).toBe(20); // "base" time }); - describe('interrupted render timings', () => { - it('should resume/accumulate "actual" time after a scheduling interruptions', () => { + describe('interruptions', () => { + it('should accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); const Yield = ({value}) => { @@ -397,7 +397,7 @@ describe('ProfileRoot', () => { expect(callback.mock.calls[0][3]).toBe(20); // "base" time }); - it('should resume/accumulate "actual" time after a higher priority interruption', () => { + it('should accumulate "actual" time after a higher priority interruption', () => { const callback = jest.fn(); const Yield = ({renderTime, value}) => { @@ -449,7 +449,7 @@ describe('ProfileRoot', () => { loadModules({replayFailedUnitOfWorkWithInvokeGuardedCallback}); }); - it('should resume/accumulate "actual" time after an ErrorBoundary re-render', () => { + it('should accumulate "actual" time after an ErrorBoundary re-render', () => { const callback = jest.fn(); const ThrowsError = () => { diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap index 2d15220464f..60ea500ca89 100644 --- a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap +++ b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap @@ -12,9 +12,9 @@ exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should rende
`; -exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty mode 1`] = `null`; +exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty ProfileRoot (with no children) 1`] = `null`; -exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty mode 2`] = `
`; +exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty ProfileRoot (with no children) 2`] = `
`; exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support nested ProfileModes 1`] = ` Array [ @@ -44,9 +44,9 @@ exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should render
`; -exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty mode 1`] = `null`; +exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty ProfileRoot (with no children) 1`] = `null`; -exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty mode 2`] = `
`; +exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty ProfileRoot (with no children) 2`] = `
`; exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support nested ProfileModes 1`] = ` Array [ From 0942eb8a737f64ffacd4e739d66d57e4979ca39c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 07:45:44 -0700 Subject: [PATCH 15/72] Cleaned up snapshot tests --- .../ReactProfileRoot-test.internal.js | 131 +++++++++--------- .../ReactProfileRoot-test.internal.js.snap | 48 +++---- 2 files changed, 92 insertions(+), 87 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index fc656a1c1db..8364fe649ed 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -29,79 +29,84 @@ function loadModules({ } describe('ProfileRoot', () => { - [true, false].forEach(enableProfileModeMetrics => { - describe(`enableProfileModeMetrics feature flag ${ - enableProfileModeMetrics ? 'enabled' : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); - - loadModules({enableProfileModeMetrics}); - }); + describe('works in profiling and non-profiling bundles', () => { + [true, false].forEach(enableProfileModeMetrics => { + describe(`enableProfileModeMetrics ${ + enableProfileModeMetrics ? 'enabled' : 'disabled' + }`, () => { + beforeEach(() => { + jest.resetModules(); + + loadModules({enableProfileModeMetrics}); + }); - // This will throw in production too, - // But the test is only interested in verifying the DEV error message. - if (__DEV__) { - it('should warn if required params are missing', () => { - expect(() => { - ReactTestRenderer.create(); - }).toThrow( - 'ProfileMode must specify a label string and callback function', - ); + // This will throw in production too, + // But the test is only interested in verifying the DEV error message. + if (__DEV__) { + it('should warn if required params are missing', () => { + expect(() => { + ReactTestRenderer.create(); + }).toThrow( + 'ProfileMode must specify a label string and callback function', + ); + }); + } + + it('should support an empty ProfileRoot (with no children)', () => { + // As root + expect( + ReactTestRenderer.create( + {}} />, + ).toJSON(), + ).toMatchSnapshot(); + + // As non-root + expect( + ReactTestRenderer.create( +
+ {}} /> +
, + ).toJSON(), + ).toMatchSnapshot(); }); - } - it('should support an empty ProfileRoot (with no children)', () => { - expect( - ReactTestRenderer.create( - {}} />, - ).toJSON(), - ).toMatchSnapshot(); - expect( - ReactTestRenderer.create( + it('should render children', () => { + const FunctionalComponent = ({label}) => {label}; + const renderer = ReactTestRenderer.create(
- {}} /> + outside span + {}}> + inside span + +
, - ).toJSON(), - ).toMatchSnapshot(); - }); - - it('should render children', () => { - const ProfiledComponent = ({name}) => {name}; - const renderer = ReactTestRenderer.create( -
- Hi - {}}> - there - - -
, - ); - expect(renderer.toJSON()).toMatchSnapshot(); - }); + ); + expect(renderer.toJSON()).toMatchSnapshot(); + }); - it('should support nested ProfileModes', () => { - const ProfiledComponent = ({name}) =>
Hi, {name}
; - class ExtraProfiledComponent extends React.Component { - render() { - return Hi, {this.props.name}; + it('should support nested ProfileModes', () => { + const FunctionalComponent = ({label}) =>
{label}
; + class ClassComponent extends React.Component { + render() { + return {this.props.label}; + } } - } - const renderer = ReactTestRenderer.create( - {}}> - - {}}> - - Now with extra profile strength! - - , - ); - expect(renderer.toJSON()).toMatchSnapshot(); + const renderer = ReactTestRenderer.create( + {}}> + + {}}> + + inner span + + , + ); + expect(renderer.toJSON()).toMatchSnapshot(); + }); }); }); }); - describe('render timings', () => { + describe('records meaningful timing information', () => { let AdvanceTime; let advanceTimeBy; @@ -364,7 +369,7 @@ describe('ProfileRoot', () => { expect(updateCall[3]).toBe(20); // "base" time }); - describe('interruptions', () => { + describe('handles interruptions', () => { it('should accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap index 60ea500ca89..0d26e58d2f9 100644 --- a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap +++ b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap @@ -1,65 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should render children 1`] = ` +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should render children 1`] = `
- Hi - there + outside span - ProfileMode + inside span + + + functional component
`; -exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty ProfileRoot (with no children) 1`] = `null`; +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty ProfileRoot (with no children) 1`] = `null`; -exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support an empty ProfileRoot (with no children) 2`] = `
`; +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty ProfileRoot (with no children) 2`] = `
`; -exports[`ProfileRoot enableProfileModeMetrics feature flag disabled should support nested ProfileModes 1`] = ` +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support nested ProfileModes 1`] = ` Array [
- Hi, - Brian + outer functional component
, - Hi, - Brian + inner class component , - Now with extra profile strength! + inner span , ] `; -exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should render children 1`] = ` +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should render children 1`] = `
- Hi - there + outside span + + + inside span - ProfileMode + functional component
`; -exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty ProfileRoot (with no children) 1`] = `null`; +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty ProfileRoot (with no children) 1`] = `null`; -exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support an empty ProfileRoot (with no children) 2`] = `
`; +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty ProfileRoot (with no children) 2`] = `
`; -exports[`ProfileRoot enableProfileModeMetrics feature flag enabled should support nested ProfileModes 1`] = ` +exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support nested ProfileModes 1`] = ` Array [
- Hi, - Brian + outer functional component
, - Hi, - Brian + inner class component , - Now with extra profile strength! + inner span , ] `; From 2e1a4e362c2e0150807af5be7c18aca352a1d540 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 09:10:27 -0700 Subject: [PATCH 16/72] Don't include time between frames in actual time measurements --- packages/react-reconciler/src/ReactFiber.js | 5 +- .../src/ReactFiberBeginWork.js | 7 ++- .../src/ReactFiberCommitWork.js | 6 ++- .../src/ReactFiberCompleteWork.js | 6 +-- .../src/ReactFiberScheduler.js | 20 +++++-- .../src/ReactFiberUnwindWork.js | 4 +- .../react-reconciler/src/ReactProfileTimer.js | 42 ++++++++++++--- .../ReactProfileRoot-test.internal.js | 54 ++++++++++++++++--- 8 files changed, 112 insertions(+), 32 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index d267aadd739..be67d53d11c 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -486,7 +486,10 @@ export function createFiberFromProfileMode( const fiber = createFiber(ProfileRoot, pendingProps, key, mode | ProfileMode); fiber.type = REACT_PROFILE_MODE_TYPE; fiber.expirationTime = expirationTime; - fiber.stateNode = 0; + fiber.stateNode = { + duration: 0, + startTime: 0, + }; return fiber; } diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index d3634595d38..6376818d363 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -69,7 +69,10 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import {stopBaseRenderTimer, startActualRenderTimer} from './ReactProfileTimer'; +import { + stopBaseRenderTimer, + markActualRenderTimeStarted, +} from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -222,7 +225,7 @@ export default function( function updateProfileRoot(current, workInProgress) { if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue - startActualRenderTimer(workInProgress); + markActualRenderTimeStarted(workInProgress); // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 64278332954..8dd2df888d6 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -775,13 +775,15 @@ export default function( null, finishedWork.pendingProps.label, finishedWork.alternate === null ? 'mount' : 'update', - finishedWork.stateNode, + 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 = 0; + finishedWork.stateNode.duration = 0; + finishedWork.stateNode.startTime = 0; break; } default: { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index e58e7a1f87e..8d9230e035c 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -47,7 +47,7 @@ import { Ref, Update, } from 'shared/ReactTypeOfSideEffect'; -import {stopActualRenderTimer} from './ReactProfileTimer'; +import {recordActualRenderTime} from './ReactProfileTimer'; import invariant from 'fbjs/lib/invariant'; import {reconcileChildFibers} from './ReactChildFiber'; @@ -201,9 +201,7 @@ export default function( function updateProfileRoot(workInProgress: Fiber) { if (enableProfileModeMetrics) { if (workInProgress.effectTag & CommitProfile) { - // Stop render timer and store the elapsed time as stateNode. - // The "commit" phase reads this value and passes it along to the callback. - workInProgress.stateNode = stopActualRenderTimer(workInProgress); + recordActualRenderTime(workInProgress); } } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index bb2fa86400b..c662f51cc1c 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -49,6 +49,8 @@ import { import { getElapsedBaseRenderTime, isBaseRenderTimerRunning, + pauseActualRenderTimer, + startActualRenderTimer, startBaseRenderTimer, stopBaseRenderTimer, } from './ReactProfileTimer'; @@ -373,11 +375,6 @@ export default function( } } - // TODO (bvaughn) Move this somewhere else? It isn't a host effect. - if (effectTag & CommitProfile) { - commitProfileWork(nextEffect); - } - // The following switch statement is only concerned about placement, // updates, and deletions. To avoid needing to add a case for every // possible bitmap value, we remove the secondary effects from the @@ -472,6 +469,10 @@ export default function( commitAttachRef(nextEffect); } + if (effectTag & CommitProfile) { + commitProfileWork(nextEffect); + } + const next = nextEffect.nextEffect; // Ensure that we clean these up so that we don't accidentally keep them. // I'm not actually sure this matters because we can't reset firstEffect @@ -1553,6 +1554,10 @@ export default function( ) { deadline = dl; + if (enableProfileModeMetrics) { + startActualRenderTimer(); + } + // Keep working on roots until there's no more work, or until the we reach // the deadline. findHighestPriorityRoot(); @@ -1748,6 +1753,11 @@ export default function( // during a timeout. This path is only hit for non-expired work. return false; } + + if (enableProfileModeMetrics) { + pauseActualRenderTimer(); + } + deadlineDidExpire = true; return true; } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index dcb17ff99f7..2af89f51573 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -196,9 +196,7 @@ export default function( workInProgress.treeBaseTime = treeBaseTime; } - if (workInProgress.mode & ProfileMode) { - // TODO (bvaughn) Maybe store info on ProfileMode stateNodes about unwind time? - } + // TODO (bvaughn, timing) Do we need to pause and store any info here? } switch (workInProgress.tag) { diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index b55b04f5135..e0cd1045ce4 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -36,21 +36,47 @@ if (hasNativePerformanceNow) { const {createCursor, push, pop} = ReactFiberStack(); -let renderTimeStackCursor: StackCursor = createCursor(0); +let renderTimeStackCursor: StackCursor = createCursor(null); -export function startActualRenderTimer(fiber: Fiber): void { - push(renderTimeStackCursor, now(), fiber); -} +export function markActualRenderTimeStarted(fiber: Fiber): void { + fiber.stateNode.startTime = now(); -export function stopActualRenderTimer(fiber: Fiber): number { - const startTime = renderTimeStackCursor.current; + push(renderTimeStackCursor, fiber, fiber); +} +export function recordActualRenderTime(fiber: Fiber): void { pop(renderTimeStackCursor, fiber); - return now() - startTime; + // Stop render timer and store the elapsed time as stateNode. + // The "commit" phase reads this value and passes it along to the callback. + fiber.stateNode.duration = now() - fiber.stateNode.startTime; } -// TODO (bvaughn) Pause actual timer +let timerPausedAt: number = 0; + +export function startActualRenderTimer(): void { + if (timerPausedAt > 0) { + const elapsed = now() - timerPausedAt; + + let stateNodes: Array = []; + while (renderTimeStackCursor.current !== null) { + const fiber = renderTimeStackCursor.current; + stateNodes.push(fiber); + pop(renderTimeStackCursor, fiber); + } + while (stateNodes.length > 0) { + const fiber = stateNodes.pop(); + fiber.stateNode.startTime += elapsed; + push(renderTimeStackCursor, fiber, fiber); + } + + timerPausedAt = 0; + } +} + +export function pauseActualRenderTimer(): void { + timerPausedAt = now(); +} /** * The "base" render time is the duration of the “begin” phase of work for a particular fiber. diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 8364fe649ed..153d701ea15 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -402,6 +402,43 @@ describe('ProfileRoot', () => { expect(callback.mock.calls[0][3]).toBe(20); // "base" time }); + it('should not include time between frames', () => { + const callback = jest.fn(); + + const Yield = ({renderTime, value}) => { + advanceTimeBy(renderTime); + renderer.unstable_yield(value); + return null; + }; + + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isAsync: true}, + ); + + // Render partially, but don't finish. + // This partial render should take 5ms of simulated time. + renderer.unstable_flushThrough(['first']); + + expect(callback).toHaveBeenCalledTimes(0); + + // Simulate time moving forward while frame is paused. + advanceTimeBy(20); + + // Flush the remaninig work, + // Which should take an additional 10ms of simulated time. + renderer.unstable_flushAll(); + + // Verify that the "actual" time includes both durations above, + // But not the time that elapsed between frames. + expect(callback).toHaveBeenCalledTimes(1); + expect(callback.mock.calls[0][2]).toBe(15); // "actual" time + expect(callback.mock.calls[0][3]).toBe(15); // "base" time + }); + it('should accumulate "actual" time after a higher priority interruption', () => { const callback = jest.fn(); @@ -411,7 +448,6 @@ describe('ProfileRoot', () => { return null; }; - // Render partially, but don't complete const renderer = ReactTestRenderer.create( @@ -419,11 +455,15 @@ describe('ProfileRoot', () => { , {unstable_isAsync: true}, ); + + // Render partially, but don't finish. + // This partial render should take 10ms of simulated time. renderer.unstable_flushThrough(['first']); expect(callback).toHaveBeenCalledTimes(0); - // Interrupt with higher priority work + // Interrupt with higher priority work. + // The interrupted work simulates an additional 5ms of time. renderer.unstable_flushSync(() => { renderer.update( @@ -432,14 +472,17 @@ describe('ProfileRoot', () => { ); }); - // Verify that logged times include both durations above. + // Verify that the "actual" time includes both durations above, + // And the "base" time includes only the final rendered tree times. expect(callback).toHaveBeenCalledTimes(1); expect(callback.mock.calls[0][2]).toBe(15); // "actual" time - expect(callback.mock.calls[0][3]).toBe(15); // "base" time + expect(callback.mock.calls[0][3]).toBe(5); // "base" time // Verify no more unexpected callbacks from low priority work renderer.unstable_flushAll(); expect(callback).toHaveBeenCalledTimes(1); + + // TODO (bvaughn, timing) Add check the queue is empty }); [true, false].forEach(replayFailedUnitOfWorkWithInvokeGuardedCallback => { @@ -491,9 +534,6 @@ describe('ProfileRoot', () => { // Callbacks bubble (reverse order). let [mountCall, updateCall] = callback.mock.calls; - // TODO (bvaughn) Maybe add test with replayFailedUnitOfWorkWithInvokeGuardedCallback enabled. - // It would add 10ms to the first "actual" time below. - // The initial mount only includes the ErrorBoundary (which takes 2ms) // But it spends time rendering all of the failed subtree also. expect(mountCall[1]).toBe('mount'); From 004940c990aa2ac7407cc0f37ab3a0f6597c8238 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 10:26:54 -0700 Subject: [PATCH 17/72] Added addl dev checks for emtpy stack, and improved pause/resume behavior --- .../src/ReactFiberScheduler.js | 34 +++++++++++++++---- .../src/ReactFiberUnwindWork.js | 20 +++-------- .../react-reconciler/src/ReactProfileTimer.js | 31 +++++++++++------ .../ReactProfileRoot-test.internal.js | 2 +- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index c662f51cc1c..740f2e68563 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -47,10 +47,12 @@ import { warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; import { + checkActualRenderTimeStackEmpty, getElapsedBaseRenderTime, + isActualRenderTimerPaused, isBaseRenderTimerRunning, pauseActualRenderTimer, - startActualRenderTimer, + resumeActualRenderTimer, startBaseRenderTimer, stopBaseRenderTimer, } from './ReactProfileTimer'; @@ -667,6 +669,12 @@ export default function( } } + if (__DEV__) { + if (enableProfileModeMetrics) { + checkActualRenderTimeStackEmpty(); + } + } + isCommitting = false; isWorking = false; stopCommitLifeCyclesTimer(); @@ -947,6 +955,14 @@ export default function( while (nextUnitOfWork !== null && !shouldYield()) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } + + if (enableProfileModeMetrics) { + // If we didn't finish, pause the "actual" render timer. + // We'll restart it when we resume work. + if (nextUnitOfWork !== null) { + pauseActualRenderTimer(); + } + } } } @@ -1555,7 +1571,9 @@ export default function( deadline = dl; if (enableProfileModeMetrics) { - startActualRenderTimer(); + if (isActualRenderTimerPaused()) { + resumeActualRenderTimer(); + } } // Keep working on roots until there's no more work, or until the we reach @@ -1707,6 +1725,14 @@ export default function( // There's no time left. Mark this root as complete. We'll come // back and commit it later. root.finishedWork = finishedWork; + + if (enableProfileModeMetrics) { + // If we didn't finish, pause the "actual" render timer. + // We'll restart it when we resume work. + if (nextUnitOfWork !== null) { + pauseActualRenderTimer(); + } + } } } } @@ -1754,10 +1780,6 @@ export default function( return false; } - if (enableProfileModeMetrics) { - pauseActualRenderTimer(); - } - deadlineDidExpire = true; return true; } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 2af89f51573..4151cf92265 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -36,7 +36,6 @@ import { NoEffect, ShouldCapture, } from 'shared/ReactTypeOfSideEffect'; -import {ProfileMode} from './ReactTypeOfMode'; import { enableGetDerivedStateFromCatch, @@ -184,21 +183,6 @@ export default function( } function unwindWork(workInProgress: Fiber) { - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // Bubble up "base" render times if we're within a ProfileRoot - let treeBaseTime = workInProgress.selfBaseTime; - let child = workInProgress.child; - while (child !== null) { - treeBaseTime += child.treeBaseTime; - child = child.sibling; - } - workInProgress.treeBaseTime = treeBaseTime; - } - - // TODO (bvaughn, timing) Do we need to pause and store any info here? - } - switch (workInProgress.tag) { case ClassComponent: { popLegacyContextProvider(workInProgress); @@ -235,6 +219,10 @@ export default function( } function unwindInterruptedWork(interruptedWork: Fiber) { + if (enableProfileModeMetrics) { + // TODO (bvaughn, timing) Do we need to pause and store any info here? + } + switch (interruptedWork.tag) { case ClassComponent: { popLegacyContextProvider(interruptedWork); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index e0cd1045ce4..8e65e22ec79 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -34,40 +34,49 @@ if (hasNativePerformanceNow) { * It is paused (and accumulated) in the event of an interruption or an aborted render. */ -const {createCursor, push, pop} = ReactFiberStack(); +const {checkThatStackIsEmpty, createCursor, push, pop} = ReactFiberStack(); -let renderTimeStackCursor: StackCursor = createCursor(null); +let stackCursor: StackCursor = createCursor(null); +let timerPausedAt: number = 0; + +export function checkActualRenderTimeStackEmpty(): void { + if (__DEV__) { + checkThatStackIsEmpty(); + } +} export function markActualRenderTimeStarted(fiber: Fiber): void { fiber.stateNode.startTime = now(); - push(renderTimeStackCursor, fiber, fiber); + push(stackCursor, fiber, fiber); +} + +export function isActualRenderTimerPaused(): boolean { + return timerPausedAt > 0; } export function recordActualRenderTime(fiber: Fiber): void { - pop(renderTimeStackCursor, fiber); + pop(stackCursor, fiber); // Stop render timer and store the elapsed time as stateNode. // The "commit" phase reads this value and passes it along to the callback. fiber.stateNode.duration = now() - fiber.stateNode.startTime; } -let timerPausedAt: number = 0; - -export function startActualRenderTimer(): void { +export function resumeActualRenderTimer(): void { if (timerPausedAt > 0) { const elapsed = now() - timerPausedAt; let stateNodes: Array = []; - while (renderTimeStackCursor.current !== null) { - const fiber = renderTimeStackCursor.current; + while (stackCursor.current !== null) { + const fiber = stackCursor.current; stateNodes.push(fiber); - pop(renderTimeStackCursor, fiber); + pop(stackCursor, fiber); } while (stateNodes.length > 0) { const fiber = stateNodes.pop(); fiber.stateNode.startTime += elapsed; - push(renderTimeStackCursor, fiber, fiber); + push(stackCursor, fiber, fiber); } timerPausedAt = 0; diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 153d701ea15..9364b75ad32 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -432,7 +432,7 @@ describe('ProfileRoot', () => { // Which should take an additional 10ms of simulated time. renderer.unstable_flushAll(); - // Verify that the "actual" time includes both durations above, + // Verify that the "actual" time includes all work times, // But not the time that elapsed between frames. expect(callback).toHaveBeenCalledTimes(1); expect(callback.mock.calls[0][2]).toBe(15); // "actual" time From 9983cc05a9e3d2549bfcc5382debcca7c04515f6 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 10:33:58 -0700 Subject: [PATCH 18/72] Added label prop to ProfileRoot component name --- packages/shared/getComponentName.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index 17fb8082c89..865136f24a2 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -38,7 +38,7 @@ function getComponentName(fiber: Fiber): string | null { case REACT_PORTAL_TYPE: return 'ReactPortal'; case REACT_PROFILE_MODE_TYPE: - return 'ProfileMode'; + return `ProfileMode(${fiber.pendingProps.label})`; case REACT_RETURN_TYPE: return 'ReactReturn'; case REACT_STRICT_MODE_TYPE: From 91af221a5145cbd6645df20e99704732a662c384 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 10:55:08 -0700 Subject: [PATCH 19/72] Added tests for shallow renderer and toTree/findByType --- .../src/server/ReactPartialRenderer.js | 4 +-- packages/react-is/src/ReactIs.js | 10 +++---- .../react-is/src/__tests__/ReactIs-test.js | 12 +++++++++ packages/react-reconciler/src/ReactFiber.js | 6 ++--- .../src/ReactTestRenderer.js | 3 +++ .../__tests__/ReactShallowRenderer-test.js | 26 +++++++++++++++++++ .../ReactTestRendererTraversal-test.js | 4 ++- packages/react/src/React.js | 4 +-- packages/shared/ReactSymbols.js | 4 +-- packages/shared/getComponentName.js | 4 +-- packages/shared/isValidElementType.js | 4 +-- 11 files changed, 62 insertions(+), 19 deletions(-) diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index f46bdea23d2..4c62a352145 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -34,7 +34,7 @@ import { REACT_CALL_TYPE, REACT_RETURN_TYPE, REACT_PORTAL_TYPE, - REACT_PROFILE_MODE_TYPE, + REACT_PROFILE_ROOT_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, } from 'shared/ReactSymbols'; @@ -812,7 +812,7 @@ class ReactDOMServerRenderer { switch (elementType) { case REACT_STRICT_MODE_TYPE: case REACT_ASYNC_MODE_TYPE: - case REACT_PROFILE_MODE_TYPE: + case REACT_PROFILE_ROOT_TYPE: case REACT_FRAGMENT_TYPE: { const nextChildren = toArray( ((nextChild: any): ReactElement).props.children, diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js index e1f3133bfea..06221bc104a 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -16,7 +16,7 @@ import { REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, REACT_PORTAL_TYPE, - REACT_PROFILE_MODE_TYPE, + REACT_PROFILE_ROOT_TYPE, REACT_PROVIDER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -33,7 +33,7 @@ export function typeOf(object: any) { switch (type) { case REACT_ASYNC_MODE_TYPE: case REACT_FRAGMENT_TYPE: - case REACT_PROFILE_MODE_TYPE: + case REACT_PROFILE_ROOT_TYPE: case REACT_STRICT_MODE_TYPE: return type; default: @@ -62,7 +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 ProfileMode = REACT_PROFILE_MODE_TYPE; +export const ProfileRoot = REACT_PROFILE_ROOT_TYPE; export const Portal = REACT_PORTAL_TYPE; export const StrictMode = REACT_STRICT_MODE_TYPE; @@ -90,8 +90,8 @@ export function isForwardRef(object: any) { export function isFragment(object: any) { return typeOf(object) === REACT_FRAGMENT_TYPE; } -export function isProfileMode(object: any) { - return typeOf(object) === REACT_PROFILE_MODE_TYPE; +export function isProfileRoot(object: any) { + return typeOf(object) === REACT_PROFILE_ROOT_TYPE; } export function isPortal(object: any) { return typeOf(object) === REACT_PORTAL_TYPE; diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js index e4ce5074b1a..1c7835df454 100644 --- a/packages/react-is/src/__tests__/ReactIs-test.js +++ b/packages/react-is/src/__tests__/ReactIs-test.js @@ -145,4 +145,16 @@ describe('ReactIs', () => { expect(ReactIs.isStrictMode()).toBe(false); expect(ReactIs.isStrictMode(
)).toBe(false); }); + + it('should identify profile root', () => { + expect(ReactIs.typeOf()).toBe( + ReactIs.ProfileRoot, + ); + expect(ReactIs.isProfileRoot()).toBe(true); + expect(ReactIs.isProfileRoot({type: ReactIs.unstable_ProfileRoot})).toBe( + false, + ); + expect(ReactIs.isProfileRoot()).toBe(false); + expect(ReactIs.isProfileRoot(
)).toBe(false); + }); }); diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index be67d53d11c..74bc5a5fe7e 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -43,7 +43,7 @@ import { REACT_RETURN_TYPE, REACT_CALL_TYPE, REACT_STRICT_MODE_TYPE, - REACT_PROFILE_MODE_TYPE, + REACT_PROFILE_ROOT_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, REACT_ASYNC_MODE_TYPE, @@ -361,7 +361,7 @@ export function createFiberFromElement( fiberTag = Mode; mode |= StrictMode; break; - case REACT_PROFILE_MODE_TYPE: + case REACT_PROFILE_ROOT_TYPE: return createFiberFromProfileMode( pendingProps, mode, @@ -484,7 +484,7 @@ export function createFiberFromProfileMode( } const fiber = createFiber(ProfileRoot, pendingProps, key, mode | ProfileMode); - fiber.type = REACT_PROFILE_MODE_TYPE; + fiber.type = REACT_PROFILE_ROOT_TYPE; fiber.expirationTime = expirationTime; fiber.stateNode = { duration: 0, diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index d3ef7aff4fc..c556fa91de1 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -27,6 +27,7 @@ import { ContextProvider, Mode, ForwardRef, + ProfileRoot, } from 'shared/ReactTypeOfWork'; import invariant from 'fbjs/lib/invariant'; @@ -383,6 +384,7 @@ function toTree(node: ?Fiber) { case ContextProvider: case ContextConsumer: case Mode: + case ProfileRoot: case ForwardRef: return childrenToTree(node.child); default: @@ -486,6 +488,7 @@ class ReactTestInstance { case ContextProvider: case ContextConsumer: case Mode: + case ProfileRoot: descend = true; break; default: diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js index b20f316d9ed..d955fba3904 100644 --- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js @@ -230,6 +230,32 @@ describe('ReactShallowRenderer', () => { ]); }); + it('should handle ProfileRoot', () => { + class SomeComponent extends React.Component { + render() { + return ( + {}}> +
+ + +
+
+ ); + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + + expect(result.type).toBe(React.unstable_ProfileRoot); + expect(result.props.children).toEqual( +
+ + +
, + ); + }); + it('should enable shouldComponentUpdate to prevent a re-render', () => { let renderCounter = 0; class SimpleComponent extends React.Component { diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index e6f3909b0b4..8583d045471 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -37,7 +37,9 @@ describe('ReactTestRendererTraversal', () => { - + {}}> + + ); diff --git a/packages/react/src/React.js b/packages/react/src/React.js index ddc373ea62d..e70646a4779 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -10,7 +10,7 @@ import ReactVersion from 'shared/ReactVersion'; import { REACT_ASYNC_MODE_TYPE, REACT_FRAGMENT_TYPE, - REACT_PROFILE_MODE_TYPE, + REACT_PROFILE_ROOT_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -52,7 +52,7 @@ const React = { Fragment: REACT_FRAGMENT_TYPE, StrictMode: REACT_STRICT_MODE_TYPE, unstable_AsyncMode: REACT_ASYNC_MODE_TYPE, - unstable_ProfileRoot: REACT_PROFILE_MODE_TYPE, + unstable_ProfileRoot: REACT_PROFILE_ROOT_TYPE, createElement: __DEV__ ? createElementWithValidation : createElement, cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index d9d072c72c8..01c17521229 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -27,8 +27,8 @@ export const REACT_FRAGMENT_TYPE = hasSymbol export const REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc; -export const REACT_PROFILE_MODE_TYPE = hasSymbol - ? Symbol.for('react.profile_mode') +export const REACT_PROFILE_ROOT_TYPE = hasSymbol + ? Symbol.for('react.profile_root') : 0xeacc; export const REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index 865136f24a2..6b76da2db53 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -16,7 +16,7 @@ import { REACT_FRAGMENT_TYPE, REACT_RETURN_TYPE, REACT_PORTAL_TYPE, - REACT_PROFILE_MODE_TYPE, + REACT_PROFILE_ROOT_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -37,7 +37,7 @@ function getComponentName(fiber: Fiber): string | null { return 'ReactFragment'; case REACT_PORTAL_TYPE: return 'ReactPortal'; - case REACT_PROFILE_MODE_TYPE: + case REACT_PROFILE_ROOT_TYPE: return `ProfileMode(${fiber.pendingProps.label})`; case REACT_RETURN_TYPE: return 'ReactReturn'; diff --git a/packages/shared/isValidElementType.js b/packages/shared/isValidElementType.js index a04dbc07103..062479d04e3 100644 --- a/packages/shared/isValidElementType.js +++ b/packages/shared/isValidElementType.js @@ -12,7 +12,7 @@ import { REACT_CONTEXT_TYPE, REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, - REACT_PROFILE_MODE_TYPE, + REACT_PROFILE_ROOT_TYPE, REACT_PROVIDER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -24,7 +24,7 @@ export default function isValidElementType(type: mixed) { // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. type === REACT_FRAGMENT_TYPE || type === REACT_ASYNC_MODE_TYPE || - type === REACT_PROFILE_MODE_TYPE || + type === REACT_PROFILE_ROOT_TYPE || type === REACT_STRICT_MODE_TYPE || (typeof type === 'object' && type !== null && From 9f0c4d47804d4eaaafda0b6c2d6ad101ea414694 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 11:20:11 -0700 Subject: [PATCH 20/72] Improved pause/resume time calculation to not require traversing stack --- .../react-reconciler/src/ReactProfileTimer.js | 21 ++++------------ .../ReactProfileRoot-test.internal.js | 25 +++++++++++++------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 8e65e22ec79..7f6fa8adbcc 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -38,6 +38,7 @@ const {checkThatStackIsEmpty, createCursor, push, pop} = ReactFiberStack(); let stackCursor: StackCursor = createCursor(null); let timerPausedAt: number = 0; +let totalElapsedPauseTime: number = 0; export function checkActualRenderTimeStackEmpty(): void { if (__DEV__) { @@ -46,7 +47,7 @@ export function checkActualRenderTimeStackEmpty(): void { } export function markActualRenderTimeStarted(fiber: Fiber): void { - fiber.stateNode.startTime = now(); + fiber.stateNode.startTime = now() - totalElapsedPauseTime; push(stackCursor, fiber, fiber); } @@ -60,25 +61,13 @@ export function recordActualRenderTime(fiber: Fiber): void { // Stop render timer and store the elapsed time as stateNode. // The "commit" phase reads this value and passes it along to the callback. - fiber.stateNode.duration = now() - fiber.stateNode.startTime; + fiber.stateNode.duration = + now() - fiber.stateNode.startTime - totalElapsedPauseTime; } export function resumeActualRenderTimer(): void { if (timerPausedAt > 0) { - const elapsed = now() - timerPausedAt; - - let stateNodes: Array = []; - while (stackCursor.current !== null) { - const fiber = stackCursor.current; - stateNodes.push(fiber); - pop(stackCursor, fiber); - } - while (stateNodes.length > 0) { - const fiber = stateNodes.pop(); - fiber.stateNode.startTime += elapsed; - push(stackCursor, fiber, fiber); - } - + totalElapsedPauseTime += now() - timerPausedAt; timerPausedAt = 0; } } diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 9364b75ad32..10b78d9a0f3 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -412,9 +412,12 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + + + + , {unstable_isAsync: true}, ); @@ -426,17 +429,24 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(20); + advanceTimeBy(50); // Flush the remaninig work, // Which should take an additional 10ms of simulated time. renderer.unstable_flushAll(); + expect(callback).toHaveBeenCalledTimes(2); + + const [innerCall, outerCall] = callback.mock.calls; + // Verify that the "actual" time includes all work times, // But not the time that elapsed between frames. - expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBe(15); // "actual" time - expect(callback.mock.calls[0][3]).toBe(15); // "base" time + expect(innerCall[0]).toBe('inner'); + expect(innerCall[2]).toBe(20); // "actual" time + expect(innerCall[3]).toBe(20); // "base" time + expect(outerCall[0]).toBe('outer'); + expect(outerCall[2]).toBe(35); // "actual" time + expect(outerCall[3]).toBe(35); // "base" time }); it('should accumulate "actual" time after a higher priority interruption', () => { @@ -462,6 +472,9 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(0); + // Simulate time moving forward while frame is paused. + advanceTimeBy(30); + // Interrupt with higher priority work. // The interrupted work simulates an additional 5ms of time. renderer.unstable_flushSync(() => { @@ -481,8 +494,6 @@ describe('ProfileRoot', () => { // Verify no more unexpected callbacks from low priority work renderer.unstable_flushAll(); expect(callback).toHaveBeenCalledTimes(1); - - // TODO (bvaughn, timing) Add check the queue is empty }); [true, false].forEach(replayFailedUnitOfWorkWithInvokeGuardedCallback => { From 643399b97168f437925947531cee3170b052ce6f Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 11:30:21 -0700 Subject: [PATCH 21/72] Pop ProfileRoot from timer stack on interruption --- packages/react-reconciler/src/ReactFiberUnwindWork.js | 7 +++++-- packages/react-reconciler/src/ReactProfileTimer.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 4151cf92265..f848d0d1fa7 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -31,12 +31,13 @@ import { ContextProvider, } from 'shared/ReactTypeOfWork'; import { + CommitProfile, DidCapture, Incomplete, NoEffect, ShouldCapture, } from 'shared/ReactTypeOfSideEffect'; - +import {recordActualRenderTime} from './ReactProfileTimer'; import { enableGetDerivedStateFromCatch, enableProfileModeMetrics, @@ -220,7 +221,9 @@ export default function( function unwindInterruptedWork(interruptedWork: Fiber) { if (enableProfileModeMetrics) { - // TODO (bvaughn, timing) Do we need to pause and store any info here? + if (interruptedWork.effectTag & CommitProfile) { + recordActualRenderTime(interruptedWork); + } } switch (interruptedWork.tag) { diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 7f6fa8adbcc..6ccd5264e2b 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -61,7 +61,7 @@ export function recordActualRenderTime(fiber: Fiber): void { // Stop render timer and store the elapsed time as stateNode. // The "commit" phase reads this value and passes it along to the callback. - fiber.stateNode.duration = + fiber.stateNode.duration += now() - fiber.stateNode.startTime - totalElapsedPauseTime; } From f609d8749f9780f98f37b86ad2ad47b15499ad36 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 11:38:08 -0700 Subject: [PATCH 22/72] Simplified API for timer pause/resume --- .../react-reconciler/src/ReactFiberScheduler.js | 13 +++++-------- packages/react-reconciler/src/ReactProfileTimer.js | 12 +++++------- .../src/__tests__/ReactProfileRoot-test.internal.js | 3 ++- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 740f2e68563..e5a016d2ee3 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -49,10 +49,9 @@ import { import { checkActualRenderTimeStackEmpty, getElapsedBaseRenderTime, - isActualRenderTimerPaused, isBaseRenderTimerRunning, - pauseActualRenderTimer, - resumeActualRenderTimer, + pauseActualRenderTimerIfRunning, + resumeActualRenderTimerIfPaused, startBaseRenderTimer, stopBaseRenderTimer, } from './ReactProfileTimer'; @@ -960,7 +959,7 @@ export default function( // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. if (nextUnitOfWork !== null) { - pauseActualRenderTimer(); + pauseActualRenderTimerIfRunning(); } } } @@ -1571,9 +1570,7 @@ export default function( deadline = dl; if (enableProfileModeMetrics) { - if (isActualRenderTimerPaused()) { - resumeActualRenderTimer(); - } + resumeActualRenderTimerIfPaused(); } // Keep working on roots until there's no more work, or until the we reach @@ -1730,7 +1727,7 @@ export default function( // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. if (nextUnitOfWork !== null) { - pauseActualRenderTimer(); + pauseActualRenderTimerIfRunning(); } } } diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 6ccd5264e2b..1e380f3aa64 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -52,10 +52,6 @@ export function markActualRenderTimeStarted(fiber: Fiber): void { push(stackCursor, fiber, fiber); } -export function isActualRenderTimerPaused(): boolean { - return timerPausedAt > 0; -} - export function recordActualRenderTime(fiber: Fiber): void { pop(stackCursor, fiber); @@ -65,15 +61,17 @@ export function recordActualRenderTime(fiber: Fiber): void { now() - fiber.stateNode.startTime - totalElapsedPauseTime; } -export function resumeActualRenderTimer(): void { +export function resumeActualRenderTimerIfPaused(): void { if (timerPausedAt > 0) { totalElapsedPauseTime += now() - timerPausedAt; timerPausedAt = 0; } } -export function pauseActualRenderTimer(): void { - timerPausedAt = now(); +export function pauseActualRenderTimerIfRunning(): void { + if (timerPausedAt === 0) { + timerPausedAt = now(); + } } /** diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 10b78d9a0f3..d37e6f50ba2 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -488,7 +488,8 @@ describe('ProfileRoot', () => { // Verify that the "actual" time includes both durations above, // And the "base" time includes only the final rendered tree times. expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBe(15); // "actual" time + // TODO (bvaughn) Enable this test once resuming is supported + // expect(callback.mock.calls[0][2]).toBe(15); // "actual" time expect(callback.mock.calls[0][3]).toBe(5); // "base" time // Verify no more unexpected callbacks from low priority work From 899529f91bbee339c0cfa272bcda370a435cfac4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 11:39:43 -0700 Subject: [PATCH 23/72] Test naming nit picks --- .../ReactProfileRoot-test.internal.js | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index d37e6f50ba2..1d57f25cb72 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -14,14 +14,12 @@ let ReactFeatureFlags; let ReactTestRenderer; function loadModules({ - debugRenderPhaseSideEffects = false, - debugRenderPhaseSideEffectsForStrictMode = false, enableProfileModeMetrics = true, replayFailedUnitOfWorkWithInvokeGuardedCallback = false, } = {}) { ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.debugRenderPhaseSideEffects = debugRenderPhaseSideEffects; - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = debugRenderPhaseSideEffectsForStrictMode; + ReactFeatureFlags.debugRenderPhaseSideEffects = false; + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.enableProfileModeMetrics = enableProfileModeMetrics; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback; React = require('react'); @@ -30,14 +28,14 @@ function loadModules({ describe('ProfileRoot', () => { describe('works in profiling and non-profiling bundles', () => { - [true, false].forEach(enableProfileModeMetrics => { + [true, false].forEach(flagEnabled => { describe(`enableProfileModeMetrics ${ - enableProfileModeMetrics ? 'enabled' : 'disabled' + flagEnabled ? 'enabled' : 'disabled' }`, () => { beforeEach(() => { jest.resetModules(); - loadModules({enableProfileModeMetrics}); + loadModules({enableProfileModeMetrics: flagEnabled}); }); // This will throw in production too, @@ -497,16 +495,16 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); }); - [true, false].forEach(replayFailedUnitOfWorkWithInvokeGuardedCallback => { + [true, false].forEach(flagEnabled => { describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ - replayFailedUnitOfWorkWithInvokeGuardedCallback - ? 'enabled' - : 'disabled' + flagEnabled ? 'enabled' : 'disabled' }`, () => { beforeEach(() => { jest.resetModules(); - loadModules({replayFailedUnitOfWorkWithInvokeGuardedCallback}); + loadModules({ + replayFailedUnitOfWorkWithInvokeGuardedCallback: flagEnabled, + }); }); it('should accumulate "actual" time after an ErrorBoundary re-render', () => { @@ -551,9 +549,7 @@ describe('ProfileRoot', () => { expect(mountCall[1]).toBe('mount'); // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) // If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed. - expect(mountCall[2]).toBe( - replayFailedUnitOfWorkWithInvokeGuardedCallback ? 27 : 17, - ); + expect(mountCall[2]).toBe(flagEnabled ? 27 : 17); // "base" time includes: 2 (ErrorBoundary) expect(mountCall[3]).toBe(2); From 1ea1562c8db77c7352c2b2bb637fccd6477cc2c1 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 12:35:23 -0700 Subject: [PATCH 24/72] Removed dead code --- .../react-reconciler/src/ReactProfileTimer.js | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 1e380f3aa64..d73f29357fd 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -131,30 +131,3 @@ export function stopBaseRenderTimer(): void { baseStartTime = null; } - -/* TODO For testing use only -export function debugPrintStack(workInProgress: Fiber): void { - let current = workInProgress; - while (current.return !== null) { - current = current.return; - } - let string = ''; - - const recurse = (fiber: Fiber, depth: number) => { - let prefix = ' '; - for (let i = 0; i <= depth; i++) { - prefix += ' '; - } - string += '\n' + prefix + require('shared/getComponentName').default(fiber) + - ' {selfBaseTime: ' + fiber.selfBaseTime + ', treeBaseTime: ' + fiber.treeBaseTime + '}'; - let innerCurrent = fiber.child; - while (innerCurrent !== null) { - recurse(innerCurrent, depth + 1); - innerCurrent = innerCurrent.sibling; - } - }; - - recurse(current, 0); - console.log(' debugPrintStack()' + string); -} -*/ From ebc4e99c520a065fb2fce1839935872ede9663a3 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 12:53:33 -0700 Subject: [PATCH 25/72] Tidied up comments and formatting a bit --- packages/react-reconciler/src/ReactFiber.js | 1 - packages/react-reconciler/src/ReactFiberBeginWork.js | 5 ----- .../react-reconciler/src/ReactFiberCommitWork.js | 1 - .../react-reconciler/src/ReactFiberCompleteWork.js | 4 ++-- packages/react-reconciler/src/ReactFiberScheduler.js | 12 +++++------- .../react-reconciler/src/ReactFiberUnwindWork.js | 4 ++-- packages/react-reconciler/src/ReactProfileTimer.js | 6 ++---- .../__tests__/ReactIncrementalPerf-test.internal.js | 2 +- .../src/__tests__/ReactProfileRoot-test.internal.js | 2 ++ 9 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 74bc5a5fe7e..c362947ff17 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -196,7 +196,6 @@ function FiberNode( this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; - this.memoizedState = null; this.mode = mode; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 6376818d363..be471d7e81b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -284,7 +284,6 @@ export default function( workInProgress.effectTag |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren); memoizeProps(workInProgress, nextProps); - return workInProgress.child; } @@ -323,7 +322,6 @@ export default function( renderExpirationTime, ); } - return finishClassComponent( current, workInProgress, @@ -374,7 +372,6 @@ export default function( nextChildren = null; if (enableProfileModeMetrics) { - // Stop "base" render timer in this case to avoid overriding the actual times. stopBaseRenderTimer(); } } else { @@ -653,7 +650,6 @@ export default function( const hasContext = pushLegacyContextProvider(workInProgress); adoptClassInstance(workInProgress, value); mountClassInstance(workInProgress, renderExpirationTime); - return finishClassComponent( current, workInProgress, @@ -717,7 +713,6 @@ export default function( } reconcileChildren(current, workInProgress, value); memoizeProps(workInProgress, props); - return workInProgress.child; } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 8dd2df888d6..3ef27af17f3 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -783,7 +783,6 @@ export default function( // Reset actualTime after successful commit. // By default, we append to this time to account for errors and pauses. finishedWork.stateNode.duration = 0; - finishedWork.stateNode.startTime = 0; break; } default: { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 8d9230e035c..e6348a3f38c 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -47,7 +47,7 @@ import { Ref, Update, } from 'shared/ReactTypeOfSideEffect'; -import {recordActualRenderTime} from './ReactProfileTimer'; +import {recordElapsedActualRenderTime} from './ReactProfileTimer'; import invariant from 'fbjs/lib/invariant'; import {reconcileChildFibers} from './ReactChildFiber'; @@ -201,7 +201,7 @@ export default function( function updateProfileRoot(workInProgress: Fiber) { if (enableProfileModeMetrics) { if (workInProgress.effectTag & CommitProfile) { - recordActualRenderTime(workInProgress); + recordElapsedActualRenderTime(workInProgress); } } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index e5a016d2ee3..fa2c8b7176f 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -319,8 +319,6 @@ export default function( clearCaughtError(); if (enableProfileModeMetrics) { - // Update "base" time if the render wasn't bailed out on. - failedUnitOfWork.selfBaseTime = failedUnitOfWork.treeBaseTime = getElapsedBaseRenderTime(); // Stop "base" render timer again (after the re-thrown error). stopBaseRenderTimer(); } @@ -470,8 +468,10 @@ export default function( commitAttachRef(nextEffect); } - if (effectTag & CommitProfile) { - commitProfileWork(nextEffect); + if (enableProfileModeMetrics) { + if (effectTag & CommitProfile) { + commitProfileWork(nextEffect); + } } const next = nextEffect.nextEffect; @@ -910,8 +910,8 @@ export default function( startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); + // Update "base" time if the render wasn't bailed out on. if (isBaseRenderTimerRunning()) { - // Update "base" time if the render wasn't bailed out on. workInProgress.selfBaseTime = getElapsedBaseRenderTime(); stopBaseRenderTimer(); } @@ -1006,7 +1006,6 @@ export default function( } catch (thrownValue) { if (enableProfileModeMetrics) { // Stop "base" render timer in the event of an error. - // It will be restarted when we replay the failed work. stopBaseRenderTimer(); } @@ -1776,7 +1775,6 @@ export default function( // during a timeout. This path is only hit for non-expired work. return false; } - deadlineDidExpire = true; return true; } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index f848d0d1fa7..6bc99fc0a4f 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -37,7 +37,7 @@ import { NoEffect, ShouldCapture, } from 'shared/ReactTypeOfSideEffect'; -import {recordActualRenderTime} from './ReactProfileTimer'; +import {recordElapsedActualRenderTime} from './ReactProfileTimer'; import { enableGetDerivedStateFromCatch, enableProfileModeMetrics, @@ -222,7 +222,7 @@ export default function( function unwindInterruptedWork(interruptedWork: Fiber) { if (enableProfileModeMetrics) { if (interruptedWork.effectTag & CommitProfile) { - recordActualRenderTime(interruptedWork); + recordElapsedActualRenderTime(interruptedWork); } } diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index d73f29357fd..f580f7398b9 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -49,14 +49,12 @@ export function checkActualRenderTimeStackEmpty(): void { export function markActualRenderTimeStarted(fiber: Fiber): void { fiber.stateNode.startTime = now() - totalElapsedPauseTime; - push(stackCursor, fiber, fiber); + push(stackCursor, fiber.stateNode, fiber); } -export function recordActualRenderTime(fiber: Fiber): void { +export function recordElapsedActualRenderTime(fiber: Fiber): void { pop(stackCursor, fiber); - // Stop render timer and store the elapsed time as stateNode. - // The "commit" phase reads this value and passes it along to the callback. fiber.stateNode.duration += now() - fiber.stateNode.startTime - totalElapsedPauseTime; } diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index e4f19b0f984..6b2aec39f53 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -187,7 +187,7 @@ describe('ReactDebugFiberPerf', () => { expect(getFlameChart()).toMatchSnapshot(); }); - it('does not include AsyncMode, StrictMode or ProfileMode components in measurements', () => { + it('does not include AsyncMode, StrictMode, or ProfileMode components in measurements', () => { ReactNoop.render( diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 1d57f25cb72..b8bc786d225 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -495,6 +495,8 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); }); + // TODO (bvaughn, timing) Add test with getDerivedStateFromCatch() + [true, false].forEach(flagEnabled => { describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ flagEnabled ? 'enabled' : 'disabled' From 909b19a4b3f20aa0766ed880b2ed5f2cd4126013 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 13:06:15 -0700 Subject: [PATCH 26/72] Added a test for getDerivedStateFromCatch() --- .../ReactProfileRoot-test.internal.js | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index b8bc786d225..a207ad93ae2 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -21,6 +21,7 @@ function loadModules({ ReactFeatureFlags.debugRenderPhaseSideEffects = false; ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.enableProfileModeMetrics = enableProfileModeMetrics; + ReactFeatureFlags.enableGetDerivedStateFromCatch = true; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback; React = require('react'); ReactTestRenderer = require('react-test-renderer'); @@ -495,8 +496,6 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); }); - // TODO (bvaughn, timing) Add test with getDerivedStateFromCatch() - [true, false].forEach(flagEnabled => { describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ flagEnabled ? 'enabled' : 'disabled' @@ -509,7 +508,7 @@ describe('ProfileRoot', () => { }); }); - it('should accumulate "actual" time after an ErrorBoundary re-render', () => { + it('should accumulate "actual" time after an error handled by componentDidCatch()', () => { const callback = jest.fn(); const ThrowsError = () => { @@ -562,6 +561,54 @@ describe('ProfileRoot', () => { // "base" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) expect(updateCall[3]).toBe(22); }); + + it('should accumulate "actual" time after an error handled by getDerivedStateFromCatch()', () => { + const callback = jest.fn(); + + const ThrowsError = () => { + advanceTimeBy(10); + throw Error('expected error'); + }; + + class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromCatch(error) { + return {error}; + } + render() { + advanceTimeBy(2); + return this.state.error === null ? ( + this.props.children + ) : ( + + ); + } + } + + ReactTestRenderer.create( + + + + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + // Callbacks bubble (reverse order). + let [mountCall] = callback.mock.calls; + + // The initial mount includes the ErrorBoundary's error state, + // But i also spends "actual" time rendering UI that fails and isn't included. + expect(mountCall[1]).toBe('mount'); + // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) + // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime) + // If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed. + expect(mountCall[2]).toBe(flagEnabled ? 49 : 39); + // "base" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(mountCall[3]).toBe(22); + }); }); }); }); From 2d9bc670c1ff4ef108526229275e3daebe5b174b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 13:18:36 -0700 Subject: [PATCH 27/72] Reanmed ProfileRoot label attribute to id --- packages/react-reconciler/src/ReactFiber.js | 4 +- .../src/ReactFiberCommitWork.js | 2 +- .../ReactProfileRoot-test.internal.js | 52 +++++++++---------- packages/shared/getComponentName.js | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index c362947ff17..727b1b39d9f 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -472,12 +472,12 @@ export function createFiberFromProfileMode( ): Fiber { if (__DEV__) { if ( - typeof pendingProps.label !== 'string' || + typeof pendingProps.id !== 'string' || typeof pendingProps.callback !== 'function' ) { invariant( false, - 'ProfileMode must specify a label string and callback function', + 'ProfileMode must specify an "id" string and "callback" function as props', ); } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 3ef27af17f3..23ac15209b9 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -773,7 +773,7 @@ export default function( if (enableProfileModeMetrics) { finishedWork.pendingProps.callback.call( null, - finishedWork.pendingProps.label, + finishedWork.pendingProps.id, finishedWork.alternate === null ? 'mount' : 'update', finishedWork.stateNode.duration, finishedWork.treeBaseTime, diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index a207ad93ae2..b6ce3abd557 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -46,7 +46,7 @@ describe('ProfileRoot', () => { expect(() => { ReactTestRenderer.create(); }).toThrow( - 'ProfileMode must specify a label string and callback function', + 'ProfileMode must specify an "id" string and "callback" function as props', ); }); } @@ -55,7 +55,7 @@ describe('ProfileRoot', () => { // As root expect( ReactTestRenderer.create( - {}} />, + {}} />, ).toJSON(), ).toMatchSnapshot(); @@ -63,7 +63,7 @@ describe('ProfileRoot', () => { expect( ReactTestRenderer.create(
- {}} /> + {}} />
, ).toJSON(), ).toMatchSnapshot(); @@ -74,7 +74,7 @@ describe('ProfileRoot', () => { const renderer = ReactTestRenderer.create(
outside span - {}}> + {}}> inside span @@ -91,9 +91,9 @@ describe('ProfileRoot', () => { } } const renderer = ReactTestRenderer.create( - {}}> + {}}> - {}}> + {}}> inner span @@ -153,7 +153,7 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + , @@ -173,7 +173,7 @@ describe('ProfileRoot', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + , ); @@ -191,7 +191,7 @@ describe('ProfileRoot', () => { callback.mockReset(); renderer.update( - + , ); @@ -212,9 +212,9 @@ describe('ProfileRoot', () => { ReactTestRenderer.create( - + - + @@ -241,10 +241,10 @@ describe('ProfileRoot', () => { ReactTestRenderer.create( - + - + , @@ -269,7 +269,7 @@ describe('ProfileRoot', () => { ReactTestRenderer.create( - + @@ -303,11 +303,11 @@ describe('ProfileRoot', () => { } const renderer = ReactTestRenderer.create( - + - + - +
@@ -338,7 +338,7 @@ describe('ProfileRoot', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + @@ -348,7 +348,7 @@ describe('ProfileRoot', () => { expect(callback).toHaveBeenCalledTimes(1); renderer.update( - + @@ -380,7 +380,7 @@ describe('ProfileRoot', () => { // Render partially, but run out of time before completing. const renderer = ReactTestRenderer.create( - + , @@ -411,10 +411,10 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + - + , @@ -458,7 +458,7 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + , @@ -478,7 +478,7 @@ describe('ProfileRoot', () => { // The interrupted work simulates an additional 5ms of time. renderer.unstable_flushSync(() => { renderer.update( - + , ); @@ -532,7 +532,7 @@ describe('ProfileRoot', () => { } ReactTestRenderer.create( - + @@ -586,7 +586,7 @@ describe('ProfileRoot', () => { } ReactTestRenderer.create( - + diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index 6b76da2db53..bdc11b5e657 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -38,7 +38,7 @@ function getComponentName(fiber: Fiber): string | null { case REACT_PORTAL_TYPE: return 'ReactPortal'; case REACT_PROFILE_ROOT_TYPE: - return `ProfileMode(${fiber.pendingProps.label})`; + return `ProfileMode(${fiber.pendingProps.id})`; case REACT_RETURN_TYPE: return 'ReactReturn'; case REACT_STRICT_MODE_TYPE: From 8b44f882c584259cb235fa51b7869371fa4e17a0 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 14:14:12 -0700 Subject: [PATCH 28/72] Fixed test --- .../__tests__/ReactIncrementalPerf-test.internal.js | 4 ++-- .../ReactIncrementalPerf-test.internal.js.snap | 7 ++++--- .../src/__tests__/ReactShallowRenderer-test.js | 2 +- .../src/__tests__/ReactTestRendererTraversal-test.js | 2 +- .../src/__tests__/ReactProfileRoot-test.internal.js | 10 +++++----- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index 6b2aec39f53..9711057315f 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -189,7 +189,7 @@ describe('ReactDebugFiberPerf', () => { it('does not include AsyncMode, StrictMode, or ProfileMode components in measurements', () => { ReactNoop.render( - + @@ -197,7 +197,7 @@ describe('ReactDebugFiberPerf', () => { - , + , ); addComment('Mount'); ReactNoop.flush(); diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap index ffe5706d1c0..323ba2bf68e 100644 --- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap +++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap @@ -91,13 +91,14 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc " `; -exports[`ReactDebugFiberPerf does not include StrictMode or AsyncMode components in measurements 1`] = ` +exports[`ReactDebugFiberPerf does not include AsyncMode, StrictMode, or ProfileMode components in measurements 1`] = ` "⚛ (Waiting for async callback... will force flush in 5230 ms) // Mount ⚛ (React Tree Reconciliation: Completed Root) - ⚛ Parent [mount] - ⚛ Child [mount] + ⚛ ProfileMode(test) [mount] + ⚛ Parent [mount] + ⚛ Child [mount] ⚛ (Committing Changes) ⚛ (Committing Snapshot Effects: 0 Total) diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js index d955fba3904..09d4f5f5de1 100644 --- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js @@ -234,7 +234,7 @@ describe('ReactShallowRenderer', () => { class SomeComponent extends React.Component { render() { return ( - {}}> +
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index 8583d045471..757760a9906 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -37,7 +37,7 @@ describe('ReactTestRendererTraversal', () => { - {}}> + {}}> diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index b6ce3abd557..28eedd8fde1 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -55,7 +55,7 @@ describe('ProfileRoot', () => { // As root expect( ReactTestRenderer.create( - {}} />, + , ).toJSON(), ).toMatchSnapshot(); @@ -63,7 +63,7 @@ describe('ProfileRoot', () => { expect( ReactTestRenderer.create(
- {}} /> +
, ).toJSON(), ).toMatchSnapshot(); @@ -74,7 +74,7 @@ describe('ProfileRoot', () => { const renderer = ReactTestRenderer.create(
outside span - {}}> + inside span @@ -91,9 +91,9 @@ describe('ProfileRoot', () => { } } const renderer = ReactTestRenderer.create( - {}}> + - {}}> + inner span From 4743d65767ef5c446b28e8fef03a60dd799f4ce0 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 14:24:32 -0700 Subject: [PATCH 29/72] Added timing test coverage for lifecycles --- .../ReactProfileRoot-test.internal.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 28eedd8fde1..2b663e7acb8 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -368,6 +368,50 @@ describe('ProfileRoot', () => { expect(updateCall[3]).toBe(20); // "base" time }); + it('includes time spent in render phase lifecycles', () => { + class WithLifecycles extends React.Component { + state = {}; + static getDerivedStateFromProps() { + advanceTimeBy(3); + return null; + } + shouldComponentUpdate() { + advanceTimeBy(7); + return true; + } + render() { + advanceTimeBy(5); + return null; + } + } + + const callback = jest.fn(); + + const renderer = ReactTestRenderer.create( + + + , + ); + + renderer.update( + + + , + ); + + expect(callback).toHaveBeenCalledTimes(2); + + const [mountCall, updateCall] = callback.mock.calls; + + expect(mountCall[1]).toBe('mount'); + expect(mountCall[2]).toBe(8); // "actual" time + expect(mountCall[3]).toBe(8); // "base" time + + expect(updateCall[1]).toBe('update'); + expect(updateCall[2]).toBe(15); // "actual" time + expect(updateCall[3]).toBe(15); // "base" time + }); + describe('handles interruptions', () => { it('should accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); From 043120638ca509d0456ba48c7f054db4015f4690 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 14:25:58 -0700 Subject: [PATCH 30/72] Fixed prod bundle test with __DEV__ conditional --- .../react/src/__tests__/ReactProfileRoot-test.internal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 2b663e7acb8..2148c185c6b 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -594,7 +594,7 @@ describe('ProfileRoot', () => { expect(mountCall[1]).toBe('mount'); // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) // If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed. - expect(mountCall[2]).toBe(flagEnabled ? 27 : 17); + expect(mountCall[2]).toBe(flagEnabled && __DEV__ ? 27 : 17); // "base" time includes: 2 (ErrorBoundary) expect(mountCall[3]).toBe(2); @@ -649,7 +649,7 @@ describe('ProfileRoot', () => { // "actual" time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime) // If replayFailedUnitOfWorkWithInvokeGuardedCallback is enbaled, ThrowsError is replayed. - expect(mountCall[2]).toBe(flagEnabled ? 49 : 39); + expect(mountCall[2]).toBe(flagEnabled && __DEV__ ? 49 : 39); // "base" time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) expect(mountCall[3]).toBe(22); }); From aabb36fcbeb3013697cfc9f0c2b80bb8dc4bfc73 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 14:48:35 -0700 Subject: [PATCH 31/72] Replace unnecssary stateNode Object wrapper with number --- packages/react-reconciler/src/ReactFiber.js | 5 +---- packages/react-reconciler/src/ReactFiberCommitWork.js | 4 ++-- packages/react-reconciler/src/ReactProfileTimer.js | 5 ++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 727b1b39d9f..69e04757896 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -485,10 +485,7 @@ export function createFiberFromProfileMode( const fiber = createFiber(ProfileRoot, pendingProps, key, mode | ProfileMode); fiber.type = REACT_PROFILE_ROOT_TYPE; fiber.expirationTime = expirationTime; - fiber.stateNode = { - duration: 0, - startTime: 0, - }; + fiber.stateNode = 0; return fiber; } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 23ac15209b9..67f4d3b1fe4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -775,14 +775,14 @@ export default function( null, finishedWork.pendingProps.id, finishedWork.alternate === null ? 'mount' : 'update', - finishedWork.stateNode.duration, + finishedWork.stateNode, finishedWork.treeBaseTime, ); } // Reset actualTime after successful commit. // By default, we append to this time to account for errors and pauses. - finishedWork.stateNode.duration = 0; + finishedWork.stateNode = 0; break; } default: { diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index f580f7398b9..125032c4aa1 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -47,7 +47,7 @@ export function checkActualRenderTimeStackEmpty(): void { } export function markActualRenderTimeStarted(fiber: Fiber): void { - fiber.stateNode.startTime = now() - totalElapsedPauseTime; + fiber.stateNode -= now() - totalElapsedPauseTime; push(stackCursor, fiber.stateNode, fiber); } @@ -55,8 +55,7 @@ export function markActualRenderTimeStarted(fiber: Fiber): void { export function recordElapsedActualRenderTime(fiber: Fiber): void { pop(stackCursor, fiber); - fiber.stateNode.duration += - now() - fiber.stateNode.startTime - totalElapsedPauseTime; + fiber.stateNode += now() - totalElapsedPauseTime; } export function resumeActualRenderTimerIfPaused(): void { From 52df41cee721685c4bba93a2d50809c1885a3630 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 15:17:22 -0700 Subject: [PATCH 32/72] Renamed ProfileRoot -> Profiler --- .../src/server/ReactPartialRenderer.js | 4 +- packages/react-is/src/ReactIs.js | 10 +- .../react-is/src/__tests__/ReactIs-test.js | 14 +-- packages/react-reconciler/src/ReactFiber.js | 10 +- .../src/ReactFiberBeginWork.js | 10 +- .../src/ReactFiberCommitWork.js | 4 +- .../src/ReactFiberCompleteWork.js | 10 +- .../react-reconciler/src/ReactProfileTimer.js | 4 +- .../ReactIncrementalPerf-test.internal.js | 4 +- .../src/ReactTestRenderer.js | 6 +- .../__tests__/ReactShallowRenderer-test.js | 8 +- .../ReactTestRendererTraversal-test.js | 4 +- packages/react/src/React.js | 4 +- .../ReactProfileRoot-test.internal.js | 114 +++++++++--------- .../ReactProfileRoot-test.internal.js.snap | 16 +-- packages/shared/ReactSymbols.js | 2 +- packages/shared/ReactTypeOfWork.js | 2 +- packages/shared/getComponentName.js | 4 +- packages/shared/isValidElementType.js | 4 +- 19 files changed, 115 insertions(+), 119 deletions(-) diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 4c62a352145..d0cef9c60a4 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -34,7 +34,7 @@ import { REACT_CALL_TYPE, REACT_RETURN_TYPE, REACT_PORTAL_TYPE, - REACT_PROFILE_ROOT_TYPE, + REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, } from 'shared/ReactSymbols'; @@ -812,7 +812,7 @@ class ReactDOMServerRenderer { switch (elementType) { case REACT_STRICT_MODE_TYPE: case REACT_ASYNC_MODE_TYPE: - case REACT_PROFILE_ROOT_TYPE: + case REACT_PROFILER_TYPE: case REACT_FRAGMENT_TYPE: { const nextChildren = toArray( ((nextChild: any): ReactElement).props.children, diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js index 06221bc104a..e94599474dc 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -16,7 +16,7 @@ import { REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, REACT_PORTAL_TYPE, - REACT_PROFILE_ROOT_TYPE, + REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -33,7 +33,7 @@ export function typeOf(object: any) { switch (type) { case REACT_ASYNC_MODE_TYPE: case REACT_FRAGMENT_TYPE: - case REACT_PROFILE_ROOT_TYPE: + case REACT_PROFILER_TYPE: case REACT_STRICT_MODE_TYPE: return type; default: @@ -62,7 +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 ProfileRoot = REACT_PROFILE_ROOT_TYPE; +export const Profiler = REACT_PROFILER_TYPE; export const Portal = REACT_PORTAL_TYPE; export const StrictMode = REACT_STRICT_MODE_TYPE; @@ -90,8 +90,8 @@ export function isForwardRef(object: any) { export function isFragment(object: any) { return typeOf(object) === REACT_FRAGMENT_TYPE; } -export function isProfileRoot(object: any) { - return typeOf(object) === REACT_PROFILE_ROOT_TYPE; +export function isProfiler(object: any) { + return typeOf(object) === REACT_PROFILER_TYPE; } export function isPortal(object: any) { return typeOf(object) === REACT_PORTAL_TYPE; diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js index 1c7835df454..cf68202eaeb 100644 --- a/packages/react-is/src/__tests__/ReactIs-test.js +++ b/packages/react-is/src/__tests__/ReactIs-test.js @@ -147,14 +147,10 @@ describe('ReactIs', () => { }); it('should identify profile root', () => { - expect(ReactIs.typeOf()).toBe( - ReactIs.ProfileRoot, - ); - expect(ReactIs.isProfileRoot()).toBe(true); - expect(ReactIs.isProfileRoot({type: ReactIs.unstable_ProfileRoot})).toBe( - false, - ); - expect(ReactIs.isProfileRoot()).toBe(false); - expect(ReactIs.isProfileRoot(
)).toBe(false); + expect(ReactIs.typeOf()).toBe(ReactIs.Profiler); + expect(ReactIs.isProfiler()).toBe(true); + expect(ReactIs.isProfiler({type: ReactIs.unstable_Profiler})).toBe(false); + expect(ReactIs.isProfiler()).toBe(false); + expect(ReactIs.isProfiler(
)).toBe(false); }); }); diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 69e04757896..1406513eaee 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -31,7 +31,7 @@ import { Mode, ContextProvider, ContextConsumer, - ProfileRoot, + Profiler, } from 'shared/ReactTypeOfWork'; import getComponentName from 'shared/getComponentName'; @@ -43,7 +43,7 @@ import { REACT_RETURN_TYPE, REACT_CALL_TYPE, REACT_STRICT_MODE_TYPE, - REACT_PROFILE_ROOT_TYPE, + REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, REACT_ASYNC_MODE_TYPE, @@ -360,7 +360,7 @@ export function createFiberFromElement( fiberTag = Mode; mode |= StrictMode; break; - case REACT_PROFILE_ROOT_TYPE: + case REACT_PROFILER_TYPE: return createFiberFromProfileMode( pendingProps, mode, @@ -482,8 +482,8 @@ export function createFiberFromProfileMode( } } - const fiber = createFiber(ProfileRoot, pendingProps, key, mode | ProfileMode); - fiber.type = REACT_PROFILE_ROOT_TYPE; + const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode); + fiber.type = REACT_PROFILER_TYPE; fiber.expirationTime = expirationTime; fiber.stateNode = 0; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index be471d7e81b..ac9fcc44c55 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -34,7 +34,7 @@ import { Mode, ContextProvider, ContextConsumer, - ProfileRoot, + Profiler, } from 'shared/ReactTypeOfWork'; import { NoEffect, @@ -222,7 +222,7 @@ export default function( return workInProgress.child; } - function updateProfileRoot(current, workInProgress) { + function updateProfiler(current, workInProgress) { if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue markActualRenderTimeStarted(workInProgress); @@ -232,7 +232,7 @@ export default function( workInProgress.effectTag |= CommitProfile; } - // Never bail out early for ProfileRoots. + // Never bail out early for Profilers. // We always want to re-measure the subtree. const nextChildren = workInProgress.pendingProps.children; @@ -1213,8 +1213,8 @@ export default function( return updateFragment(current, workInProgress); case Mode: return updateMode(current, workInProgress); - case ProfileRoot: - return updateProfileRoot(current, workInProgress); + case Profiler: + return updateProfiler(current, workInProgress); case ContextProvider: return updateContextProvider( current, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 67f4d3b1fe4..7054b5154b3 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -26,7 +26,7 @@ import { HostText, HostPortal, CallComponent, - ProfileRoot, + Profiler, } from 'shared/ReactTypeOfWork'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import { @@ -769,7 +769,7 @@ export default function( function commitProfileWork(finishedWork: Fiber): void { switch (finishedWork.tag) { - case ProfileRoot: { + case Profiler: { if (enableProfileModeMetrics) { finishedWork.pendingProps.callback.call( null, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index e6348a3f38c..fb64aa41873 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -38,7 +38,7 @@ import { ForwardRef, Fragment, Mode, - ProfileRoot, + Profiler, } from 'shared/ReactTypeOfWork'; import {ProfileMode} from './ReactTypeOfMode'; import { @@ -198,7 +198,7 @@ export default function( } } - function updateProfileRoot(workInProgress: Fiber) { + function updateProfiler(workInProgress: Fiber) { if (enableProfileModeMetrics) { if (workInProgress.effectTag & CommitProfile) { recordElapsedActualRenderTime(workInProgress); @@ -424,7 +424,7 @@ export default function( if (enableProfileModeMetrics) { if (workInProgress.mode & ProfileMode) { - // Bubble up "base" render times if we're within a ProfileRoot + // Bubble up "base" render times if we're within a Profiler let treeBaseTime = workInProgress.selfBaseTime; let child = workInProgress.child; while (child !== null) { @@ -622,8 +622,8 @@ export default function( return null; case Mode: return null; - case ProfileRoot: - updateProfileRoot(workInProgress); + case Profiler: + updateProfiler(workInProgress); return null; case HostPortal: popHostContainer(workInProgress); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 125032c4aa1..9518da33e46 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -28,8 +28,8 @@ if (hasNativePerformanceNow) { } /** - * The "actual" render time is total time required to render the descendants of a ProfileRoot component. - * This time is stored as a stack, since ProfileRoots can be nested. + * The "actual" render time is total time required to render the descendants of a Profiler component. + * This time is stored as a stack, since Profilers can be nested. * This time is started during the "begin" phase and stopped during the "complete" phase. * It is paused (and accumulated) in the event of an interruption or an aborted render. */ diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index 9711057315f..23280029ab3 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -189,7 +189,7 @@ describe('ReactDebugFiberPerf', () => { it('does not include AsyncMode, StrictMode, or ProfileMode components in measurements', () => { ReactNoop.render( - + @@ -197,7 +197,7 @@ describe('ReactDebugFiberPerf', () => { - , + , ); addComment('Mount'); ReactNoop.flush(); diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index c556fa91de1..37ac8458116 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -27,7 +27,7 @@ import { ContextProvider, Mode, ForwardRef, - ProfileRoot, + Profiler, } from 'shared/ReactTypeOfWork'; import invariant from 'fbjs/lib/invariant'; @@ -384,7 +384,7 @@ function toTree(node: ?Fiber) { case ContextProvider: case ContextConsumer: case Mode: - case ProfileRoot: + case Profiler: case ForwardRef: return childrenToTree(node.child); default: @@ -488,7 +488,7 @@ class ReactTestInstance { case ContextProvider: case ContextConsumer: case Mode: - case ProfileRoot: + case Profiler: descend = true; break; default: diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js index 09d4f5f5de1..e50905a4552 100644 --- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js @@ -230,16 +230,16 @@ describe('ReactShallowRenderer', () => { ]); }); - it('should handle ProfileRoot', () => { + it('should handle Profiler', () => { class SomeComponent extends React.Component { render() { return ( - +
-
+ ); } } @@ -247,7 +247,7 @@ describe('ReactShallowRenderer', () => { const shallowRenderer = createRenderer(); const result = shallowRenderer.render(); - expect(result.type).toBe(React.unstable_ProfileRoot); + expect(result.type).toBe(React.unstable_Profiler); expect(result.props.children).toEqual(
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index 757760a9906..0d1bd4db1d3 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -37,9 +37,9 @@ describe('ReactTestRendererTraversal', () => { - {}}> + {}}> - + ); diff --git a/packages/react/src/React.js b/packages/react/src/React.js index e70646a4779..d4740406116 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -10,7 +10,7 @@ import ReactVersion from 'shared/ReactVersion'; import { REACT_ASYNC_MODE_TYPE, REACT_FRAGMENT_TYPE, - REACT_PROFILE_ROOT_TYPE, + REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -52,7 +52,7 @@ const React = { Fragment: REACT_FRAGMENT_TYPE, StrictMode: REACT_STRICT_MODE_TYPE, unstable_AsyncMode: REACT_ASYNC_MODE_TYPE, - unstable_ProfileRoot: REACT_PROFILE_ROOT_TYPE, + unstable_Profiler: REACT_PROFILER_TYPE, createElement: __DEV__ ? createElementWithValidation : createElement, cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 2148c185c6b..6fa61e95059 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -27,7 +27,7 @@ function loadModules({ ReactTestRenderer = require('react-test-renderer'); } -describe('ProfileRoot', () => { +describe('Profiler', () => { describe('works in profiling and non-profiling bundles', () => { [true, false].forEach(flagEnabled => { describe(`enableProfileModeMetrics ${ @@ -44,18 +44,18 @@ describe('ProfileRoot', () => { if (__DEV__) { it('should warn if required params are missing', () => { expect(() => { - ReactTestRenderer.create(); + ReactTestRenderer.create(); }).toThrow( 'ProfileMode must specify an "id" string and "callback" function as props', ); }); } - it('should support an empty ProfileRoot (with no children)', () => { + it('should support an empty Profiler (with no children)', () => { // As root expect( ReactTestRenderer.create( - , + , ).toJSON(), ).toMatchSnapshot(); @@ -63,7 +63,7 @@ describe('ProfileRoot', () => { expect( ReactTestRenderer.create(
- +
, ).toJSON(), ).toMatchSnapshot(); @@ -74,10 +74,10 @@ describe('ProfileRoot', () => { const renderer = ReactTestRenderer.create(
outside span - + inside span - +
, ); expect(renderer.toJSON()).toMatchSnapshot(); @@ -91,13 +91,13 @@ describe('ProfileRoot', () => { } } const renderer = ReactTestRenderer.create( - + - + inner span - - , +
+
, ); expect(renderer.toJSON()).toMatchSnapshot(); }); @@ -153,10 +153,10 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + - , + , { unstable_isAsync: true, }, @@ -173,9 +173,9 @@ describe('ProfileRoot', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + - , + , ); expect(callback).toHaveBeenCalledTimes(1); @@ -191,9 +191,9 @@ describe('ProfileRoot', () => { callback.mockReset(); renderer.update( - + - , + , ); expect(callback).toHaveBeenCalledTimes(1); @@ -207,18 +207,18 @@ describe('ProfileRoot', () => { expect(call[3]).toBe(10); // "base" time }); - it('includes render times of nested ProfileRoots in their parent times', () => { + it('includes render times of nested Profilers in their parent times', () => { const callback = jest.fn(); ReactTestRenderer.create( - + - + - + - + , ); @@ -236,17 +236,17 @@ describe('ProfileRoot', () => { expect(parentCall[3]).toBe(30); // "base" time }); - it('tracks sibling ProfileRoots separately', () => { + it('tracks sibling Profilers separately', () => { const callback = jest.fn(); ReactTestRenderer.create( - + - - + + - + , ); @@ -269,9 +269,9 @@ describe('ProfileRoot', () => { ReactTestRenderer.create( - + - + , ); @@ -303,17 +303,17 @@ describe('ProfileRoot', () => { } const renderer = ReactTestRenderer.create( - + - + - +
- + - + - , + , ); // All profile callbacks are called for initial render @@ -338,21 +338,21 @@ describe('ProfileRoot', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + - , + , ); expect(callback).toHaveBeenCalledTimes(1); renderer.update( - + - , + , ); expect(callback).toHaveBeenCalledTimes(2); @@ -388,15 +388,15 @@ describe('ProfileRoot', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + - , + , ); renderer.update( - + - , + , ); expect(callback).toHaveBeenCalledTimes(2); @@ -424,10 +424,10 @@ describe('ProfileRoot', () => { // Render partially, but run out of time before completing. const renderer = ReactTestRenderer.create( - + - , + , {unstable_isAsync: true}, ); @@ -455,13 +455,13 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + - + - - , + + , {unstable_isAsync: true}, ); @@ -502,10 +502,10 @@ describe('ProfileRoot', () => { }; const renderer = ReactTestRenderer.create( - + - , + , {unstable_isAsync: true}, ); @@ -522,9 +522,9 @@ describe('ProfileRoot', () => { // The interrupted work simulates an additional 5ms of time. renderer.unstable_flushSync(() => { renderer.update( - + - , + , ); }); @@ -576,12 +576,12 @@ describe('ProfileRoot', () => { } ReactTestRenderer.create( - + - , + , ); expect(callback).toHaveBeenCalledTimes(2); @@ -630,12 +630,12 @@ describe('ProfileRoot', () => { } ReactTestRenderer.create( - + - , + , ); expect(callback).toHaveBeenCalledTimes(1); diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap index 0d26e58d2f9..099e81d435e 100644 --- a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap +++ b/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should render children 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should render children 1`] = `
outside span @@ -14,11 +14,11 @@ exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileM
`; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty ProfileRoot (with no children) 1`] = `null`; +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty Profiler (with no children) 1`] = `null`; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty ProfileRoot (with no children) 2`] = `
`; +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty Profiler (with no children) 2`] = `
`; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support nested ProfileModes 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support nested ProfileModes 1`] = ` Array [
outer functional component @@ -32,7 +32,7 @@ Array [ ] `; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should render children 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should render children 1`] = `
outside span @@ -46,11 +46,11 @@ exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileM
`; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty ProfileRoot (with no children) 1`] = `null`; +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty Profiler (with no children) 1`] = `null`; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty ProfileRoot (with no children) 2`] = `
`; +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty Profiler (with no children) 2`] = `
`; -exports[`ProfileRoot works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support nested ProfileModes 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support nested ProfileModes 1`] = ` Array [
outer functional component diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 01c17521229..672e51de8ee 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -27,7 +27,7 @@ export const REACT_FRAGMENT_TYPE = hasSymbol export const REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc; -export const REACT_PROFILE_ROOT_TYPE = hasSymbol +export const REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profile_root') : 0xeacc; export const REACT_PROVIDER_TYPE = hasSymbol diff --git a/packages/shared/ReactTypeOfWork.js b/packages/shared/ReactTypeOfWork.js index b36394c419e..1c672bb4b55 100644 --- a/packages/shared/ReactTypeOfWork.js +++ b/packages/shared/ReactTypeOfWork.js @@ -40,4 +40,4 @@ export const Mode = 11; export const ContextConsumer = 12; export const ContextProvider = 13; export const ForwardRef = 14; -export const ProfileRoot = 15; +export const Profiler = 15; diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index bdc11b5e657..35661c16f1d 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -16,7 +16,7 @@ import { REACT_FRAGMENT_TYPE, REACT_RETURN_TYPE, REACT_PORTAL_TYPE, - REACT_PROFILE_ROOT_TYPE, + REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -37,7 +37,7 @@ function getComponentName(fiber: Fiber): string | null { return 'ReactFragment'; case REACT_PORTAL_TYPE: return 'ReactPortal'; - case REACT_PROFILE_ROOT_TYPE: + case REACT_PROFILER_TYPE: return `ProfileMode(${fiber.pendingProps.id})`; case REACT_RETURN_TYPE: return 'ReactReturn'; diff --git a/packages/shared/isValidElementType.js b/packages/shared/isValidElementType.js index 062479d04e3..c9a759d5026 100644 --- a/packages/shared/isValidElementType.js +++ b/packages/shared/isValidElementType.js @@ -12,7 +12,7 @@ import { REACT_CONTEXT_TYPE, REACT_FORWARD_REF_TYPE, REACT_FRAGMENT_TYPE, - REACT_PROFILE_ROOT_TYPE, + REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; @@ -24,7 +24,7 @@ export default function isValidElementType(type: mixed) { // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. type === REACT_FRAGMENT_TYPE || type === REACT_ASYNC_MODE_TYPE || - type === REACT_PROFILE_ROOT_TYPE || + type === REACT_PROFILER_TYPE || type === REACT_STRICT_MODE_TYPE || (typeof type === 'object' && type !== null && From f3e0fcfd21a8c5af6c7421f39c1239d0db3564e0 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 15:36:26 -0700 Subject: [PATCH 33/72] Renamed callback prop -> onRender --- .../react-is/src/__tests__/ReactIs-test.js | 10 +++- packages/react-reconciler/src/ReactFiber.js | 4 +- .../src/ReactFiberCommitWork.js | 2 +- .../ReactIncrementalPerf-test.internal.js | 2 +- .../__tests__/ReactShallowRenderer-test.js | 2 +- .../ReactTestRendererTraversal-test.js | 2 +- .../ReactProfileRoot-test.internal.js | 56 +++++++++---------- 7 files changed, 42 insertions(+), 36 deletions(-) diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js index cf68202eaeb..04c7cec745d 100644 --- a/packages/react-is/src/__tests__/ReactIs-test.js +++ b/packages/react-is/src/__tests__/ReactIs-test.js @@ -147,8 +147,14 @@ describe('ReactIs', () => { }); it('should identify profile root', () => { - expect(ReactIs.typeOf()).toBe(ReactIs.Profiler); - expect(ReactIs.isProfiler()).toBe(true); + expect( + ReactIs.typeOf(), + ).toBe(ReactIs.Profiler); + expect( + ReactIs.isProfiler( + , + ), + ).toBe(true); expect(ReactIs.isProfiler({type: ReactIs.unstable_Profiler})).toBe(false); expect(ReactIs.isProfiler()).toBe(false); expect(ReactIs.isProfiler(
)).toBe(false); diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 1406513eaee..cc6313da0f3 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -473,11 +473,11 @@ export function createFiberFromProfileMode( if (__DEV__) { if ( typeof pendingProps.id !== 'string' || - typeof pendingProps.callback !== 'function' + typeof pendingProps.onRender !== 'function' ) { invariant( false, - 'ProfileMode must specify an "id" string and "callback" function as props', + 'ProfileMode must specify an "id" string and "onRender" function as props', ); } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 7054b5154b3..5126a117e58 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -771,7 +771,7 @@ export default function( switch (finishedWork.tag) { case Profiler: { if (enableProfileModeMetrics) { - finishedWork.pendingProps.callback.call( + finishedWork.pendingProps.onRender.call( null, finishedWork.pendingProps.id, finishedWork.alternate === null ? 'mount' : 'update', diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index 23280029ab3..a49c7cfa626 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -189,7 +189,7 @@ describe('ReactDebugFiberPerf', () => { it('does not include AsyncMode, StrictMode, or ProfileMode components in measurements', () => { ReactNoop.render( - + diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js index e50905a4552..ce62bba2807 100644 --- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js @@ -234,7 +234,7 @@ describe('ReactShallowRenderer', () => { class SomeComponent extends React.Component { render() { return ( - +
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index 0d1bd4db1d3..cefef5657c8 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -37,7 +37,7 @@ describe('ReactTestRendererTraversal', () => { - {}}> + {}}> diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js index 6fa61e95059..c2dff7d852b 100644 --- a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js +++ b/packages/react/src/__tests__/ReactProfileRoot-test.internal.js @@ -46,7 +46,7 @@ describe('Profiler', () => { expect(() => { ReactTestRenderer.create(); }).toThrow( - 'ProfileMode must specify an "id" string and "callback" function as props', + 'ProfileMode must specify an "id" string and "onRender" function as props', ); }); } @@ -55,7 +55,7 @@ describe('Profiler', () => { // As root expect( ReactTestRenderer.create( - , + , ).toJSON(), ).toMatchSnapshot(); @@ -63,7 +63,7 @@ describe('Profiler', () => { expect( ReactTestRenderer.create(
- +
, ).toJSON(), ).toMatchSnapshot(); @@ -74,7 +74,7 @@ describe('Profiler', () => { const renderer = ReactTestRenderer.create(
outside span - + inside span @@ -91,9 +91,9 @@ describe('Profiler', () => { } } const renderer = ReactTestRenderer.create( - + - + inner span @@ -153,7 +153,7 @@ describe('Profiler', () => { }; const renderer = ReactTestRenderer.create( - + , @@ -173,7 +173,7 @@ describe('Profiler', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + , ); @@ -191,7 +191,7 @@ describe('Profiler', () => { callback.mockReset(); renderer.update( - + , ); @@ -212,9 +212,9 @@ describe('Profiler', () => { ReactTestRenderer.create( - + - + @@ -241,10 +241,10 @@ describe('Profiler', () => { ReactTestRenderer.create( - + - + , @@ -269,7 +269,7 @@ describe('Profiler', () => { ReactTestRenderer.create( - + @@ -303,11 +303,11 @@ describe('Profiler', () => { } const renderer = ReactTestRenderer.create( - + - + - +
@@ -338,7 +338,7 @@ describe('Profiler', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + @@ -348,7 +348,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(1); renderer.update( - + @@ -388,13 +388,13 @@ describe('Profiler', () => { const callback = jest.fn(); const renderer = ReactTestRenderer.create( - + , ); renderer.update( - + , ); @@ -424,7 +424,7 @@ describe('Profiler', () => { // Render partially, but run out of time before completing. const renderer = ReactTestRenderer.create( - + , @@ -455,10 +455,10 @@ describe('Profiler', () => { }; const renderer = ReactTestRenderer.create( - + - + , @@ -502,7 +502,7 @@ describe('Profiler', () => { }; const renderer = ReactTestRenderer.create( - + , @@ -522,7 +522,7 @@ describe('Profiler', () => { // The interrupted work simulates an additional 5ms of time. renderer.unstable_flushSync(() => { renderer.update( - + , ); @@ -576,7 +576,7 @@ describe('Profiler', () => { } ReactTestRenderer.create( - + @@ -630,7 +630,7 @@ describe('Profiler', () => { } ReactTestRenderer.create( - + From e4ff3d2a0af1ae69f395b62c0efc47b8165fb94b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 15:36:45 -0700 Subject: [PATCH 34/72] Renamed ReactProfileRoot-test -> ReactProfiler-test --- ...rofileRoot-test.internal.js => ReactProfiler-test.internal.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react/src/__tests__/{ReactProfileRoot-test.internal.js => ReactProfiler-test.internal.js} (100%) diff --git a/packages/react/src/__tests__/ReactProfileRoot-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js similarity index 100% rename from packages/react/src/__tests__/ReactProfileRoot-test.internal.js rename to packages/react/src/__tests__/ReactProfiler-test.internal.js From 20eff9aa77d0b1917aae8bfe808c8171dc64276d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 15:38:59 -0700 Subject: [PATCH 35/72] Renamed snapshot --- ...-test.internal.js.snap => ReactProfiler-test.internal.js.snap} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/react/src/__tests__/__snapshots__/{ReactProfileRoot-test.internal.js.snap => ReactProfiler-test.internal.js.snap} (100%) diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap similarity index 100% rename from packages/react/src/__tests__/__snapshots__/ReactProfileRoot-test.internal.js.snap rename to packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap From 6f803d3ba34ca73a18ac46abcbcbc9929ef06d6f Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 15:49:26 -0700 Subject: [PATCH 36/72] Moved bubbling of base times from ReactFiberCompleteWork -> ReactFiberScheduler --- .../src/ReactFiberCompleteWork.js | 14 -------------- .../react-reconciler/src/ReactFiberScheduler.js | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index fb64aa41873..b971ef16d38 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -40,7 +40,6 @@ import { Mode, Profiler, } from 'shared/ReactTypeOfWork'; -import {ProfileMode} from './ReactTypeOfMode'; import { CommitProfile, Placement, @@ -422,19 +421,6 @@ export default function( ): Fiber | null { const newProps = workInProgress.pendingProps; - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - // Bubble up "base" render times if we're within a Profiler - let treeBaseTime = workInProgress.selfBaseTime; - let child = workInProgress.child; - while (child !== null) { - treeBaseTime += child.treeBaseTime; - child = child.sibling; - } - workInProgress.treeBaseTime = treeBaseTime; - } - } - switch (workInProgress.tag) { case FunctionalComponent: return null; diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index fa2c8b7176f..8b10ea59aa9 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -96,7 +96,7 @@ import { expirationTimeToMs, computeExpirationBucket, } from './ReactFiberExpirationTime'; -import {AsyncMode} from './ReactTypeOfMode'; +import {AsyncMode, ProfileMode} from './ReactTypeOfMode'; import ReactFiberLegacyContext from './ReactFiberContext'; import ReactFiberNewContext from './ReactFiberNewContext'; import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue'; @@ -731,6 +731,19 @@ export default function( child = child.sibling; } workInProgress.expirationTime = newExpirationTime; + + if (enableProfileModeMetrics) { + if (workInProgress.mode & ProfileMode) { + // Bubble up "base" render times if we're within a Profiler + let treeBaseTime = workInProgress.selfBaseTime; + child = workInProgress.child; + while (child !== null) { + treeBaseTime += child.treeBaseTime; + child = child.sibling; + } + workInProgress.treeBaseTime = treeBaseTime; + } + } } function completeUnitOfWork(workInProgress: Fiber): Fiber | null { From 3ff9121ddc5db95276b5c039b55f931e662605b4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 16:43:54 -0700 Subject: [PATCH 37/72] Removed unnecessary CommitProfile effect tag in favor of Update --- .../src/ReactFiberBeginWork.js | 7 ++- .../src/ReactFiberCommitWork.js | 50 ++++++++----------- .../src/ReactFiberCompleteWork.js | 11 +--- .../src/ReactFiberScheduler.js | 8 --- .../src/ReactFiberUnwindWork.js | 13 +++-- packages/shared/ReactTypeOfSideEffect.js | 29 ++++++----- 6 files changed, 47 insertions(+), 71 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index ac9fcc44c55..6ab995a262b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -43,7 +43,7 @@ import { ContentReset, Ref, DidCapture, - CommitProfile, + Update, } from 'shared/ReactTypeOfSideEffect'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; import { @@ -229,7 +229,7 @@ export default function( // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. - workInProgress.effectTag |= CommitProfile; + workInProgress.effectTag |= Update; } // Never bail out early for Profilers. @@ -1133,6 +1133,9 @@ export default function( case ContextProvider: pushProvider(workInProgress); break; + case Profiler: + markActualRenderTimeStarted(workInProgress); + break; } // TODO: What if this is currently in progress? // How can that happen? How is this not being cloned? diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 5126a117e58..578f9a6d797 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -310,6 +310,10 @@ export default function( // We have no life-cycles associated with portals. return; } + case Profiler: { + // We have no life-cycles associated with Profiler. + return; + } default: { invariant( false, @@ -513,7 +517,6 @@ export default function( commitBeforeMutationLifeCycles, commitAttachRef, commitDetachRef, - commitProfileWork, }; } else if (persistence) { invariant(false, 'Persistent reconciler is disabled.'); @@ -767,34 +770,6 @@ export default function( detachFiber(current); } - function commitProfileWork(finishedWork: Fiber): void { - switch (finishedWork.tag) { - case Profiler: { - if (enableProfileModeMetrics) { - finishedWork.pendingProps.onRender.call( - null, - finishedWork.pendingProps.id, - finishedWork.alternate === null ? 'mount' : 'update', - finishedWork.stateNode, - finishedWork.treeBaseTime, - ); - } - - // Reset actualTime after successful commit. - // By default, we append to this time to account for errors and pauses. - finishedWork.stateNode = 0; - break; - } - default: { - invariant( - false, - 'This unit of work tag should not have side-effects. This error is ' + - 'likely caused by a bug in React. Please file an issue.', - ); - } - } - } - function commitWork(current: Fiber | null, finishedWork: Fiber): void { switch (finishedWork.tag) { case ClassComponent: { @@ -845,6 +820,22 @@ export default function( case HostRoot: { return; } + case Profiler: { + if (enableProfileModeMetrics) { + finishedWork.pendingProps.onRender.call( + null, + finishedWork.pendingProps.id, + finishedWork.alternate === null ? 'mount' : 'update', + finishedWork.stateNode, + finishedWork.treeBaseTime, + ); + + // Reset actualTime after successful commit. + // By default, we append to this time to account for errors and pauses. + finishedWork.stateNode = 0; + } + return; + } default: { invariant( false, @@ -869,7 +860,6 @@ export default function( commitLifeCycles, commitAttachRef, commitDetachRef, - commitProfileWork, }; } else { invariant(false, 'Mutating reconciler is disabled.'); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index b971ef16d38..57150aeb866 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -40,12 +40,7 @@ import { Mode, Profiler, } from 'shared/ReactTypeOfWork'; -import { - CommitProfile, - Placement, - Ref, - Update, -} from 'shared/ReactTypeOfSideEffect'; +import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect'; import {recordElapsedActualRenderTime} from './ReactProfileTimer'; import invariant from 'fbjs/lib/invariant'; @@ -199,9 +194,7 @@ export default function( function updateProfiler(workInProgress: Fiber) { if (enableProfileModeMetrics) { - if (workInProgress.effectTag & CommitProfile) { - recordElapsedActualRenderTime(workInProgress); - } + recordElapsedActualRenderTime(workInProgress); } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 8b10ea59aa9..d6a88f68c51 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -31,7 +31,6 @@ import { Ref, Incomplete, HostEffectMask, - CommitProfile, } from 'shared/ReactTypeOfSideEffect'; import { HostRoot, @@ -222,7 +221,6 @@ export default function( commitLifeCycles, commitAttachRef, commitDetachRef, - commitProfileWork, } = ReactFiberCommitWork( config, onCommitPhaseError, @@ -468,12 +466,6 @@ export default function( commitAttachRef(nextEffect); } - if (enableProfileModeMetrics) { - if (effectTag & CommitProfile) { - commitProfileWork(nextEffect); - } - } - const next = nextEffect.nextEffect; // Ensure that we clean these up so that we don't accidentally keep them. // I'm not actually sure this matters because we can't reset firstEffect diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 6bc99fc0a4f..82f754d4dda 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -29,9 +29,9 @@ import { HostComponent, HostPortal, ContextProvider, + Profiler, } from 'shared/ReactTypeOfWork'; import { - CommitProfile, DidCapture, Incomplete, NoEffect, @@ -220,12 +220,6 @@ export default function( } function unwindInterruptedWork(interruptedWork: Fiber) { - if (enableProfileModeMetrics) { - if (interruptedWork.effectTag & CommitProfile) { - recordElapsedActualRenderTime(interruptedWork); - } - } - switch (interruptedWork.tag) { case ClassComponent: { popLegacyContextProvider(interruptedWork); @@ -246,6 +240,11 @@ export default function( case ContextProvider: popProvider(interruptedWork); break; + case Profiler: + if (enableProfileModeMetrics) { + recordElapsedActualRenderTime(interruptedWork); + } + break; default: break; } diff --git a/packages/shared/ReactTypeOfSideEffect.js b/packages/shared/ReactTypeOfSideEffect.js index 048fa3d9204..27d6aa6090e 100644 --- a/packages/shared/ReactTypeOfSideEffect.js +++ b/packages/shared/ReactTypeOfSideEffect.js @@ -10,23 +10,22 @@ export type TypeOfSideEffect = number; // Don't change these two values. They're used by React Dev Tools. -export const NoEffect = /* */ 0b000000000000; -export const PerformedWork = /* */ 0b000000000001; +export const NoEffect = /* */ 0b00000000000; +export const PerformedWork = /* */ 0b00000000001; // You can change the rest (and add more). -export const Placement = /* */ 0b000000000010; -export const Update = /* */ 0b000000000100; -export const PlacementAndUpdate = /* */ 0b000000000110; -export const Deletion = /* */ 0b000000001000; -export const ContentReset = /* */ 0b000000010000; -export const Callback = /* */ 0b000000100000; -export const DidCapture = /* */ 0b000001000000; -export const Ref = /* */ 0b000010000000; -export const Snapshot = /* */ 0b000100000000; +export const Placement = /* */ 0b00000000010; +export const Update = /* */ 0b00000000100; +export const PlacementAndUpdate = /* */ 0b00000000110; +export const Deletion = /* */ 0b00000001000; +export const ContentReset = /* */ 0b00000010000; +export const Callback = /* */ 0b00000100000; +export const DidCapture = /* */ 0b00001000000; +export const Ref = /* */ 0b00010000000; +export const Snapshot = /* */ 0b00100000000; // Union of all host effects -export const HostEffectMask = /* */ 0b000111111111; +export const HostEffectMask = /* */ 0b00111111111; -export const Incomplete = /* */ 0b001000000000; -export const ShouldCapture = /* */ 0b010000000000; -export const CommitProfile = /* */ 0b100000000000; +export const Incomplete = /* */ 0b01000000000; +export const ShouldCapture = /* */ 0b10000000000; From 6c9b1e2fa6da657659c4fca1fcc7d4d9bd07ad70 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 19:09:16 -0700 Subject: [PATCH 38/72] Bailout on Profiler if memoized and pending props are equal --- packages/react-reconciler/src/ReactFiberBeginWork.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 6ab995a262b..f8963eb20eb 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -223,6 +223,7 @@ export default function( } function updateProfiler(current, workInProgress) { + const nextProps = workInProgress.pendingProps; if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue markActualRenderTimeStarted(workInProgress); @@ -230,12 +231,10 @@ export default function( // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. workInProgress.effectTag |= Update; + } else if (workInProgress.memoizedProps === nextProps) { + return bailoutOnAlreadyFinishedWork(current, workInProgress); } - - // Never bail out early for Profilers. - // We always want to re-measure the subtree. - - const nextChildren = workInProgress.pendingProps.children; + const nextChildren = nextProps.children; reconcileChildren(current, workInProgress, nextChildren); memoizeProps(workInProgress, nextChildren); return workInProgress.child; From 081a83b2a9bf66ae39f38db92ca13974708d3cfe Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 19:25:57 -0700 Subject: [PATCH 39/72] Fixed props memoization typo for Profiler component --- packages/react-reconciler/src/ReactFiberBeginWork.js | 2 +- packages/react-reconciler/src/ReactFiberCommitWork.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index f8963eb20eb..7544fd58477 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -236,7 +236,7 @@ export default function( } const nextChildren = nextProps.children; reconcileChildren(current, workInProgress, nextChildren); - memoizeProps(workInProgress, nextChildren); + memoizeProps(workInProgress, nextProps); return workInProgress.child; } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 578f9a6d797..f9b3117b602 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -822,9 +822,9 @@ export default function( } case Profiler: { if (enableProfileModeMetrics) { - finishedWork.pendingProps.onRender.call( + finishedWork.memoizedProps.onRender.call( null, - finishedWork.pendingProps.id, + finishedWork.memoizedProps.id, finishedWork.alternate === null ? 'mount' : 'update', finishedWork.stateNode, finishedWork.treeBaseTime, From bae55f45c843da7f6bccfd629fc6950c53016f6d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 4 May 2018 21:58:32 -0700 Subject: [PATCH 40/72] Replaced onRender.call with onRender() --- packages/react-reconciler/src/ReactFiberCommitWork.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index f9b3117b602..e35219dfdf0 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -822,8 +822,8 @@ export default function( } case Profiler: { if (enableProfileModeMetrics) { - finishedWork.memoizedProps.onRender.call( - null, + const onRender = finishedWork.memoizedProps.onRender; + onRender( finishedWork.memoizedProps.id, finishedWork.alternate === null ? 'mount' : 'update', finishedWork.stateNode, From 0a4b44da791529328ca8869fc9bcd82746b196ac Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 5 May 2018 18:50:07 -0700 Subject: [PATCH 41/72] Small tweaks based on Seb's feedback --- .../src/ReactFiberCommitWork.js | 2 +- .../react-reconciler/src/ReactProfileTimer.js | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index e35219dfdf0..43dc41b02f4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -825,7 +825,7 @@ export default function( const onRender = finishedWork.memoizedProps.onRender; onRender( finishedWork.memoizedProps.id, - finishedWork.alternate === null ? 'mount' : 'update', + current === null ? 'mount' : 'update', finishedWork.stateNode, finishedWork.treeBaseTime, ); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 9518da33e46..b941db39bc4 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -47,9 +47,11 @@ export function checkActualRenderTimeStackEmpty(): void { } export function markActualRenderTimeStarted(fiber: Fiber): void { - fiber.stateNode -= now() - totalElapsedPauseTime; + const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); - push(stackCursor, fiber.stateNode, fiber); + fiber.stateNode = startTime; + + push(stackCursor, startTime, fiber); } export function recordElapsedActualRenderTime(fiber: Fiber): void { @@ -78,11 +80,11 @@ export function pauseActualRenderTimerIfRunning(): void { * If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated. */ -let baseStartTime: number | null = null; +let baseStartTime: number = -1; export function getElapsedBaseRenderTime(): number { if (__DEV__) { - if (baseStartTime === null) { + if (baseStartTime === -1) { warning( false, 'Cannot read elapsed time when base timer is not running. ' + @@ -92,16 +94,16 @@ export function getElapsedBaseRenderTime(): number { } } - return baseStartTime === null ? 0 : now() - baseStartTime; + return baseStartTime === -1 ? 0 : now() - baseStartTime; } export function isBaseRenderTimerRunning(): boolean { - return baseStartTime !== null; + return baseStartTime !== -1; } export function startBaseRenderTimer(): void { if (__DEV__) { - if (baseStartTime !== null) { + if (baseStartTime !== -1) { warning( false, 'Cannot start base timer that is already running. ' + @@ -116,7 +118,7 @@ export function startBaseRenderTimer(): void { export function stopBaseRenderTimer(): void { if (__DEV__) { - if (baseStartTime === null) { + if (baseStartTime === -1) { warning( false, 'Cannot stop a base timer is not running. ' + @@ -126,5 +128,5 @@ export function stopBaseRenderTimer(): void { } } - baseStartTime = null; + baseStartTime = -1; } From d1c0ad1a8c0f96bc5a18486b9062c8d26beccb90 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 08:34:17 -0700 Subject: [PATCH 42/72] Removed unnecessary conditionals --- packages/react-reconciler/src/ReactFiberScheduler.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index d6a88f68c51..b5b173db929 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -963,9 +963,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - if (nextUnitOfWork !== null) { - pauseActualRenderTimerIfRunning(); - } + pauseActualRenderTimerIfRunning(); } } } @@ -1730,9 +1728,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - if (nextUnitOfWork !== null) { - pauseActualRenderTimerIfRunning(); - } + pauseActualRenderTimerIfRunning(); } } } From 417c9b4a01e517ead809212c88b33efb406c9e0a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 08:57:44 -0700 Subject: [PATCH 43/72] Updated ReactProfileTimer to use HostConfig now() timer --- .../src/ReactFiberBeginWork.js | 6 ++--- .../src/ReactFiberCompleteWork.js | 3 ++- .../src/ReactFiberScheduler.js | 11 ++++---- .../src/ReactFiberUnwindWork.js | 7 +++-- .../react-reconciler/src/ReactProfileTimer.js | 26 +++++-------------- .../src/ReactTestRenderer.js | 12 ++++++--- .../__tests__/ReactProfiler-test.internal.js | 20 ++++++-------- 7 files changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 7544fd58477..72ed88a2c2c 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -96,7 +96,7 @@ export default function( scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void, computeExpirationForFiber: (fiber: Fiber) => ExpirationTime, ) { - const {shouldSetTextContent, shouldDeprioritizeSubtree} = config; + const {now, shouldSetTextContent, shouldDeprioritizeSubtree} = config; const {pushHostContext, pushHostContainer} = hostContext; @@ -226,7 +226,7 @@ export default function( const nextProps = workInProgress.pendingProps; if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue - markActualRenderTimeStarted(workInProgress); + markActualRenderTimeStarted(workInProgress, now); // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. @@ -1133,7 +1133,7 @@ export default function( pushProvider(workInProgress); break; case Profiler: - markActualRenderTimeStarted(workInProgress); + markActualRenderTimeStarted(workInProgress, now); break; } // TODO: What if this is currently in progress? diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 57150aeb866..c859a50f1af 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -58,6 +58,7 @@ export default function( createTextInstance, appendInitialChild, finalizeInitialChildren, + now, prepareUpdate, mutation, persistence, @@ -194,7 +195,7 @@ export default function( function updateProfiler(workInProgress: Fiber) { if (enableProfileModeMetrics) { - recordElapsedActualRenderTime(workInProgress); + recordElapsedActualRenderTime(workInProgress, now); } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index b5b173db929..936167632ae 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -204,6 +204,7 @@ export default function( createRootErrorUpdate, createClassErrorUpdate, } = ReactFiberUnwindWork( + config, hostContext, legacyContext, newContext, @@ -912,12 +913,12 @@ export default function( let next; if (enableProfileModeMetrics) { - startBaseRenderTimer(); + startBaseRenderTimer(now); next = beginWork(current, workInProgress, nextRenderExpirationTime); // Update "base" time if the render wasn't bailed out on. if (isBaseRenderTimerRunning()) { - workInProgress.selfBaseTime = getElapsedBaseRenderTime(); + workInProgress.selfBaseTime = getElapsedBaseRenderTime(now); stopBaseRenderTimer(); } } else { @@ -963,7 +964,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - pauseActualRenderTimerIfRunning(); + pauseActualRenderTimerIfRunning(now); } } } @@ -1572,7 +1573,7 @@ export default function( deadline = dl; if (enableProfileModeMetrics) { - resumeActualRenderTimerIfPaused(); + resumeActualRenderTimerIfPaused(now); } // Keep working on roots until there's no more work, or until the we reach @@ -1728,7 +1729,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - pauseActualRenderTimerIfRunning(); + pauseActualRenderTimerIfRunning(now); } } } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 82f754d4dda..215d8104b02 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -7,6 +7,7 @@ * @flow */ +import type {HostConfig} from 'react-reconciler'; import type {Fiber} from './ReactFiber'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {HostContext} from './ReactFiberHostContext'; @@ -43,7 +44,8 @@ import { enableProfileModeMetrics, } from 'shared/ReactFeatureFlags'; -export default function( +export default function( + config: HostConfig, hostContext: HostContext, legacyContext: LegacyContext, newContext: NewContext, @@ -56,6 +58,7 @@ export default function( isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean, onUncaughtError: (error: mixed) => void, ) { + const {now} = config; const {popHostContainer, popHostContext} = hostContext; const { popContextProvider: popLegacyContextProvider, @@ -242,7 +245,7 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { - recordElapsedActualRenderTime(interruptedWork); + recordElapsedActualRenderTime(interruptedWork, now); } break; default: diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index b941db39bc4..50df5a30ef3 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -13,19 +13,7 @@ import type {StackCursor} from './ReactFiberStack'; import warning from 'fbjs/lib/warning'; import ReactFiberStack from './ReactFiberStack'; -const hasNativePerformanceNow = - typeof performance === 'object' && typeof performance.now === 'function'; - -let now; -if (hasNativePerformanceNow) { - now = function() { - return performance.now(); - }; -} else { - now = function() { - return Date.now(); - }; -} +type Now = () => number; /** * The "actual" render time is total time required to render the descendants of a Profiler component. @@ -46,7 +34,7 @@ export function checkActualRenderTimeStackEmpty(): void { } } -export function markActualRenderTimeStarted(fiber: Fiber): void { +export function markActualRenderTimeStarted(fiber: Fiber, now: Now): void { const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); fiber.stateNode = startTime; @@ -54,20 +42,20 @@ export function markActualRenderTimeStarted(fiber: Fiber): void { push(stackCursor, startTime, fiber); } -export function recordElapsedActualRenderTime(fiber: Fiber): void { +export function recordElapsedActualRenderTime(fiber: Fiber, now: Now): void { pop(stackCursor, fiber); fiber.stateNode += now() - totalElapsedPauseTime; } -export function resumeActualRenderTimerIfPaused(): void { +export function resumeActualRenderTimerIfPaused(now: Now): void { if (timerPausedAt > 0) { totalElapsedPauseTime += now() - timerPausedAt; timerPausedAt = 0; } } -export function pauseActualRenderTimerIfRunning(): void { +export function pauseActualRenderTimerIfRunning(now: Now): void { if (timerPausedAt === 0) { timerPausedAt = now(); } @@ -82,7 +70,7 @@ export function pauseActualRenderTimerIfRunning(): void { let baseStartTime: number = -1; -export function getElapsedBaseRenderTime(): number { +export function getElapsedBaseRenderTime(now: Now): number { if (__DEV__) { if (baseStartTime === -1) { warning( @@ -101,7 +89,7 @@ export function isBaseRenderTimerRunning(): boolean { return baseStartTime !== -1; } -export function startBaseRenderTimer(): void { +export function startBaseRenderTimer(now: Now): void { if (__DEV__) { if (baseStartTime !== -1) { warning( diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 37ac8458116..d109e649101 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -120,7 +120,7 @@ function removeChild( } // Current virtual time -let currentTime: number = 0; +let nowImplementation = () => 0; let scheduledCallback: ((deadline: Deadline) => mixed) | null = null; let yieldedValues: Array | null = null; @@ -222,9 +222,9 @@ const TestRenderer = ReactFiberReconciler({ getPublicInstance, - now(): number { - return currentTime; - }, + // This approach enables `now` to be mocked by tests, + // Even after the reconciler has initialized and read host config values. + now: () => nowImplementation(), mutation: { commitUpdate( @@ -764,6 +764,10 @@ const ReactTestRendererFiber = { /* eslint-disable camelcase */ unstable_batchedUpdates: batchedUpdates, /* eslint-enable camelcase */ + + unstable_setNowImplementation(implementation: () => number): void { + nowImplementation = implementation; + }, }; export default ReactTestRendererFiber; diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index c2dff7d852b..c8aea80f014 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -109,21 +109,19 @@ describe('Profiler', () => { let AdvanceTime; let advanceTimeBy; - beforeEach(() => { - jest.resetModules(); - + const mockNowForTests = () => { let currentTime = 0; - global.performance = { - now: () => { - return currentTime; - }, - }; + ReactTestRenderer.unstable_setNowImplementation(() => currentTime); advanceTimeBy = amount => { currentTime += amount; }; + }; + + beforeEach(() => { + jest.resetModules(); - // Import after polyfill loadModules(); + mockNowForTests(); AdvanceTime = class extends React.Component { static defaultProps = { @@ -140,9 +138,6 @@ describe('Profiler', () => { } }; }); - afterEach(() => { - delete global.performance; - }); it('does not invoke the callback until the commit phase', () => { const callback = jest.fn(); @@ -550,6 +545,7 @@ describe('Profiler', () => { loadModules({ replayFailedUnitOfWorkWithInvokeGuardedCallback: flagEnabled, }); + mockNowForTests(); }); it('should accumulate "actual" time after an error handled by componentDidCatch()', () => { From 1f132dbbb360a8b92268c1b6548ddc3f10bb27f4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 08:58:50 -0700 Subject: [PATCH 44/72] Fixed funky stack cursor Flow typing --- packages/react-reconciler/src/ReactProfileTimer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 50df5a30ef3..cf25a231d4c 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -24,7 +24,7 @@ type Now = () => number; const {checkThatStackIsEmpty, createCursor, push, pop} = ReactFiberStack(); -let stackCursor: StackCursor = createCursor(null); +let stackCursor: StackCursor = createCursor(0); let timerPausedAt: number = 0; let totalElapsedPauseTime: number = 0; From 7ea22771c8d1e4e1bae243ae4237ef5d4b703da7 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:06:10 -0700 Subject: [PATCH 45/72] Combined expiration and base render time bubbling into one loop --- .../src/ReactFiberScheduler.js | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 936167632ae..77a0440368a 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -712,31 +712,51 @@ export default function( // TODO: Calls need to visit stateNode // Bubble up the earliest expiration time. - let child = workInProgress.child; - while (child !== null) { - if ( - child.expirationTime !== NoWork && - (newExpirationTime === NoWork || - newExpirationTime > child.expirationTime) - ) { - newExpirationTime = child.expirationTime; - } - child = child.sibling; - } - workInProgress.expirationTime = newExpirationTime; - + // (And "base" render timers if that feature flag is enabled) if (enableProfileModeMetrics) { if (workInProgress.mode & ProfileMode) { - // Bubble up "base" render times if we're within a Profiler let treeBaseTime = workInProgress.selfBaseTime; - child = workInProgress.child; + let child = workInProgress.child; while (child !== null) { treeBaseTime += child.treeBaseTime; + if ( + child.expirationTime !== NoWork && + (newExpirationTime === NoWork || + newExpirationTime > child.expirationTime) + ) { + newExpirationTime = child.expirationTime; + } child = child.sibling; } workInProgress.treeBaseTime = treeBaseTime; + } else { + let child = workInProgress.child; + while (child !== null) { + if ( + child.expirationTime !== NoWork && + (newExpirationTime === NoWork || + newExpirationTime > child.expirationTime) + ) { + newExpirationTime = child.expirationTime; + } + child = child.sibling; + } + } + } else { + let child = workInProgress.child; + while (child !== null) { + if ( + child.expirationTime !== NoWork && + (newExpirationTime === NoWork || + newExpirationTime > child.expirationTime) + ) { + newExpirationTime = child.expirationTime; + } + child = child.sibling; } } + + workInProgress.expirationTime = newExpirationTime; } function completeUnitOfWork(workInProgress: Fiber): Fiber | null { From 13dae8b3e5eb1967c9b8a19b91f70f3dc512c8d9 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:11:46 -0700 Subject: [PATCH 46/72] Moved render timing check to be better colocated with uer timing check --- packages/react-reconciler/src/ReactFiberScheduler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 77a0440368a..dfdc485521c 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -1592,14 +1592,14 @@ export default function( ) { deadline = dl; - if (enableProfileModeMetrics) { - resumeActualRenderTimerIfPaused(now); - } - // Keep working on roots until there's no more work, or until the we reach // the deadline. findHighestPriorityRoot(); + if (enableProfileModeMetrics) { + resumeActualRenderTimerIfPaused(now); + } + if (enableUserTimingAPI && deadline !== null) { const didExpire = nextFlushedExpirationTime < recalculateCurrentTime(); const timeout = expirationTimeToMs(nextFlushedExpirationTime); From ae8b527e275bf0e82e580487d1300721d6c2faf3 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:13:06 -0700 Subject: [PATCH 47/72] Wrapped timer call in enableProfileModeMetrics conditional --- packages/react-reconciler/src/ReactFiberBeginWork.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 72ed88a2c2c..42ac6ca78d2 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -1133,7 +1133,9 @@ export default function( pushProvider(workInProgress); break; case Profiler: - markActualRenderTimeStarted(workInProgress, now); + if (enableProfileModeMetrics) { + markActualRenderTimeStarted(workInProgress, now); + } break; } // TODO: What if this is currently in progress? From 305db33e028ff21bc0585e9081f30981bc466064 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:17:01 -0700 Subject: [PATCH 48/72] Don't prevent bailout unnecessarily for Profiler component --- packages/react-reconciler/src/ReactFiberBeginWork.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 42ac6ca78d2..af70e4ccabc 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -231,7 +231,8 @@ export default function( // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. workInProgress.effectTag |= Update; - } else if (workInProgress.memoizedProps === nextProps) { + } + if (workInProgress.memoizedProps === nextProps) { return bailoutOnAlreadyFinishedWork(current, workInProgress); } const nextChildren = nextProps.children; From b8782e530098ea59e129b4da7cad5d82cf221614 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:18:46 -0700 Subject: [PATCH 49/72] Fixed Flow type for selfBaseTime and treeBaseTime in Fiber --- packages/react-reconciler/src/ReactFiber.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index cc6313da0f3..a5130d14798 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -154,8 +154,8 @@ export type Fiber = {| alternate: Fiber | null, // Profiling metrics - selfBaseTime: number | null, - treeBaseTime: number | null, + selfBaseTime: number, + treeBaseTime: number, // Conceptual aliases // workInProgress : Fiber -> alternate The alternate used for reuse happens From 4e54ff63d1396e15457637b97d14aaee8fc8a308 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:25:15 -0700 Subject: [PATCH 50/72] Refactored base render timer logic a bit to remove redundant check --- .../src/ReactFiberBeginWork.js | 8 ++--- .../src/ReactFiberScheduler.js | 15 ++++---- .../react-reconciler/src/ReactProfileTimer.js | 35 ++++--------------- 3 files changed, 17 insertions(+), 41 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index af70e4ccabc..8b88ebc5865 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -70,7 +70,7 @@ import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; import { - stopBaseRenderTimer, + stopBaseRenderTimerIfRunning, markActualRenderTimeStarted, } from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; @@ -372,7 +372,7 @@ export default function( nextChildren = null; if (enableProfileModeMetrics) { - stopBaseRenderTimer(); + stopBaseRenderTimerIfRunning(); } } else { if (__DEV__) { @@ -1086,7 +1086,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - stopBaseRenderTimer(); + stopBaseRenderTimerIfRunning(); } // TODO: We should ideally be able to bail out early if the children have no @@ -1112,7 +1112,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - stopBaseRenderTimer(); + stopBaseRenderTimerIfRunning(); } // TODO: Handle HostComponent tags here as well and call pushHostContext()? diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index dfdc485521c..edb349f0289 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -47,12 +47,11 @@ import { } from 'shared/ReactFeatureFlags'; import { checkActualRenderTimeStackEmpty, - getElapsedBaseRenderTime, - isBaseRenderTimerRunning, pauseActualRenderTimerIfRunning, + recordElapsedBaseRenderTimeIfRunning, resumeActualRenderTimerIfPaused, startBaseRenderTimer, - stopBaseRenderTimer, + stopBaseRenderTimerIfRunning, } from './ReactProfileTimer'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; @@ -319,7 +318,7 @@ export default function( if (enableProfileModeMetrics) { // Stop "base" render timer again (after the re-thrown error). - stopBaseRenderTimer(); + stopBaseRenderTimerIfRunning(); } } else { // If the begin phase did not fail the second time, set this pointer @@ -937,10 +936,8 @@ export default function( next = beginWork(current, workInProgress, nextRenderExpirationTime); // Update "base" time if the render wasn't bailed out on. - if (isBaseRenderTimerRunning()) { - workInProgress.selfBaseTime = getElapsedBaseRenderTime(now); - stopBaseRenderTimer(); - } + recordElapsedBaseRenderTimeIfRunning(workInProgress, now); + stopBaseRenderTimerIfRunning(); } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); } @@ -1030,7 +1027,7 @@ export default function( } catch (thrownValue) { if (enableProfileModeMetrics) { // Stop "base" render timer in the event of an error. - stopBaseRenderTimer(); + stopBaseRenderTimerIfRunning(); } if (nextUnitOfWork === null) { diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index cf25a231d4c..5eddd36401e 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -70,23 +70,13 @@ export function pauseActualRenderTimerIfRunning(now: Now): void { let baseStartTime: number = -1; -export function getElapsedBaseRenderTime(now: Now): number { - if (__DEV__) { - if (baseStartTime === -1) { - warning( - false, - 'Cannot read elapsed time when base timer is not running. ' + - 'This error is likely caused by a bug in React. ' + - 'Please file an issue.', - ); - } +export function recordElapsedBaseRenderTimeIfRunning( + fiber: Fiber, + now: Now, +): void { + if (baseStartTime !== -1) { + fiber.selfBaseTime = now() - baseStartTime; } - - return baseStartTime === -1 ? 0 : now() - baseStartTime; -} - -export function isBaseRenderTimerRunning(): boolean { - return baseStartTime !== -1; } export function startBaseRenderTimer(now: Now): void { @@ -104,17 +94,6 @@ export function startBaseRenderTimer(now: Now): void { baseStartTime = now(); } -export function stopBaseRenderTimer(): void { - if (__DEV__) { - if (baseStartTime === -1) { - warning( - false, - 'Cannot stop a base timer is not running. ' + - 'This error is likely caused by a bug in React. ' + - 'Please file an issue.', - ); - } - } - +export function stopBaseRenderTimerIfRunning(): void { baseStartTime = -1; } From 45cd176e11158abc465a4b50ed11b704b2fbd8fa Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:34:53 -0700 Subject: [PATCH 51/72] Reset totalElapsedPauseTime after commit (at the end of commitRoot) --- .../react-reconciler/src/ReactFiberScheduler.js | 6 ++++-- .../react-reconciler/src/ReactProfileTimer.js | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index edb349f0289..2994c168f55 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -49,6 +49,7 @@ import { checkActualRenderTimeStackEmpty, pauseActualRenderTimerIfRunning, recordElapsedBaseRenderTimeIfRunning, + resetActualRenderTimer, resumeActualRenderTimerIfPaused, startBaseRenderTimer, stopBaseRenderTimerIfRunning, @@ -660,10 +661,11 @@ export default function( } } - if (__DEV__) { - if (enableProfileModeMetrics) { + if (enableProfileModeMetrics) { + if (__DEV__) { checkActualRenderTimeStackEmpty(); } + resetActualRenderTimer(); } isCommitting = false; diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 5eddd36401e..0661ca800a1 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -42,12 +42,22 @@ export function markActualRenderTimeStarted(fiber: Fiber, now: Now): void { push(stackCursor, startTime, fiber); } +export function pauseActualRenderTimerIfRunning(now: Now): void { + if (timerPausedAt === 0) { + timerPausedAt = now(); + } +} + export function recordElapsedActualRenderTime(fiber: Fiber, now: Now): void { pop(stackCursor, fiber); fiber.stateNode += now() - totalElapsedPauseTime; } +export function resetActualRenderTimer(): void { + totalElapsedPauseTime = 0; +} + export function resumeActualRenderTimerIfPaused(now: Now): void { if (timerPausedAt > 0) { totalElapsedPauseTime += now() - timerPausedAt; @@ -55,12 +65,6 @@ export function resumeActualRenderTimerIfPaused(now: Now): void { } } -export function pauseActualRenderTimerIfRunning(now: Now): void { - if (timerPausedAt === 0) { - timerPausedAt = now(); - } -} - /** * The "base" render time is the duration of the “begin” phase of work for a particular fiber. * This time is measured and stored on each fiber. From 283652af4209c67a303cf8b47ead8728e08a30c7 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 09:53:06 -0700 Subject: [PATCH 52/72] Refactored actual render timer to use shared ReactFiberStack --- .../src/ReactFiberBeginWork.js | 17 ++-- .../src/ReactFiberCompleteWork.js | 8 +- .../src/ReactFiberScheduler.js | 28 +++++-- .../src/ReactFiberUnwindWork.js | 8 +- .../react-reconciler/src/ReactProfileTimer.js | 83 ++++++++++++------- 5 files changed, 93 insertions(+), 51 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 8b88ebc5865..7c00e1f7b20 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -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 {ActualRenderTimer} from './ReactProfileTimer'; import checkPropTypes from 'prop-types/checkPropTypes'; import { @@ -69,10 +70,7 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import { - stopBaseRenderTimerIfRunning, - markActualRenderTimeStarted, -} from './ReactProfileTimer'; +import {stopBaseRenderTimerIfRunning} from './ReactProfileTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -95,6 +93,7 @@ export default function( hydrationContext: HydrationContext, scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void, computeExpirationForFiber: (fiber: Fiber) => ExpirationTime, + actualRenderTimer: ActualRenderTimer | null, ) { const {now, shouldSetTextContent, shouldDeprioritizeSubtree} = config; @@ -226,7 +225,10 @@ export default function( const nextProps = workInProgress.pendingProps; if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue - markActualRenderTimeStarted(workInProgress, now); + ((actualRenderTimer: any): ActualRenderTimer).markActualRenderTimeStarted( + workInProgress, + now, + ); // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. @@ -1135,7 +1137,10 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { - markActualRenderTimeStarted(workInProgress, now); + ((actualRenderTimer: any): ActualRenderTimer).markActualRenderTimeStarted( + workInProgress, + now, + ); } break; } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index c859a50f1af..edd33b12b53 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -15,6 +15,7 @@ import type {LegacyContext} from './ReactFiberContext'; import type {NewContext} from './ReactFiberNewContext'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {FiberRoot} from './ReactFiberRoot'; +import type {ActualRenderTimer} from './ReactProfileTimer'; import { enableMutatingReconciler, @@ -41,7 +42,6 @@ import { Profiler, } from 'shared/ReactTypeOfWork'; import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect'; -import {recordElapsedActualRenderTime} from './ReactProfileTimer'; import invariant from 'fbjs/lib/invariant'; import {reconcileChildFibers} from './ReactChildFiber'; @@ -52,6 +52,7 @@ export default function( legacyContext: LegacyContext, newContext: NewContext, hydrationContext: HydrationContext, + actualRenderTimer: ActualRenderTimer | null, ) { const { createInstance, @@ -195,7 +196,10 @@ export default function( function updateProfiler(workInProgress: Fiber) { if (enableProfileModeMetrics) { - recordElapsedActualRenderTime(workInProgress, now); + ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( + workInProgress, + now, + ); } } diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 2994c168f55..9a5bfd8669d 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber'; import type {FiberRoot, Batch} from './ReactFiberRoot'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {ExpirationTime} from './ReactFiberExpirationTime'; +import type {ActualRenderTimer} from './ReactProfileTimer'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; @@ -46,11 +47,8 @@ import { warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; import { - checkActualRenderTimeStackEmpty, - pauseActualRenderTimerIfRunning, + createActualRenderTimer, recordElapsedBaseRenderTimeIfRunning, - resetActualRenderTimer, - resumeActualRenderTimerIfPaused, startBaseRenderTimer, stopBaseRenderTimerIfRunning, } from './ReactProfileTimer'; @@ -172,6 +170,9 @@ export default function( const hostContext = ReactFiberHostContext(config, stack); const legacyContext = ReactFiberLegacyContext(stack); const newContext = ReactFiberNewContext(stack); + const actualRenderTimer: ActualRenderTimer | null = enableProfileModeMetrics + ? createActualRenderTimer(stack) + : null; const {popHostContext, popHostContainer} = hostContext; const { popTopLevelContextObject: popTopLevelLegacyContextObject, @@ -189,6 +190,7 @@ export default function( hydrationContext, scheduleWork, computeExpirationForFiber, + actualRenderTimer, ); const {completeWork} = ReactFiberCompleteWork( config, @@ -196,6 +198,7 @@ export default function( legacyContext, newContext, hydrationContext, + actualRenderTimer, ); const { throwException, @@ -212,6 +215,7 @@ export default function( markLegacyErrorBoundaryAsFailed, isAlreadyFailedLegacyErrorBoundary, onUncaughtError, + actualRenderTimer, ); const { commitBeforeMutationLifeCycles, @@ -663,9 +667,9 @@ export default function( if (enableProfileModeMetrics) { if (__DEV__) { - checkActualRenderTimeStackEmpty(); + ((actualRenderTimer: any): ActualRenderTimer).checkActualRenderTimeStackEmpty(); } - resetActualRenderTimer(); + ((actualRenderTimer: any): ActualRenderTimer).resetActualRenderTimer(); } isCommitting = false; @@ -983,7 +987,9 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - pauseActualRenderTimerIfRunning(now); + ((actualRenderTimer: any): ActualRenderTimer).pauseActualRenderTimerIfRunning( + now, + ); } } } @@ -1596,7 +1602,9 @@ export default function( findHighestPriorityRoot(); if (enableProfileModeMetrics) { - resumeActualRenderTimerIfPaused(now); + ((actualRenderTimer: any): ActualRenderTimer).resumeActualRenderTimerIfPaused( + now, + ); } if (enableUserTimingAPI && deadline !== null) { @@ -1748,7 +1756,9 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - pauseActualRenderTimerIfRunning(now); + ((actualRenderTimer: any): ActualRenderTimer).pauseActualRenderTimerIfRunning( + now, + ); } } } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 215d8104b02..0f08a25baa5 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -14,6 +14,7 @@ import type {HostContext} from './ReactFiberHostContext'; import type {LegacyContext} from './ReactFiberContext'; import type {NewContext} from './ReactFiberNewContext'; import type {CapturedValue} from './ReactCapturedValue'; +import type {ActualRenderTimer} from './ReactProfileTimer'; import type {Update} from './ReactUpdateQueue'; import {createCapturedValue} from './ReactCapturedValue'; @@ -38,7 +39,6 @@ import { NoEffect, ShouldCapture, } from 'shared/ReactTypeOfSideEffect'; -import {recordElapsedActualRenderTime} from './ReactProfileTimer'; import { enableGetDerivedStateFromCatch, enableProfileModeMetrics, @@ -57,6 +57,7 @@ export default function( markLegacyErrorBoundaryAsFailed: (instance: mixed) => void, isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean, onUncaughtError: (error: mixed) => void, + actualRenderTimer: ActualRenderTimer | null, ) { const {now} = config; const {popHostContainer, popHostContext} = hostContext; @@ -245,7 +246,10 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { - recordElapsedActualRenderTime(interruptedWork, now); + ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( + interruptedWork, + now, + ); } break; default: diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index 0661ca800a1..d6ffd9a110f 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -8,10 +8,9 @@ */ import type {Fiber} from './ReactFiber'; -import type {StackCursor} from './ReactFiberStack'; +import type {StackCursor, Stack} from './ReactFiberStack'; import warning from 'fbjs/lib/warning'; -import ReactFiberStack from './ReactFiberStack'; type Now = () => number; @@ -22,47 +21,67 @@ type Now = () => number; * It is paused (and accumulated) in the event of an interruption or an aborted render. */ -const {checkThatStackIsEmpty, createCursor, push, pop} = ReactFiberStack(); - -let stackCursor: StackCursor = createCursor(0); -let timerPausedAt: number = 0; -let totalElapsedPauseTime: number = 0; - -export function checkActualRenderTimeStackEmpty(): void { - if (__DEV__) { - checkThatStackIsEmpty(); +export type ActualRenderTimer = { + checkActualRenderTimeStackEmpty(): void, + markActualRenderTimeStarted(fiber: Fiber, now: Now): void, + pauseActualRenderTimerIfRunning(now: Now): void, + recordElapsedActualRenderTime(fiber: Fiber, now: Now): void, + resetActualRenderTimer(): void, + resumeActualRenderTimerIfPaused(now: Now): void, +}; + +export function createActualRenderTimer(stack: Stack): ActualRenderTimer { + const {checkThatStackIsEmpty, createCursor, push, pop} = stack; + + let stackCursor: StackCursor = createCursor(0); + let timerPausedAt: number = 0; + let totalElapsedPauseTime: number = 0; + + function checkActualRenderTimeStackEmpty(): void { + if (__DEV__) { + checkThatStackIsEmpty(); + } } -} -export function markActualRenderTimeStarted(fiber: Fiber, now: Now): void { - const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); + function markActualRenderTimeStarted(fiber: Fiber, now: Now): void { + const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); - fiber.stateNode = startTime; + fiber.stateNode = startTime; - push(stackCursor, startTime, fiber); -} + push(stackCursor, startTime, fiber); + } -export function pauseActualRenderTimerIfRunning(now: Now): void { - if (timerPausedAt === 0) { - timerPausedAt = now(); + function pauseActualRenderTimerIfRunning(now: Now): void { + if (timerPausedAt === 0) { + timerPausedAt = now(); + } } -} -export function recordElapsedActualRenderTime(fiber: Fiber, now: Now): void { - pop(stackCursor, fiber); + function recordElapsedActualRenderTime(fiber: Fiber, now: Now): void { + pop(stackCursor, fiber); - fiber.stateNode += now() - totalElapsedPauseTime; -} + fiber.stateNode += now() - totalElapsedPauseTime; + } -export function resetActualRenderTimer(): void { - totalElapsedPauseTime = 0; -} + function resetActualRenderTimer(): void { + totalElapsedPauseTime = 0; + } -export function resumeActualRenderTimerIfPaused(now: Now): void { - if (timerPausedAt > 0) { - totalElapsedPauseTime += now() - timerPausedAt; - timerPausedAt = 0; + function resumeActualRenderTimerIfPaused(now: Now): void { + if (timerPausedAt > 0) { + totalElapsedPauseTime += now() - timerPausedAt; + timerPausedAt = 0; + } } + + return { + checkActualRenderTimeStackEmpty, + markActualRenderTimeStarted, + pauseActualRenderTimerIfRunning, + recordElapsedActualRenderTime, + resetActualRenderTimer, + resumeActualRenderTimerIfPaused, + }; } /** From f834d670f5bfd0e18b6a079b98a2bdd5c0d2895d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 10:05:45 -0700 Subject: [PATCH 53/72] Removed unnecessary function call --- .../src/ReactFiberCompleteWork.js | 17 ++++++----------- .../react-reconciler/src/ReactProfileTimer.js | 4 ---- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index edd33b12b53..14d87f8b756 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -194,15 +194,6 @@ export default function( } } - function updateProfiler(workInProgress: Fiber) { - if (enableProfileModeMetrics) { - ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( - workInProgress, - now, - ); - } - } - let updateHostContainer; let updateHostComponent; let updateHostText; @@ -418,7 +409,6 @@ export default function( renderExpirationTime: ExpirationTime, ): Fiber | null { const newProps = workInProgress.pendingProps; - switch (workInProgress.tag) { case FunctionalComponent: return null; @@ -607,7 +597,12 @@ export default function( case Mode: return null; case Profiler: - updateProfiler(workInProgress); + if (enableProfileModeMetrics) { + ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( + workInProgress, + now, + ); + } return null; case HostPortal: popHostContainer(workInProgress); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfileTimer.js index d6ffd9a110f..61c66f493d5 100644 --- a/packages/react-reconciler/src/ReactProfileTimer.js +++ b/packages/react-reconciler/src/ReactProfileTimer.js @@ -45,9 +45,7 @@ export function createActualRenderTimer(stack: Stack): ActualRenderTimer { function markActualRenderTimeStarted(fiber: Fiber, now: Now): void { const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); - fiber.stateNode = startTime; - push(stackCursor, startTime, fiber); } @@ -59,7 +57,6 @@ export function createActualRenderTimer(stack: Stack): ActualRenderTimer { function recordElapsedActualRenderTime(fiber: Fiber, now: Now): void { pop(stackCursor, fiber); - fiber.stateNode += now() - totalElapsedPauseTime; } @@ -113,7 +110,6 @@ export function startBaseRenderTimer(now: Now): void { ); } } - baseStartTime = now(); } From 2977521c286553d58fef031def28e7d9113b53e8 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 10:10:56 -0700 Subject: [PATCH 54/72] Fixed Flow type for ReactFiber selfBaseTime/treeBaseTime --- packages/react-reconciler/src/ReactFiber.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index a5130d14798..82b5c4316ac 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -154,8 +154,8 @@ export type Fiber = {| alternate: Fiber | null, // Profiling metrics - selfBaseTime: number, - treeBaseTime: number, + selfBaseTime?: number, + treeBaseTime?: number, // Conceptual aliases // workInProgress : Fiber -> alternate The alternate used for reuse happens From abf999df1da4741c10abd0f785ae0d4c33c37979 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 16:58:32 -0700 Subject: [PATCH 55/72] Added an additional pause/resume test --- .../src/ReactFiberCommitWork.js | 2 + .../src/ReactFiberUnwindWork.js | 3 + .../__tests__/ReactProfiler-test.internal.js | 74 +++++++++++++++++-- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 43dc41b02f4..2c8840a768f 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber'; import type {FiberRoot} from './ReactFiber'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {CapturedValue, CapturedError} from './ReactCapturedValue'; +import type {ActualRenderTimer} from './ReactProfileTimer'; import { enableMutatingReconciler, @@ -106,6 +107,7 @@ export default function( ) => ExpirationTime, markLegacyErrorBoundaryAsFailed: (instance: mixed) => void, recalculateCurrentTime: () => ExpirationTime, + actualRenderTimer: ActualRenderTimer | null, ) { const {getPublicInstance, mutation, persistence} = config; diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 0f08a25baa5..5f40a9683ea 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -246,6 +246,9 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { + ((actualRenderTimer: any): ActualRenderTimer).resumeActualRenderTimerIfPaused( + now, + ); ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( interruptedWork, now, diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index c8aea80f014..bcd1d76ae5b 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -487,7 +487,7 @@ describe('Profiler', () => { expect(outerCall[3]).toBe(35); // "base" time }); - it('should accumulate "actual" time after a higher priority interruption', () => { + it('should accumulate "actual" time if initial render is interrupted with higher priority work', () => { const callback = jest.fn(); const Yield = ({renderTime, value}) => { @@ -504,8 +504,72 @@ describe('Profiler', () => { {unstable_isAsync: true}, ); - // Render partially, but don't finish. + // Render a partially update, but don't finish. + // This partial render should take 10ms of simulated time. + renderer.unstable_flushThrough(['first']); + + expect(callback).toHaveBeenCalledTimes(0); + + // Simulate time moving forward while frame is paused. + advanceTimeBy(30); + + // Interrupt with higher priority work. + // The interrupted work simulates an additional 5ms of time. + renderer.unstable_flushSync(() => { + renderer.update( + + + , + ); + }); + + // The initial work was thrown away in this case, + // So the "actual" and "base" times should only include the final rendered tree times. + expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; + expect(call[2]).toBe(5); // "actual" time + expect(call[3]).toBe(5); // "base" time + + // Verify no more unexpected callbacks from low priority work + renderer.unstable_flushAll(); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should accumulate "actual" time if an update is interrupted with higher priority work', () => { + const callback = jest.fn(); + + const Yield = ({renderTime, value}) => { + advanceTimeBy(renderTime); + renderer.unstable_yield(value); + return null; + }; + + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isAsync: true}, + ); + + // Render everything initially. + renderer.unstable_flushAll(); + + expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; + expect(call[2]).toBe(30); // "actual" time + expect(call[3]).toBe(30); // "base" time + + callback.mockReset(); + + // Render a partially update, but don't finish. // This partial render should take 10ms of simulated time. + renderer.update( + + + + , + ); renderer.unstable_flushThrough(['first']); expect(callback).toHaveBeenCalledTimes(0); @@ -526,9 +590,9 @@ describe('Profiler', () => { // Verify that the "actual" time includes both durations above, // And the "base" time includes only the final rendered tree times. expect(callback).toHaveBeenCalledTimes(1); - // TODO (bvaughn) Enable this test once resuming is supported - // expect(callback.mock.calls[0][2]).toBe(15); // "actual" time - expect(callback.mock.calls[0][3]).toBe(5); // "base" time + call = callback.mock.calls[0]; + expect(call[2]).toBe(15); // "actual" time + expect(call[3]).toBe(5); // "base" time // Verify no more unexpected callbacks from low priority work renderer.unstable_flushAll(); From f551e4d89ae0f7cd00b9045cd0a27585cf7b8294 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 17:00:48 -0700 Subject: [PATCH 56/72] Renamed ReactProfileTimer -> ReactProfilerTimer --- packages/react-reconciler/src/ReactFiberBeginWork.js | 4 ++-- packages/react-reconciler/src/ReactFiberCommitWork.js | 2 +- packages/react-reconciler/src/ReactFiberCompleteWork.js | 2 +- packages/react-reconciler/src/ReactFiberScheduler.js | 4 ++-- packages/react-reconciler/src/ReactFiberUnwindWork.js | 3 ++- .../src/{ReactProfileTimer.js => ReactProfilerTimer.js} | 0 6 files changed, 8 insertions(+), 7 deletions(-) rename packages/react-reconciler/src/{ReactProfileTimer.js => ReactProfilerTimer.js} (100%) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 7c00e1f7b20..ca497d2681f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -16,7 +16,7 @@ import type {NewContext} from './ReactFiberNewContext'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {FiberRoot} from './ReactFiberRoot'; import type {ExpirationTime} from './ReactFiberExpirationTime'; -import type {ActualRenderTimer} from './ReactProfileTimer'; +import type {ActualRenderTimer} from './ReactProfilerTimer'; import checkPropTypes from 'prop-types/checkPropTypes'; import { @@ -70,7 +70,7 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import {stopBaseRenderTimerIfRunning} from './ReactProfileTimer'; +import {stopBaseRenderTimerIfRunning} from './ReactProfilerTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 2c8840a768f..91d166c41a4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -12,7 +12,7 @@ import type {Fiber} from './ReactFiber'; import type {FiberRoot} from './ReactFiber'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {CapturedValue, CapturedError} from './ReactCapturedValue'; -import type {ActualRenderTimer} from './ReactProfileTimer'; +import type {ActualRenderTimer} from './ReactProfilerTimer'; import { enableMutatingReconciler, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 14d87f8b756..ce7fb384eec 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -15,7 +15,7 @@ import type {LegacyContext} from './ReactFiberContext'; import type {NewContext} from './ReactFiberNewContext'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {FiberRoot} from './ReactFiberRoot'; -import type {ActualRenderTimer} from './ReactProfileTimer'; +import type {ActualRenderTimer} from './ReactProfilerTimer'; import { enableMutatingReconciler, diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 9a5bfd8669d..65aa92456ad 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -12,7 +12,7 @@ import type {Fiber} from './ReactFiber'; import type {FiberRoot, Batch} from './ReactFiberRoot'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {ExpirationTime} from './ReactFiberExpirationTime'; -import type {ActualRenderTimer} from './ReactProfileTimer'; +import type {ActualRenderTimer} from './ReactProfilerTimer'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; @@ -51,7 +51,7 @@ import { recordElapsedBaseRenderTimeIfRunning, startBaseRenderTimer, stopBaseRenderTimerIfRunning, -} from './ReactProfileTimer'; +} from './ReactProfilerTimer'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 5f40a9683ea..7148f0cad45 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -14,7 +14,7 @@ import type {HostContext} from './ReactFiberHostContext'; import type {LegacyContext} from './ReactFiberContext'; import type {NewContext} from './ReactFiberNewContext'; import type {CapturedValue} from './ReactCapturedValue'; -import type {ActualRenderTimer} from './ReactProfileTimer'; +import type {ActualRenderTimer} from './ReactProfilerTimer'; import type {Update} from './ReactUpdateQueue'; import {createCapturedValue} from './ReactCapturedValue'; @@ -246,6 +246,7 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { + // Resume in case we're picking up on work that was paused. ((actualRenderTimer: any): ActualRenderTimer).resumeActualRenderTimerIfPaused( now, ); diff --git a/packages/react-reconciler/src/ReactProfileTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js similarity index 100% rename from packages/react-reconciler/src/ReactProfileTimer.js rename to packages/react-reconciler/src/ReactProfilerTimer.js From 2d909aef38c6c5191d1772a3655a128f7a0f81e5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 17:13:33 -0700 Subject: [PATCH 57/72] Moved all timer functions inside of createProfilerTimer factory This is being done to better support multiple, concurrent renderers. --- .../src/ReactFiberBeginWork.js | 23 ++--- .../src/ReactFiberCommitWork.js | 2 - .../src/ReactFiberCompleteWork.js | 10 +- .../src/ReactFiberScheduler.js | 58 +++++------ .../src/ReactFiberUnwindWork.js | 14 +-- .../src/ReactProfilerTimer.js | 95 ++++++++++--------- 6 files changed, 86 insertions(+), 116 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index ca497d2681f..f36bfa40c94 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -16,7 +16,7 @@ import type {NewContext} from './ReactFiberNewContext'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {FiberRoot} from './ReactFiberRoot'; import type {ExpirationTime} from './ReactFiberExpirationTime'; -import type {ActualRenderTimer} from './ReactProfilerTimer'; +import type {ProfilerTimer} from './ReactProfilerTimer'; import checkPropTypes from 'prop-types/checkPropTypes'; import { @@ -70,7 +70,6 @@ import { import {processUpdateQueue} from './ReactUpdateQueue'; import {NoWork, Never} from './ReactFiberExpirationTime'; import {AsyncMode, StrictMode} from './ReactTypeOfMode'; -import {stopBaseRenderTimerIfRunning} from './ReactProfilerTimer'; import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber; @@ -93,9 +92,9 @@ export default function( hydrationContext: HydrationContext, scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void, computeExpirationForFiber: (fiber: Fiber) => ExpirationTime, - actualRenderTimer: ActualRenderTimer | null, + profilerTimer: ProfilerTimer, ) { - const {now, shouldSetTextContent, shouldDeprioritizeSubtree} = config; + const {shouldSetTextContent, shouldDeprioritizeSubtree} = config; const {pushHostContext, pushHostContainer} = hostContext; @@ -225,10 +224,7 @@ export default function( const nextProps = workInProgress.pendingProps; if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue - ((actualRenderTimer: any): ActualRenderTimer).markActualRenderTimeStarted( - workInProgress, - now, - ); + profilerTimer.markActualRenderTimeStarted(workInProgress); // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. @@ -374,7 +370,7 @@ export default function( nextChildren = null; if (enableProfileModeMetrics) { - stopBaseRenderTimerIfRunning(); + profilerTimer.stopBaseRenderTimerIfRunning(); } } else { if (__DEV__) { @@ -1088,7 +1084,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - stopBaseRenderTimerIfRunning(); + profilerTimer.stopBaseRenderTimerIfRunning(); } // TODO: We should ideally be able to bail out early if the children have no @@ -1114,7 +1110,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - stopBaseRenderTimerIfRunning(); + profilerTimer.stopBaseRenderTimerIfRunning(); } // TODO: Handle HostComponent tags here as well and call pushHostContext()? @@ -1137,10 +1133,7 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { - ((actualRenderTimer: any): ActualRenderTimer).markActualRenderTimeStarted( - workInProgress, - now, - ); + profilerTimer.markActualRenderTimeStarted(workInProgress); } break; } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 91d166c41a4..43dc41b02f4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -12,7 +12,6 @@ import type {Fiber} from './ReactFiber'; import type {FiberRoot} from './ReactFiber'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {CapturedValue, CapturedError} from './ReactCapturedValue'; -import type {ActualRenderTimer} from './ReactProfilerTimer'; import { enableMutatingReconciler, @@ -107,7 +106,6 @@ export default function( ) => ExpirationTime, markLegacyErrorBoundaryAsFailed: (instance: mixed) => void, recalculateCurrentTime: () => ExpirationTime, - actualRenderTimer: ActualRenderTimer | null, ) { const {getPublicInstance, mutation, persistence} = config; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index ce7fb384eec..37669db8b89 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -15,7 +15,7 @@ import type {LegacyContext} from './ReactFiberContext'; import type {NewContext} from './ReactFiberNewContext'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {FiberRoot} from './ReactFiberRoot'; -import type {ActualRenderTimer} from './ReactProfilerTimer'; +import type {ProfilerTimer} from './ReactProfilerTimer'; import { enableMutatingReconciler, @@ -52,14 +52,13 @@ export default function( legacyContext: LegacyContext, newContext: NewContext, hydrationContext: HydrationContext, - actualRenderTimer: ActualRenderTimer | null, + profilerTimer: ProfilerTimer, ) { const { createInstance, createTextInstance, appendInitialChild, finalizeInitialChildren, - now, prepareUpdate, mutation, persistence, @@ -598,10 +597,7 @@ export default function( return null; case Profiler: if (enableProfileModeMetrics) { - ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( - workInProgress, - now, - ); + profilerTimer.recordElapsedActualRenderTime(workInProgress); } return null; case HostPortal: diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 65aa92456ad..e5b178f3e7d 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -12,7 +12,6 @@ import type {Fiber} from './ReactFiber'; import type {FiberRoot, Batch} from './ReactFiberRoot'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {ExpirationTime} from './ReactFiberExpirationTime'; -import type {ActualRenderTimer} from './ReactProfilerTimer'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; @@ -46,12 +45,7 @@ import { replayFailedUnitOfWorkWithInvokeGuardedCallback, warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; -import { - createActualRenderTimer, - recordElapsedBaseRenderTimeIfRunning, - startBaseRenderTimer, - stopBaseRenderTimerIfRunning, -} from './ReactProfilerTimer'; +import {createProfilerTimer} from './ReactProfilerTimer'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -166,13 +160,18 @@ if (__DEV__) { export default function( config: HostConfig, ) { + const { + now, + scheduleDeferredCallback, + cancelDeferredCallback, + prepareForCommit, + resetAfterCommit, + } = config; const stack = ReactFiberStack(); const hostContext = ReactFiberHostContext(config, stack); const legacyContext = ReactFiberLegacyContext(stack); const newContext = ReactFiberNewContext(stack); - const actualRenderTimer: ActualRenderTimer | null = enableProfileModeMetrics - ? createActualRenderTimer(stack) - : null; + const profilerTimer = createProfilerTimer(stack, now); const {popHostContext, popHostContainer} = hostContext; const { popTopLevelContextObject: popTopLevelLegacyContextObject, @@ -190,7 +189,7 @@ export default function( hydrationContext, scheduleWork, computeExpirationForFiber, - actualRenderTimer, + profilerTimer, ); const {completeWork} = ReactFiberCompleteWork( config, @@ -198,7 +197,7 @@ export default function( legacyContext, newContext, hydrationContext, - actualRenderTimer, + profilerTimer, ); const { throwException, @@ -215,7 +214,7 @@ export default function( markLegacyErrorBoundaryAsFailed, isAlreadyFailedLegacyErrorBoundary, onUncaughtError, - actualRenderTimer, + profilerTimer, ); const { commitBeforeMutationLifeCycles, @@ -234,13 +233,6 @@ export default function( markLegacyErrorBoundaryAsFailed, recalculateCurrentTime, ); - const { - now, - scheduleDeferredCallback, - cancelDeferredCallback, - prepareForCommit, - resetAfterCommit, - } = config; // Represents the current time in ms. const originalStartTimeMs = now(); @@ -323,7 +315,7 @@ export default function( if (enableProfileModeMetrics) { // Stop "base" render timer again (after the re-thrown error). - stopBaseRenderTimerIfRunning(); + profilerTimer.stopBaseRenderTimerIfRunning(); } } else { // If the begin phase did not fail the second time, set this pointer @@ -667,9 +659,9 @@ export default function( if (enableProfileModeMetrics) { if (__DEV__) { - ((actualRenderTimer: any): ActualRenderTimer).checkActualRenderTimeStackEmpty(); + profilerTimer.checkActualRenderTimeStackEmpty(); } - ((actualRenderTimer: any): ActualRenderTimer).resetActualRenderTimer(); + profilerTimer.resetActualRenderTimer(); } isCommitting = false; @@ -938,12 +930,12 @@ export default function( let next; if (enableProfileModeMetrics) { - startBaseRenderTimer(now); + profilerTimer.startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); // Update "base" time if the render wasn't bailed out on. - recordElapsedBaseRenderTimeIfRunning(workInProgress, now); - stopBaseRenderTimerIfRunning(); + profilerTimer.recordElapsedBaseRenderTimeIfRunning(workInProgress); + profilerTimer.stopBaseRenderTimerIfRunning(); } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); } @@ -987,9 +979,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - ((actualRenderTimer: any): ActualRenderTimer).pauseActualRenderTimerIfRunning( - now, - ); + profilerTimer.pauseActualRenderTimerIfRunning(); } } } @@ -1035,7 +1025,7 @@ export default function( } catch (thrownValue) { if (enableProfileModeMetrics) { // Stop "base" render timer in the event of an error. - stopBaseRenderTimerIfRunning(); + profilerTimer.stopBaseRenderTimerIfRunning(); } if (nextUnitOfWork === null) { @@ -1602,9 +1592,7 @@ export default function( findHighestPriorityRoot(); if (enableProfileModeMetrics) { - ((actualRenderTimer: any): ActualRenderTimer).resumeActualRenderTimerIfPaused( - now, - ); + profilerTimer.resumeActualRenderTimerIfPaused(); } if (enableUserTimingAPI && deadline !== null) { @@ -1756,9 +1744,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - ((actualRenderTimer: any): ActualRenderTimer).pauseActualRenderTimerIfRunning( - now, - ); + profilerTimer.pauseActualRenderTimerIfRunning(); } } } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 7148f0cad45..3b8af6cdb90 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -14,7 +14,7 @@ import type {HostContext} from './ReactFiberHostContext'; import type {LegacyContext} from './ReactFiberContext'; import type {NewContext} from './ReactFiberNewContext'; import type {CapturedValue} from './ReactCapturedValue'; -import type {ActualRenderTimer} from './ReactProfilerTimer'; +import type {ProfilerTimer} from './ReactProfilerTimer'; import type {Update} from './ReactUpdateQueue'; import {createCapturedValue} from './ReactCapturedValue'; @@ -57,9 +57,8 @@ export default function( markLegacyErrorBoundaryAsFailed: (instance: mixed) => void, isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean, onUncaughtError: (error: mixed) => void, - actualRenderTimer: ActualRenderTimer | null, + profilerTimer: ProfilerTimer, ) { - const {now} = config; const {popHostContainer, popHostContext} = hostContext; const { popContextProvider: popLegacyContextProvider, @@ -247,13 +246,8 @@ export default function( case Profiler: if (enableProfileModeMetrics) { // Resume in case we're picking up on work that was paused. - ((actualRenderTimer: any): ActualRenderTimer).resumeActualRenderTimerIfPaused( - now, - ); - ((actualRenderTimer: any): ActualRenderTimer).recordElapsedActualRenderTime( - interruptedWork, - now, - ); + profilerTimer.resumeActualRenderTimerIfPaused(); + profilerTimer.recordElapsedActualRenderTime(interruptedWork); } break; default: diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 61c66f493d5..4dbceb21635 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -21,16 +21,19 @@ type Now = () => number; * It is paused (and accumulated) in the event of an interruption or an aborted render. */ -export type ActualRenderTimer = { +export type ProfilerTimer = { checkActualRenderTimeStackEmpty(): void, - markActualRenderTimeStarted(fiber: Fiber, now: Now): void, - pauseActualRenderTimerIfRunning(now: Now): void, - recordElapsedActualRenderTime(fiber: Fiber, now: Now): void, + markActualRenderTimeStarted(fiber: Fiber): void, + pauseActualRenderTimerIfRunning(): void, + recordElapsedActualRenderTime(fiber: Fiber): void, resetActualRenderTimer(): void, - resumeActualRenderTimerIfPaused(now: Now): void, + resumeActualRenderTimerIfPaused(): void, + recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void, + startBaseRenderTimer(): void, + stopBaseRenderTimerIfRunning(): void, }; -export function createActualRenderTimer(stack: Stack): ActualRenderTimer { +export function createProfilerTimer(stack: Stack, now: Now): ProfilerTimer { const {checkThatStackIsEmpty, createCursor, push, pop} = stack; let stackCursor: StackCursor = createCursor(0); @@ -43,19 +46,19 @@ export function createActualRenderTimer(stack: Stack): ActualRenderTimer { } } - function markActualRenderTimeStarted(fiber: Fiber, now: Now): void { + function markActualRenderTimeStarted(fiber: Fiber): void { const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); fiber.stateNode = startTime; push(stackCursor, startTime, fiber); } - function pauseActualRenderTimerIfRunning(now: Now): void { + function pauseActualRenderTimerIfRunning(): void { if (timerPausedAt === 0) { timerPausedAt = now(); } } - function recordElapsedActualRenderTime(fiber: Fiber, now: Now): void { + function recordElapsedActualRenderTime(fiber: Fiber): void { pop(stackCursor, fiber); fiber.stateNode += now() - totalElapsedPauseTime; } @@ -64,13 +67,46 @@ export function createActualRenderTimer(stack: Stack): ActualRenderTimer { totalElapsedPauseTime = 0; } - function resumeActualRenderTimerIfPaused(now: Now): void { + function resumeActualRenderTimerIfPaused(): void { if (timerPausedAt > 0) { totalElapsedPauseTime += now() - timerPausedAt; timerPausedAt = 0; } } + /** + * The "base" render time is the duration of the “begin” phase of work for a particular fiber. + * This time is measured and stored on each fiber. + * The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase. + * If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated. + */ + + let baseStartTime: number = -1; + + function recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void { + if (baseStartTime !== -1) { + fiber.selfBaseTime = now() - baseStartTime; + } + } + + function startBaseRenderTimer(): void { + if (__DEV__) { + if (baseStartTime !== -1) { + warning( + false, + 'Cannot start base timer that is already running. ' + + 'This error is likely caused by a bug in React. ' + + 'Please file an issue.', + ); + } + } + baseStartTime = now(); + } + + function stopBaseRenderTimerIfRunning(): void { + baseStartTime = -1; + } + return { checkActualRenderTimeStackEmpty, markActualRenderTimeStarted, @@ -78,41 +114,8 @@ export function createActualRenderTimer(stack: Stack): ActualRenderTimer { recordElapsedActualRenderTime, resetActualRenderTimer, resumeActualRenderTimerIfPaused, + recordElapsedBaseRenderTimeIfRunning, + startBaseRenderTimer, + stopBaseRenderTimerIfRunning, }; } - -/** - * The "base" render time is the duration of the “begin” phase of work for a particular fiber. - * This time is measured and stored on each fiber. - * The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase. - * If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated. - */ - -let baseStartTime: number = -1; - -export function recordElapsedBaseRenderTimeIfRunning( - fiber: Fiber, - now: Now, -): void { - if (baseStartTime !== -1) { - fiber.selfBaseTime = now() - baseStartTime; - } -} - -export function startBaseRenderTimer(now: Now): void { - if (__DEV__) { - if (baseStartTime !== -1) { - warning( - false, - 'Cannot start base timer that is already running. ' + - 'This error is likely caused by a bug in React. ' + - 'Please file an issue.', - ); - } - } - baseStartTime = now(); -} - -export function stopBaseRenderTimerIfRunning(): void { - baseStartTime = -1; -} From 9ad72e255853171e035f1291a2ab3e4865de7441 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 17:18:47 -0700 Subject: [PATCH 58/72] Removed ReactFiberStack from ProfilerTimer. Kept DEV only push/pop warning behavior. --- .../src/ReactFiberScheduler.js | 2 +- .../src/ReactProfilerTimer.js | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index e5b178f3e7d..fc6ab68d2dd 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -171,7 +171,7 @@ export default function( const hostContext = ReactFiberHostContext(config, stack); const legacyContext = ReactFiberLegacyContext(stack); const newContext = ReactFiberNewContext(stack); - const profilerTimer = createProfilerTimer(stack, now); + const profilerTimer = createProfilerTimer(now); const {popHostContext, popHostContainer} = hostContext; const { popTopLevelContextObject: popTopLevelLegacyContextObject, diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 4dbceb21635..ec18ca78ba5 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -8,12 +8,9 @@ */ import type {Fiber} from './ReactFiber'; -import type {StackCursor, Stack} from './ReactFiberStack'; import warning from 'fbjs/lib/warning'; -type Now = () => number; - /** * The "actual" render time is total time required to render the descendants of a Profiler component. * This time is stored as a stack, since Profilers can be nested. @@ -33,23 +30,31 @@ export type ProfilerTimer = { stopBaseRenderTimerIfRunning(): void, }; -export function createProfilerTimer(stack: Stack, now: Now): ProfilerTimer { - const {checkThatStackIsEmpty, createCursor, push, pop} = stack; +export function createProfilerTimer(now: () => number): ProfilerTimer { + let fiberStack: Array; + + if (__DEV__) { + fiberStack = []; + } - let stackCursor: StackCursor = createCursor(0); let timerPausedAt: number = 0; let totalElapsedPauseTime: number = 0; function checkActualRenderTimeStackEmpty(): void { if (__DEV__) { - checkThatStackIsEmpty(); + warning( + fiberStack.length === 0, + 'Expected an empty stack. Something was not reset properly.', + ); } } function markActualRenderTimeStarted(fiber: Fiber): void { + if (__DEV__) { + fiberStack.push(fiber); + } const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); fiber.stateNode = startTime; - push(stackCursor, startTime, fiber); } function pauseActualRenderTimerIfRunning(): void { @@ -59,7 +64,9 @@ export function createProfilerTimer(stack: Stack, now: Now): ProfilerTimer { } function recordElapsedActualRenderTime(fiber: Fiber): void { - pop(stackCursor, fiber); + if (__DEV__) { + warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.'); + } fiber.stateNode += now() - totalElapsedPauseTime; } From 4923a9dbcfd757c4aa3380fd520030f338f5166b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 17:21:47 -0700 Subject: [PATCH 59/72] Removed unnecessary else conditinoal in scheduler --- .../src/ReactFiberScheduler.js | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index fc6ab68d2dd..6ed28fe9e28 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -710,35 +710,21 @@ export default function( // Bubble up the earliest expiration time. // (And "base" render timers if that feature flag is enabled) - if (enableProfileModeMetrics) { - if (workInProgress.mode & ProfileMode) { - let treeBaseTime = workInProgress.selfBaseTime; - let child = workInProgress.child; - while (child !== null) { - treeBaseTime += child.treeBaseTime; - if ( - child.expirationTime !== NoWork && - (newExpirationTime === NoWork || - newExpirationTime > child.expirationTime) - ) { - newExpirationTime = child.expirationTime; - } - child = child.sibling; - } - workInProgress.treeBaseTime = treeBaseTime; - } else { - let child = workInProgress.child; - while (child !== null) { - if ( - child.expirationTime !== NoWork && - (newExpirationTime === NoWork || - newExpirationTime > child.expirationTime) - ) { - newExpirationTime = child.expirationTime; - } - child = child.sibling; + if (enableProfileModeMetrics && workInProgress.mode & ProfileMode) { + let treeBaseTime = workInProgress.selfBaseTime; + let child = workInProgress.child; + while (child !== null) { + treeBaseTime += child.treeBaseTime; + if ( + child.expirationTime !== NoWork && + (newExpirationTime === NoWork || + newExpirationTime > child.expirationTime) + ) { + newExpirationTime = child.expirationTime; } + child = child.sibling; } + workInProgress.treeBaseTime = treeBaseTime; } else { let child = workInProgress.child; while (child !== null) { From 5e14a49283a25c30cff873f433abd28a98e48c7d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 17:24:49 -0700 Subject: [PATCH 60/72] Destructured ProfilerTimer methods to avoid unnecessary property access --- .../src/ReactFiberBeginWork.js | 15 ++++++---- .../src/ReactFiberCompleteWork.js | 4 ++- .../src/ReactFiberScheduler.js | 30 ++++++++++++------- .../src/ReactFiberUnwindWork.js | 8 +++-- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index f36bfa40c94..eb6a08d4262 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -100,6 +100,11 @@ export default function( const {pushProvider} = newContext; + const { + markActualRenderTimeStarted, + stopBaseRenderTimerIfRunning, + } = profilerTimer; + const { getMaskedContext, getUnmaskedContext, @@ -224,7 +229,7 @@ export default function( const nextProps = workInProgress.pendingProps; if (enableProfileModeMetrics) { // Start render timer here and push start time onto queue - profilerTimer.markActualRenderTimeStarted(workInProgress); + markActualRenderTimeStarted(workInProgress); // Let the "complete" phase know to stop the timer, // And the scheduler to record the measured time. @@ -370,7 +375,7 @@ export default function( nextChildren = null; if (enableProfileModeMetrics) { - profilerTimer.stopBaseRenderTimerIfRunning(); + stopBaseRenderTimerIfRunning(); } } else { if (__DEV__) { @@ -1084,7 +1089,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - profilerTimer.stopBaseRenderTimerIfRunning(); + stopBaseRenderTimerIfRunning(); } // TODO: We should ideally be able to bail out early if the children have no @@ -1110,7 +1115,7 @@ export default function( if (enableProfileModeMetrics) { // Don't update "base" render times for bailouts. - profilerTimer.stopBaseRenderTimerIfRunning(); + stopBaseRenderTimerIfRunning(); } // TODO: Handle HostComponent tags here as well and call pushHostContext()? @@ -1133,7 +1138,7 @@ export default function( break; case Profiler: if (enableProfileModeMetrics) { - profilerTimer.markActualRenderTimeStarted(workInProgress); + markActualRenderTimeStarted(workInProgress); } break; } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 37669db8b89..4080c456980 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -71,6 +71,8 @@ export default function( popHostContainer, } = hostContext; + const {recordElapsedActualRenderTime} = profilerTimer; + const { popContextProvider: popLegacyContextProvider, popTopLevelContextObject: popTopLevelLegacyContextObject, @@ -597,7 +599,7 @@ export default function( return null; case Profiler: if (enableProfileModeMetrics) { - profilerTimer.recordElapsedActualRenderTime(workInProgress); + recordElapsedActualRenderTime(workInProgress); } return null; case HostPortal: diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 6ed28fe9e28..3522a41c32c 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -234,6 +234,16 @@ export default function( recalculateCurrentTime, ); + const { + checkActualRenderTimeStackEmpty, + pauseActualRenderTimerIfRunning, + recordElapsedBaseRenderTimeIfRunning, + resetActualRenderTimer, + resumeActualRenderTimerIfPaused, + startBaseRenderTimer, + stopBaseRenderTimerIfRunning, + } = profilerTimer; + // Represents the current time in ms. const originalStartTimeMs = now(); let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0); @@ -315,7 +325,7 @@ export default function( if (enableProfileModeMetrics) { // Stop "base" render timer again (after the re-thrown error). - profilerTimer.stopBaseRenderTimerIfRunning(); + stopBaseRenderTimerIfRunning(); } } else { // If the begin phase did not fail the second time, set this pointer @@ -659,9 +669,9 @@ export default function( if (enableProfileModeMetrics) { if (__DEV__) { - profilerTimer.checkActualRenderTimeStackEmpty(); + checkActualRenderTimeStackEmpty(); } - profilerTimer.resetActualRenderTimer(); + resetActualRenderTimer(); } isCommitting = false; @@ -916,12 +926,12 @@ export default function( let next; if (enableProfileModeMetrics) { - profilerTimer.startBaseRenderTimer(); + startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); // Update "base" time if the render wasn't bailed out on. - profilerTimer.recordElapsedBaseRenderTimeIfRunning(workInProgress); - profilerTimer.stopBaseRenderTimerIfRunning(); + recordElapsedBaseRenderTimeIfRunning(workInProgress); + stopBaseRenderTimerIfRunning(); } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); } @@ -965,7 +975,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - profilerTimer.pauseActualRenderTimerIfRunning(); + pauseActualRenderTimerIfRunning(); } } } @@ -1011,7 +1021,7 @@ export default function( } catch (thrownValue) { if (enableProfileModeMetrics) { // Stop "base" render timer in the event of an error. - profilerTimer.stopBaseRenderTimerIfRunning(); + stopBaseRenderTimerIfRunning(); } if (nextUnitOfWork === null) { @@ -1578,7 +1588,7 @@ export default function( findHighestPriorityRoot(); if (enableProfileModeMetrics) { - profilerTimer.resumeActualRenderTimerIfPaused(); + resumeActualRenderTimerIfPaused(); } if (enableUserTimingAPI && deadline !== null) { @@ -1730,7 +1740,7 @@ export default function( if (enableProfileModeMetrics) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. - profilerTimer.pauseActualRenderTimerIfRunning(); + pauseActualRenderTimerIfRunning(); } } } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index 3b8af6cdb90..d39af08bb54 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -65,6 +65,10 @@ export default function( popTopLevelContextObject: popTopLevelLegacyContextObject, } = legacyContext; const {popProvider} = newContext; + const { + resumeActualRenderTimerIfPaused, + recordElapsedActualRenderTime, + } = profilerTimer; function createRootErrorUpdate( fiber: Fiber, @@ -246,8 +250,8 @@ export default function( case Profiler: if (enableProfileModeMetrics) { // Resume in case we're picking up on work that was paused. - profilerTimer.resumeActualRenderTimerIfPaused(); - profilerTimer.recordElapsedActualRenderTime(interruptedWork); + resumeActualRenderTimerIfPaused(); + recordElapsedActualRenderTime(interruptedWork); } break; default: From 25fcd3ac802793bfbceb2394760eb058c9972434 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 7 May 2018 18:39:33 -0700 Subject: [PATCH 61/72] Converted Profiler stateNode from number to wrapper Object Hopefully this will make it a little more readable / easier to understand. It may also avoid a v8 deopt Dan mentioned. --- packages/react-reconciler/src/ReactFiber.js | 5 ++++- packages/react-reconciler/src/ReactFiberCommitWork.js | 4 ++-- packages/react-reconciler/src/ReactProfilerTimer.js | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 82b5c4316ac..683a720438d 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -485,7 +485,10 @@ export function createFiberFromProfileMode( const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode); fiber.type = REACT_PROFILER_TYPE; fiber.expirationTime = expirationTime; - fiber.stateNode = 0; + fiber.stateNode = { + duration: 0, + startTime: 0, + }; return fiber; } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 43dc41b02f4..3062985deee 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -826,13 +826,13 @@ export default function( onRender( finishedWork.memoizedProps.id, current === null ? 'mount' : 'update', - finishedWork.stateNode, + 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 = 0; + finishedWork.stateNode.duration = 0; } return; } diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index ec18ca78ba5..da471da8d21 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -53,8 +53,7 @@ export function createProfilerTimer(now: () => number): ProfilerTimer { if (__DEV__) { fiberStack.push(fiber); } - const startTime = fiber.stateNode - (now() - totalElapsedPauseTime); - fiber.stateNode = startTime; + fiber.stateNode.startTime = now() - totalElapsedPauseTime; } function pauseActualRenderTimerIfRunning(): void { @@ -67,7 +66,8 @@ export function createProfilerTimer(now: () => number): ProfilerTimer { if (__DEV__) { warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.'); } - fiber.stateNode += now() - totalElapsedPauseTime; + fiber.stateNode.duration += + now() - fiber.stateNode.startTime - totalElapsedPauseTime; } function resetActualRenderTimer(): void { From 120a6d7d7972ed3058aa3192aa50f69e0c2c1daf Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 8 May 2018 12:42:09 -0700 Subject: [PATCH 62/72] Removed source of potential false positive from test --- packages/react/src/__tests__/ReactProfiler-test.internal.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index bcd1d76ae5b..979342fe84e 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -566,8 +566,8 @@ describe('Profiler', () => { // This partial render should take 10ms of simulated time. renderer.update( - - + + , ); renderer.unstable_flushThrough(['first']); @@ -591,7 +591,7 @@ describe('Profiler', () => { // And the "base" time includes only the final rendered tree times. expect(callback).toHaveBeenCalledTimes(1); call = callback.mock.calls[0]; - expect(call[2]).toBe(15); // "actual" time + expect(call[2]).toBe(13); // "actual" time expect(call[3]).toBe(5); // "base" time // Verify no more unexpected callbacks from low priority work From bde491e744427055cdbb0718a196e52eab892aab Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 8 May 2018 15:23:37 -0700 Subject: [PATCH 63/72] Added a test for change to id prop --- .../__tests__/ReactProfiler-test.internal.js | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 979342fe84e..1fbbf87d9a4 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -105,7 +105,7 @@ describe('Profiler', () => { }); }); - describe('records meaningful timing information', () => { + describe('onRender callback', () => { let AdvanceTime; let advanceTimeBy; @@ -139,7 +139,7 @@ describe('Profiler', () => { }; }); - it('does not invoke the callback until the commit phase', () => { + it('is not invoked until the commit phase', () => { const callback = jest.fn(); const Yield = ({value}) => { @@ -279,7 +279,7 @@ describe('Profiler', () => { expect(call[3]).toBe(5); // "base" time }); - it('does not call callbacks after update for descendents of sCU false', () => { + it('is not called when blocked by sCU false', () => { const callback = jest.fn(); let instance; @@ -407,7 +407,7 @@ describe('Profiler', () => { expect(updateCall[3]).toBe(15); // "base" time }); - describe('handles interruptions', () => { + describe('with regard to interruptions', () => { it('should accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); @@ -716,5 +716,37 @@ describe('Profiler', () => { }); }); }); + + it('reflects the most recently rendered id value', () => { + const callback = jest.fn(); + + const renderer = ReactTestRenderer.create( + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + renderer.update( + + + , + ); + + expect(callback).toHaveBeenCalledTimes(2); + + const [mountCall, updateCall] = callback.mock.calls; + + expect(mountCall[0]).toBe('one'); + expect(mountCall[1]).toBe('mount'); + expect(mountCall[2]).toBe(2); // "actual" time + expect(mountCall[3]).toBe(2); // "base" time + + expect(updateCall[0]).toBe('two'); + expect(updateCall[1]).toBe('update'); + expect(updateCall[2]).toBe(1); // "actual" time + expect(updateCall[3]).toBe(1); // "base" time + }); }); }); From c5e44c2f4c3a0c15bf28bf4dfa682b6559507b8b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 8 May 2018 17:55:27 -0700 Subject: [PATCH 64/72] Expanded update interrupt test to be more thorough --- .../src/ReactProfilerTimer.js | 2 +- .../__tests__/ReactProfiler-test.internal.js | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index da471da8d21..c77ba6c0710 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -67,7 +67,7 @@ export function createProfilerTimer(now: () => number): ProfilerTimer { warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.'); } fiber.stateNode.duration += - now() - fiber.stateNode.startTime - totalElapsedPauseTime; + now() - totalElapsedPauseTime - fiber.stateNode.startTime; } function resetActualRenderTimer(): void { diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 1fbbf87d9a4..61460d449e0 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -511,7 +511,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(30); + advanceTimeBy(100); // Interrupt with higher priority work. // The interrupted work simulates an additional 5ms of time. @@ -563,11 +563,12 @@ describe('Profiler', () => { callback.mockReset(); // Render a partially update, but don't finish. - // This partial render should take 10ms of simulated time. + // This partial render should take 3ms of simulated time. renderer.update( - - + + + , ); renderer.unstable_flushThrough(['first']); @@ -575,24 +576,32 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(30); + advanceTimeBy(100); + + // Render another 5ms of simulated time. + renderer.unstable_flushThrough(['second']); + + expect(callback).toHaveBeenCalledTimes(0); + + // Simulate time moving forward while frame is paused. + advanceTimeBy(100); // Interrupt with higher priority work. - // The interrupted work simulates an additional 5ms of time. + // The interrupted work simulates an additional 11ms of time. renderer.unstable_flushSync(() => { renderer.update( - + , ); }); - // Verify that the "actual" time includes both durations above, + // Verify that the "actual" time includes all three durations above. // And the "base" time includes only the final rendered tree times. expect(callback).toHaveBeenCalledTimes(1); call = callback.mock.calls[0]; - expect(call[2]).toBe(13); // "actual" time - expect(call[3]).toBe(5); // "base" time + expect(call[2]).toBe(19); // "actual" time + expect(call[3]).toBe(11); // "base" time // Verify no more unexpected callbacks from low priority work renderer.unstable_flushAll(); From 84854678bda25145883fa954eb9a2d5f73a0a114 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 9 May 2018 13:59:57 -0700 Subject: [PATCH 65/72] TestRenderer unstable_flushSync returns yielded values (so you can assert against them) --- packages/react-test-renderer/src/ReactTestRenderer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index d109e649101..4e368e1914c 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -740,7 +740,11 @@ const ReactTestRendererFiber = { } return TestRenderer.getPublicRootInstance(root); }, - unstable_flushSync: TestRenderer.flushSync, + unstable_flushSync(fn) { + yieldedValues = []; + TestRenderer.flushSync(fn); + return yieldedValues; + }, }; Object.defineProperty( From da2686df4c4004a87e06ccd422f1bfaa82677c44 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 9 May 2018 14:34:55 -0700 Subject: [PATCH 66/72] Added a better interrupt test case --- .../__tests__/ReactProfiler-test.internal.js | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 61460d449e0..92b7e600c7b 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -535,7 +535,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(1); }); - it('should accumulate "actual" time if an update is interrupted with higher priority work', () => { + it('should report the expected times when a high-priority update replaces a low-priority update', () => { const callback = jest.fn(); const Yield = ({renderTime, value}) => { @@ -608,6 +608,97 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(1); }); + it('should report the expected times when a high-priority update interrupts a low-priority update', () => { + const callback = jest.fn(); + + const Yield = ({renderTime}) => { + advanceTimeBy(renderTime); + renderer.unstable_yield('Yield:' + renderTime); + return null; + }; + + let first; + class FirstComponent extends React.Component { + state = {renderTime: 1}; + render() { + first = this; + advanceTimeBy(this.state.renderTime); + renderer.unstable_yield('FirstComponent:' + this.state.renderTime); + return ( + + ); + } + } + let second; + class SecondComponent extends React.Component { + state = {renderTime: 2}; + render() { + second = this; + advanceTimeBy(this.state.renderTime); + renderer.unstable_yield('SecondComponent:' + this.state.renderTime); + return ( + + ); + } + } + + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isAsync: true}, + ); + + // Render everything initially. + // This simulates a total of 14ms of "actual" render time. + // The "base" render time is also 14ms for the initial render. + expect(renderer.unstable_flushAll()).toEqual(['FirstComponent:1', 'Yield:4', 'SecondComponent:2', 'Yield:7']); + expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; + expect(call[2]).toBe(14); // "actual" time + expect(call[3]).toBe(14); // "base" time + + callback.mockClear(); + + // Render a partially update, but don't finish. + // This partial render will take 10ms of "actual" render time. + first.setState({renderTime: 10}); + expect(renderer.unstable_flushThrough(['FirstComponent:10'])).toEqual(['FirstComponent:10']); + expect(callback).toHaveBeenCalledTimes(0); + + // Simulate time moving forward while frame is paused. + advanceTimeBy(100); + + // Interrupt with higher priority work. + // This simulates a total of 37ms of "actual" render time. + expect(renderer.unstable_flushSync( + () => second.setState({renderTime: 30}) + )).toEqual(['SecondComponent:30', 'Yield:7']); + + // Verify that the "actual" time includes time spent in the both renders so far (10ms and 37ms). + // The "base" time should include the more recent times for the SecondComponent subtree, + // As well as the original times for the FirstComponent subtree. + expect(callback).toHaveBeenCalledTimes(1); + call = callback.mock.calls[0]; + expect(call[2]).toBe(47); // "actual" time + expect(call[3]).toBe(42); // "base" time + + callback.mockClear(); + + // Resume the original low priority update, with rebased state. + // This simulates a total of 14ms of "actual" render time, + // And does not include the original (interrupted) 10ms. + // The tree contains 42ms of "base" render time at this point, + // Reflecting the most recent (longer) render durations. + // TODO: This "actual" time should change to 14ms once the scheduler supports resuming. + expect(renderer.unstable_flushAll()).toEqual(['FirstComponent:10', 'Yield:4']); + expect(callback).toHaveBeenCalledTimes(1); + call = callback.mock.calls[0]; + expect(call[2]).toBe(14); // "actual" time + expect(call[3]).toBe(51); // "base" time + }); + [true, false].forEach(flagEnabled => { describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ flagEnabled ? 'enabled' : 'disabled' From 48f113c29acc4a53242bbeae220031c511bec35e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 9 May 2018 14:39:27 -0700 Subject: [PATCH 67/72] Prettier --- .../src/ReactTestRenderer.js | 2 +- .../__tests__/ReactProfiler-test.internal.js | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 4e368e1914c..561aed6c584 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -740,7 +740,7 @@ const ReactTestRendererFiber = { } return TestRenderer.getPublicRootInstance(root); }, - unstable_flushSync(fn) { + unstable_flushSync(fn: Function) { yieldedValues = []; TestRenderer.flushSync(fn); return yieldedValues; diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 92b7e600c7b..0bbdf124922 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -624,9 +624,7 @@ describe('Profiler', () => { first = this; advanceTimeBy(this.state.renderTime); renderer.unstable_yield('FirstComponent:' + this.state.renderTime); - return ( - - ); + return ; } } let second; @@ -636,9 +634,7 @@ describe('Profiler', () => { second = this; advanceTimeBy(this.state.renderTime); renderer.unstable_yield('SecondComponent:' + this.state.renderTime); - return ( - - ); + return ; } } @@ -653,7 +649,12 @@ describe('Profiler', () => { // Render everything initially. // This simulates a total of 14ms of "actual" render time. // The "base" render time is also 14ms for the initial render. - expect(renderer.unstable_flushAll()).toEqual(['FirstComponent:1', 'Yield:4', 'SecondComponent:2', 'Yield:7']); + expect(renderer.unstable_flushAll()).toEqual([ + 'FirstComponent:1', + 'Yield:4', + 'SecondComponent:2', + 'Yield:7', + ]); expect(callback).toHaveBeenCalledTimes(1); let call = callback.mock.calls[0]; expect(call[2]).toBe(14); // "actual" time @@ -664,7 +665,9 @@ describe('Profiler', () => { // Render a partially update, but don't finish. // This partial render will take 10ms of "actual" render time. first.setState({renderTime: 10}); - expect(renderer.unstable_flushThrough(['FirstComponent:10'])).toEqual(['FirstComponent:10']); + expect(renderer.unstable_flushThrough(['FirstComponent:10'])).toEqual([ + 'FirstComponent:10', + ]); expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. @@ -672,9 +675,9 @@ describe('Profiler', () => { // Interrupt with higher priority work. // This simulates a total of 37ms of "actual" render time. - expect(renderer.unstable_flushSync( - () => second.setState({renderTime: 30}) - )).toEqual(['SecondComponent:30', 'Yield:7']); + expect( + renderer.unstable_flushSync(() => second.setState({renderTime: 30})), + ).toEqual(['SecondComponent:30', 'Yield:7']); // Verify that the "actual" time includes time spent in the both renders so far (10ms and 37ms). // The "base" time should include the more recent times for the SecondComponent subtree, @@ -691,8 +694,11 @@ describe('Profiler', () => { // And does not include the original (interrupted) 10ms. // The tree contains 42ms of "base" render time at this point, // Reflecting the most recent (longer) render durations. - // TODO: This "actual" time should change to 14ms once the scheduler supports resuming. - expect(renderer.unstable_flushAll()).toEqual(['FirstComponent:10', 'Yield:4']); + // TODO: This "actual" time should decrease by 10ms once the scheduler supports resuming. + expect(renderer.unstable_flushAll()).toEqual([ + 'FirstComponent:10', + 'Yield:4', + ]); expect(callback).toHaveBeenCalledTimes(1); call = callback.mock.calls[0]; expect(call[2]).toBe(14); // "actual" time From d55372c2aa10fd8f5371a16e7d8447b630c7de7b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 9 May 2018 15:03:22 -0700 Subject: [PATCH 68/72] Tidied up tests with more explicit flush/yield expectations and inline comments --- .../__tests__/ReactProfiler-test.internal.js | 144 +++++++++--------- 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 0bbdf124922..025d9c7fc31 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -411,59 +411,58 @@ describe('Profiler', () => { it('should accumulate "actual" time after a scheduling interruptions', () => { const callback = jest.fn(); - const Yield = ({value}) => { - advanceTimeBy(10); - renderer.unstable_yield(value); + const Yield = ({renderTime}) => { + advanceTimeBy(renderTime); + renderer.unstable_yield('Yield:' + renderTime); return null; }; // Render partially, but run out of time before completing. const renderer = ReactTestRenderer.create( - - + + , {unstable_isAsync: true}, ); - - // Simulate only enough time to render the first Yield - renderer.unstable_flushThrough(['first']); - + expect(renderer.unstable_flushThrough(['Yield:2'])).toEqual([ + 'Yield:2', + ]); expect(callback).toHaveBeenCalledTimes(0); // Resume render for remaining children. - renderer.unstable_flushAll(); + expect(renderer.unstable_flushAll()).toEqual(['Yield:3']); // Verify that logged times include both durations above. expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][2]).toBe(20); // "actual" time - expect(callback.mock.calls[0][3]).toBe(20); // "base" time + expect(callback.mock.calls[0][2]).toBe(5); // "actual" time + expect(callback.mock.calls[0][3]).toBe(5); // "base" time }); it('should not include time between frames', () => { const callback = jest.fn(); - const Yield = ({renderTime, value}) => { + const Yield = ({renderTime}) => { advanceTimeBy(renderTime); - renderer.unstable_yield(value); + renderer.unstable_yield('Yield:' + renderTime); return null; }; + // Render partially, but don't finish. + // This partial render should take 5ms of simulated time. const renderer = ReactTestRenderer.create( - - + + - + , {unstable_isAsync: true}, ); - - // Render partially, but don't finish. - // This partial render should take 5ms of simulated time. - renderer.unstable_flushThrough(['first']); - + expect(renderer.unstable_flushThrough(['Yield:5'])).toEqual([ + 'Yield:5', + ]); expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. @@ -471,8 +470,7 @@ describe('Profiler', () => { // Flush the remaninig work, // Which should take an additional 10ms of simulated time. - renderer.unstable_flushAll(); - + expect(renderer.unstable_flushAll()).toEqual(['Yield:10', 'Yield:17']); expect(callback).toHaveBeenCalledTimes(2); const [innerCall, outerCall] = callback.mock.calls; @@ -480,34 +478,32 @@ describe('Profiler', () => { // Verify that the "actual" time includes all work times, // But not the time that elapsed between frames. expect(innerCall[0]).toBe('inner'); - expect(innerCall[2]).toBe(20); // "actual" time - expect(innerCall[3]).toBe(20); // "base" time + expect(innerCall[2]).toBe(17); // "actual" time + expect(innerCall[3]).toBe(17); // "base" time expect(outerCall[0]).toBe('outer'); - expect(outerCall[2]).toBe(35); // "actual" time - expect(outerCall[3]).toBe(35); // "base" time + expect(outerCall[2]).toBe(32); // "actual" time + expect(outerCall[3]).toBe(32); // "base" time }); - it('should accumulate "actual" time if initial render is interrupted with higher priority work', () => { + it('should report the expected times when a high-priority update replaces an in-progress initial render', () => { const callback = jest.fn(); - const Yield = ({renderTime, value}) => { + const Yield = ({renderTime}) => { advanceTimeBy(renderTime); - renderer.unstable_yield(value); + renderer.unstable_yield('Yield:' + renderTime); return null; }; + // Render a partially update, but don't finish. + // This partial render should take 10ms of simulated time. const renderer = ReactTestRenderer.create( - - + + , {unstable_isAsync: true}, ); - - // Render a partially update, but don't finish. - // This partial render should take 10ms of simulated time. - renderer.unstable_flushThrough(['first']); - + expect(renderer.unstable_flushThrough(['first'])).toEqual(['Yield:10']); expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. @@ -515,13 +511,15 @@ describe('Profiler', () => { // Interrupt with higher priority work. // The interrupted work simulates an additional 5ms of time. - renderer.unstable_flushSync(() => { - renderer.update( - - - , - ); - }); + expect( + renderer.unstable_flushSync(() => { + renderer.update( + + + , + ); + }), + ).toEqual(['Yield:5']); // The initial work was thrown away in this case, // So the "actual" and "base" times should only include the final rendered tree times. @@ -530,35 +528,37 @@ describe('Profiler', () => { expect(call[2]).toBe(5); // "actual" time expect(call[3]).toBe(5); // "base" time + callback.mockReset(); + // Verify no more unexpected callbacks from low priority work - renderer.unstable_flushAll(); - expect(callback).toHaveBeenCalledTimes(1); + expect(renderer.unstable_flushAll()).toEqual([]); + expect(callback).toHaveBeenCalledTimes(0); }); it('should report the expected times when a high-priority update replaces a low-priority update', () => { const callback = jest.fn(); - const Yield = ({renderTime, value}) => { + const Yield = ({renderTime}) => { advanceTimeBy(renderTime); - renderer.unstable_yield(value); + renderer.unstable_yield('Yield:' + renderTime); return null; }; const renderer = ReactTestRenderer.create( - - + + , {unstable_isAsync: true}, ); // Render everything initially. - renderer.unstable_flushAll(); - + // This should take 21 seconds of "actual" and "base" time. + expect(renderer.unstable_flushAll()).toEqual(['Yield:6', 'Yield:15']); expect(callback).toHaveBeenCalledTimes(1); let call = callback.mock.calls[0]; - expect(call[2]).toBe(30); // "actual" time - expect(call[3]).toBe(30); // "base" time + expect(call[2]).toBe(21); // "actual" time + expect(call[3]).toBe(21); // "base" time callback.mockReset(); @@ -566,21 +566,23 @@ describe('Profiler', () => { // This partial render should take 3ms of simulated time. renderer.update( - - - + + + , ); - renderer.unstable_flushThrough(['first']); - + expect(renderer.unstable_flushThrough(['Yield:3'])).toEqual([ + 'Yield:3', + ]); expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. advanceTimeBy(100); // Render another 5ms of simulated time. - renderer.unstable_flushThrough(['second']); - + expect(renderer.unstable_flushThrough(['Yield:5'])).toEqual([ + 'Yield:5', + ]); expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. @@ -588,13 +590,15 @@ describe('Profiler', () => { // Interrupt with higher priority work. // The interrupted work simulates an additional 11ms of time. - renderer.unstable_flushSync(() => { - renderer.update( - - - , - ); - }); + expect( + renderer.unstable_flushSync(() => { + renderer.update( + + + , + ); + }), + ).toEqual(['Yield:11']); // Verify that the "actual" time includes all three durations above. // And the "base" time includes only the final rendered tree times. @@ -604,7 +608,7 @@ describe('Profiler', () => { expect(call[3]).toBe(11); // "base" time // Verify no more unexpected callbacks from low priority work - renderer.unstable_flushAll(); + expect(renderer.unstable_flushAll()).toEqual([]); expect(callback).toHaveBeenCalledTimes(1); }); From fd731b452ba972816a9b0aa455a6a5eb8893fc87 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 10 May 2018 11:51:22 -0700 Subject: [PATCH 69/72] Renamed getComponentName() output from ProfileMode -> Profiler --- packages/shared/getComponentName.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js index 35661c16f1d..ed448d81520 100644 --- a/packages/shared/getComponentName.js +++ b/packages/shared/getComponentName.js @@ -38,7 +38,7 @@ function getComponentName(fiber: Fiber): string | null { case REACT_PORTAL_TYPE: return 'ReactPortal'; case REACT_PROFILER_TYPE: - return `ProfileMode(${fiber.pendingProps.id})`; + return `Profiler(${fiber.pendingProps.id})`; case REACT_RETURN_TYPE: return 'ReactReturn'; case REACT_STRICT_MODE_TYPE: From 68b05537773c47552fa87ad0f170517f1ba5d0af Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 10 May 2018 12:17:39 -0700 Subject: [PATCH 70/72] Update snapshot --- .../__snapshots__/ReactIncrementalPerf-test.internal.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap index 323ba2bf68e..45ea1947c7a 100644 --- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap +++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap @@ -96,7 +96,7 @@ exports[`ReactDebugFiberPerf does not include AsyncMode, StrictMode, or ProfileM // Mount ⚛ (React Tree Reconciliation: Completed Root) - ⚛ ProfileMode(test) [mount] + ⚛ Profiler(test) [mount] ⚛ Parent [mount] ⚛ Child [mount] From 71034a79a5b0ef4dbbdc2f5146ed7a78407257ea Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 10 May 2018 15:00:11 -0700 Subject: [PATCH 71/72] Conditionally return from createProfilerTimer based on feature flag --- .../src/ReactProfilerTimer.js | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index c77ba6c0710..a05b876bd52 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -9,6 +9,8 @@ import type {Fiber} from './ReactFiber'; +import {enableProfileModeMetrics} from 'shared/ReactFeatureFlags'; + import warning from 'fbjs/lib/warning'; /** @@ -114,15 +116,29 @@ export function createProfilerTimer(now: () => number): ProfilerTimer { baseStartTime = -1; } - return { - checkActualRenderTimeStackEmpty, - markActualRenderTimeStarted, - pauseActualRenderTimerIfRunning, - recordElapsedActualRenderTime, - resetActualRenderTimer, - resumeActualRenderTimerIfPaused, - recordElapsedBaseRenderTimeIfRunning, - startBaseRenderTimer, - stopBaseRenderTimerIfRunning, - }; + if (enableProfileModeMetrics) { + return { + checkActualRenderTimeStackEmpty, + markActualRenderTimeStarted, + pauseActualRenderTimerIfRunning, + recordElapsedActualRenderTime, + resetActualRenderTimer, + resumeActualRenderTimerIfPaused, + recordElapsedBaseRenderTimeIfRunning, + startBaseRenderTimer, + stopBaseRenderTimerIfRunning, + }; + } else { + return { + checkActualRenderTimeStackEmpty(): void {}, + markActualRenderTimeStarted(fiber: Fiber): void {}, + pauseActualRenderTimerIfRunning(): void {}, + recordElapsedActualRenderTime(fiber: Fiber): void {}, + resetActualRenderTimer(): void {}, + resumeActualRenderTimerIfPaused(): void {}, + recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {}, + startBaseRenderTimer(): void {}, + stopBaseRenderTimerIfRunning(): void {}, + }; + } } From e3a256237b51582ec3c071074f37d1d429da3c99 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 10 May 2018 15:14:30 -0700 Subject: [PATCH 72/72] Renamed flag enableProfileModeMetrics -> enableProfilerTiming and a few internal references ProfileMode -> Profiler --- packages/react-reconciler/src/ReactFiber.js | 19 +++++++------------ .../src/ReactFiberBeginWork.js | 12 ++++++------ .../src/ReactFiberCommitWork.js | 4 ++-- .../src/ReactFiberCompleteWork.js | 4 ++-- .../src/ReactFiberScheduler.js | 18 +++++++++--------- .../src/ReactFiberUnwindWork.js | 4 ++-- .../src/ReactProfilerTimer.js | 4 ++-- .../ReactIncrementalPerf-test.internal.js | 2 +- ...ReactIncrementalPerf-test.internal.js.snap | 2 +- .../__tests__/ReactProfiler-test.internal.js | 12 ++++++------ .../ReactProfiler-test.internal.js.snap | 16 ++++++++-------- packages/shared/ReactFeatureFlags.js | 4 ++-- .../ReactFeatureFlags.native-fabric-fb.js | 2 +- .../ReactFeatureFlags.native-fabric-oss.js | 2 +- .../forks/ReactFeatureFlags.native-fb.js | 2 +- .../forks/ReactFeatureFlags.native-oss.js | 2 +- .../forks/ReactFeatureFlags.persistent.js | 2 +- .../forks/ReactFeatureFlags.test-renderer.js | 2 +- .../shared/forks/ReactFeatureFlags.www.js | 2 +- 19 files changed, 55 insertions(+), 60 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 683a720438d..51c4d52fe57 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -15,7 +15,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {UpdateQueue} from './ReactUpdateQueue'; import invariant from 'fbjs/lib/invariant'; -import {enableProfileModeMetrics} from 'shared/ReactFeatureFlags'; +import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; import {NoEffect} from 'shared/ReactTypeOfSideEffect'; import { IndeterminateComponent, @@ -211,7 +211,7 @@ function FiberNode( this.alternate = null; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { this.selfBaseTime = 0; this.treeBaseTime = 0; } @@ -310,7 +310,7 @@ export function createWorkInProgress( workInProgress.index = current.index; workInProgress.ref = current.ref; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { workInProgress.selfBaseTime = current.selfBaseTime; workInProgress.treeBaseTime = current.treeBaseTime; } @@ -361,12 +361,7 @@ export function createFiberFromElement( mode |= StrictMode; break; case REACT_PROFILER_TYPE: - return createFiberFromProfileMode( - pendingProps, - mode, - expirationTime, - key, - ); + return createFiberFromProfiler(pendingProps, mode, expirationTime, key); case REACT_CALL_TYPE: fiberTag = CallComponent; break; @@ -464,7 +459,7 @@ export function createFiberFromFragment( return fiber; } -export function createFiberFromProfileMode( +export function createFiberFromProfiler( pendingProps: any, mode: TypeOfMode, expirationTime: ExpirationTime, @@ -477,7 +472,7 @@ export function createFiberFromProfileMode( ) { invariant( false, - 'ProfileMode must specify an "id" string and "onRender" function as props', + 'Profiler must specify an "id" string and "onRender" function as props', ); } } @@ -562,7 +557,7 @@ export function assignFiberPropertiesInDEV( target.lastEffect = source.lastEffect; target.expirationTime = source.expirationTime; target.alternate = source.alternate; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { target.selfBaseTime = source.selfBaseTime; target.treeBaseTime = source.treeBaseTime; } diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index eb6a08d4262..2c80e26ffb5 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -51,7 +51,7 @@ import { enableGetDerivedStateFromCatch, debugRenderPhaseSideEffects, debugRenderPhaseSideEffectsForStrictMode, - enableProfileModeMetrics, + enableProfilerTimer, } from 'shared/ReactFeatureFlags'; import invariant from 'fbjs/lib/invariant'; import getComponentName from 'shared/getComponentName'; @@ -227,7 +227,7 @@ export default function( function updateProfiler(current, workInProgress) { const nextProps = workInProgress.pendingProps; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // Start render timer here and push start time onto queue markActualRenderTimeStarted(workInProgress); @@ -374,7 +374,7 @@ export default function( // TODO: Warn in a future release. nextChildren = null; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { stopBaseRenderTimerIfRunning(); } } else { @@ -1087,7 +1087,7 @@ export default function( ): Fiber | null { cancelWorkTimer(workInProgress); - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // Don't update "base" render times for bailouts. stopBaseRenderTimerIfRunning(); } @@ -1113,7 +1113,7 @@ export default function( function bailoutOnLowPriority(current, workInProgress) { cancelWorkTimer(workInProgress); - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // Don't update "base" render times for bailouts. stopBaseRenderTimerIfRunning(); } @@ -1137,7 +1137,7 @@ export default function( pushProvider(workInProgress); break; case Profiler: - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { markActualRenderTimeStarted(workInProgress); } break; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 3062985deee..686a49219be 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -17,7 +17,7 @@ import { enableMutatingReconciler, enableNoopReconciler, enablePersistentReconciler, - enableProfileModeMetrics, + enableProfilerTimer, } from 'shared/ReactFeatureFlags'; import { ClassComponent, @@ -821,7 +821,7 @@ export default function( return; } case Profiler: { - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { const onRender = finishedWork.memoizedProps.onRender; onRender( finishedWork.memoizedProps.id, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 4080c456980..e9752e38da4 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -21,7 +21,7 @@ import { enableMutatingReconciler, enablePersistentReconciler, enableNoopReconciler, - enableProfileModeMetrics, + enableProfilerTimer, } from 'shared/ReactFeatureFlags'; import { IndeterminateComponent, @@ -598,7 +598,7 @@ export default function( case Mode: return null; case Profiler: - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { recordElapsedActualRenderTime(workInProgress); } return null; diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 3522a41c32c..fde57fd5a42 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -40,7 +40,7 @@ import { HostPortal, } from 'shared/ReactTypeOfWork'; import { - enableProfileModeMetrics, + enableProfilerTimer, enableUserTimingAPI, replayFailedUnitOfWorkWithInvokeGuardedCallback, warnAboutDeprecatedLifecycles, @@ -323,7 +323,7 @@ export default function( if (hasCaughtError()) { clearCaughtError(); - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // Stop "base" render timer again (after the re-thrown error). stopBaseRenderTimerIfRunning(); } @@ -667,7 +667,7 @@ export default function( } } - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { if (__DEV__) { checkActualRenderTimeStackEmpty(); } @@ -720,7 +720,7 @@ export default function( // Bubble up the earliest expiration time. // (And "base" render timers if that feature flag is enabled) - if (enableProfileModeMetrics && workInProgress.mode & ProfileMode) { + if (enableProfilerTimer && workInProgress.mode & ProfileMode) { let treeBaseTime = workInProgress.selfBaseTime; let child = workInProgress.child; while (child !== null) { @@ -925,7 +925,7 @@ export default function( } let next; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { startBaseRenderTimer(); next = beginWork(current, workInProgress, nextRenderExpirationTime); @@ -972,7 +972,7 @@ export default function( nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. pauseActualRenderTimerIfRunning(); @@ -1019,7 +1019,7 @@ export default function( try { workLoop(isAsync); } catch (thrownValue) { - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // Stop "base" render timer in the event of an error. stopBaseRenderTimerIfRunning(); } @@ -1587,7 +1587,7 @@ export default function( // the deadline. findHighestPriorityRoot(); - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { resumeActualRenderTimerIfPaused(); } @@ -1737,7 +1737,7 @@ export default function( // back and commit it later. root.finishedWork = finishedWork; - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // If we didn't finish, pause the "actual" render timer. // We'll restart it when we resume work. pauseActualRenderTimerIfRunning(); diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index d39af08bb54..3337f02fcd5 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -41,7 +41,7 @@ import { } from 'shared/ReactTypeOfSideEffect'; import { enableGetDerivedStateFromCatch, - enableProfileModeMetrics, + enableProfilerTimer, } from 'shared/ReactFeatureFlags'; export default function( @@ -248,7 +248,7 @@ export default function( popProvider(interruptedWork); break; case Profiler: - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { // Resume in case we're picking up on work that was paused. resumeActualRenderTimerIfPaused(); recordElapsedActualRenderTime(interruptedWork); diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index a05b876bd52..d192bc1e91d 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -9,7 +9,7 @@ import type {Fiber} from './ReactFiber'; -import {enableProfileModeMetrics} from 'shared/ReactFeatureFlags'; +import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; import warning from 'fbjs/lib/warning'; @@ -116,7 +116,7 @@ export function createProfilerTimer(now: () => number): ProfilerTimer { baseStartTime = -1; } - if (enableProfileModeMetrics) { + if (enableProfilerTimer) { return { checkActualRenderTimeStackEmpty, markActualRenderTimeStarted, diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js index a49c7cfa626..1e33804d12f 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js @@ -187,7 +187,7 @@ describe('ReactDebugFiberPerf', () => { expect(getFlameChart()).toMatchSnapshot(); }); - it('does not include AsyncMode, StrictMode, or ProfileMode components in measurements', () => { + it('does not include AsyncMode, StrictMode, or Profiler components in measurements', () => { ReactNoop.render( diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap index 45ea1947c7a..119c55154e3 100644 --- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap +++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap @@ -91,7 +91,7 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc " `; -exports[`ReactDebugFiberPerf does not include AsyncMode, StrictMode, or ProfileMode components in measurements 1`] = ` +exports[`ReactDebugFiberPerf does not include AsyncMode, StrictMode, or Profiler components in measurements 1`] = ` "⚛ (Waiting for async callback... will force flush in 5230 ms) // Mount diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 025d9c7fc31..e97d8eeef97 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -14,13 +14,13 @@ let ReactFeatureFlags; let ReactTestRenderer; function loadModules({ - enableProfileModeMetrics = true, + enableProfilerTimer = true, replayFailedUnitOfWorkWithInvokeGuardedCallback = false, } = {}) { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffects = false; ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; - ReactFeatureFlags.enableProfileModeMetrics = enableProfileModeMetrics; + ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer; ReactFeatureFlags.enableGetDerivedStateFromCatch = true; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback; React = require('react'); @@ -30,13 +30,13 @@ function loadModules({ describe('Profiler', () => { describe('works in profiling and non-profiling bundles', () => { [true, false].forEach(flagEnabled => { - describe(`enableProfileModeMetrics ${ + describe(`enableProfilerTimer ${ flagEnabled ? 'enabled' : 'disabled' }`, () => { beforeEach(() => { jest.resetModules(); - loadModules({enableProfileModeMetrics: flagEnabled}); + loadModules({enableProfilerTimer: flagEnabled}); }); // This will throw in production too, @@ -46,7 +46,7 @@ describe('Profiler', () => { expect(() => { ReactTestRenderer.create(); }).toThrow( - 'ProfileMode must specify an "id" string and "onRender" function as props', + 'Profiler must specify an "id" string and "onRender" function as props', ); }); } @@ -83,7 +83,7 @@ describe('Profiler', () => { expect(renderer.toJSON()).toMatchSnapshot(); }); - it('should support nested ProfileModes', () => { + it('should support nested Profilers', () => { const FunctionalComponent = ({label}) =>
{label}
; class ClassComponent extends React.Component { render() { diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap index 099e81d435e..0022e5abd34 100644 --- a/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap +++ b/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should render children 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer disabled should render children 1`] = `
outside span @@ -14,11 +14,11 @@ exports[`Profiler works in profiling and non-profiling bundles enableProfileMode
`; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty Profiler (with no children) 1`] = `null`; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer disabled should support an empty Profiler (with no children) 1`] = `null`; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support an empty Profiler (with no children) 2`] = `
`; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer disabled should support an empty Profiler (with no children) 2`] = `
`; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics disabled should support nested ProfileModes 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer disabled should support nested Profilers 1`] = ` Array [
outer functional component @@ -32,7 +32,7 @@ Array [ ] `; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should render children 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer enabled should render children 1`] = `
outside span @@ -46,11 +46,11 @@ exports[`Profiler works in profiling and non-profiling bundles enableProfileMode
`; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty Profiler (with no children) 1`] = `null`; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer enabled should support an empty Profiler (with no children) 1`] = `null`; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support an empty Profiler (with no children) 2`] = `
`; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer enabled should support an empty Profiler (with no children) 2`] = `
`; -exports[`Profiler works in profiling and non-profiling bundles enableProfileModeMetrics enabled should support nested ProfileModes 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer enabled should support nested Profilers 1`] = ` Array [
outer functional component diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index ff6e980dc8f..614a1b6cc7e 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -37,8 +37,8 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; // Warn about deprecated, async-unsafe lifecycles; relates to RFC #6: export const warnAboutDeprecatedLifecycles = false; -// Gather advanced timing metrics for ProfileMode subtrees. -export const enableProfileModeMetrics = false; +// Gather advanced timing metrics for Profiler subtrees. +export const enableProfilerTimer = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js index d8cb0786b78..7eb809c952e 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js @@ -18,7 +18,7 @@ export const enableUserTimingAPI = __DEV__; export const enableGetDerivedStateFromCatch = false; export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; -export const enableProfileModeMetrics = __DEV__; +export const enableProfilerTimer = __DEV__; // React Fabric uses persistent reconciler. export const enableMutatingReconciler = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js index 5eabc978444..c6b30c6532f 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js @@ -18,7 +18,7 @@ export const enableUserTimingAPI = __DEV__; export const enableGetDerivedStateFromCatch = false; export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; -export const enableProfileModeMetrics = false; +export const enableProfilerTimer = false; // React Fabric uses persistent reconciler. export const enableMutatingReconciler = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index ad0e80fe81e..51e2133a677 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -19,7 +19,7 @@ export const { debugRenderPhaseSideEffectsForStrictMode, warnAboutDeprecatedLifecycles, replayFailedUnitOfWorkWithInvokeGuardedCallback, - enableProfileModeMetrics, + enableProfilerTimer, } = require('ReactFeatureFlags'); // The rest of the flags are static for better dead code elimination. diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index d13f28cb1fa..5e03310dc08 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -21,7 +21,7 @@ export const enablePersistentReconciler = false; export const enableUserTimingAPI = __DEV__; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; export const warnAboutDeprecatedLifecycles = false; -export const enableProfileModeMetrics = false; +export const enableProfilerTimer = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index 2aadef28616..780c62db4da 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -18,7 +18,7 @@ export const enableUserTimingAPI = __DEV__; export const enableGetDerivedStateFromCatch = false; export const warnAboutDeprecatedLifecycles = false; export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; -export const enableProfileModeMetrics = false; +export const enableProfilerTimer = false; // react-reconciler/persistent entry point // uses a persistent reconciler. diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 290af34d3bb..486aecfa68c 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -21,7 +21,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableMutatingReconciler = true; export const enableNoopReconciler = false; export const enablePersistentReconciler = false; -export const enableProfileModeMetrics = false; +export const enableProfilerTimer = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index ab097040be3..523e1604a65 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -17,7 +17,7 @@ export const { enableGetDerivedStateFromCatch, replayFailedUnitOfWorkWithInvokeGuardedCallback, warnAboutDeprecatedLifecycles, - enableProfileModeMetrics, + enableProfilerTimer, } = require('ReactFeatureFlags'); // The rest of the flags are static for better dead code elimination.