From 845eda9d37aadf3a4028c335bae773025e9e614d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 28 Feb 2017 19:08:52 +0000 Subject: [PATCH 01/37] wip --- .../shared/fiber/ReactDebugFiberPerf.js | 30 +++++++++++++++++++ .../shared/fiber/ReactFiberScheduler.js | 6 ++++ 2 files changed, 36 insertions(+) create mode 100644 src/renderers/shared/fiber/ReactDebugFiberPerf.js diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js new file mode 100644 index 000000000000..50f08b31afda --- /dev/null +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -0,0 +1,30 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDebugFiberPerf + * @flow + */ + +// TODO +let isProfiling = true; + +function markBeginWork() { + +} + +function markBailWork() { + +} + +function markCompleteWork() { + +} + +exports.markCompleteWork = markBeginWork; +exports.markCompleteWork = markBeginWork; +exports.markCompleteWork = markCompleteWork; \ No newline at end of file diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index f1034f542640..bc4f7307d132 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -85,6 +85,12 @@ var { resetContext, } = require('ReactFiberContext'); +// TODO: gate by DEV? +var { + markBeginWork, + markCompleteWork, +} = require('ReactDebugFiberPerf'); + var invariant = require('invariant'); if (__DEV__) { From ad3d5b852735c3b525f5e981398cd031abd7852e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 28 Feb 2017 20:00:25 +0000 Subject: [PATCH 02/37] better --- .../shared/fiber/ReactDebugFiberPerf.js | 50 ++++++++++++++++--- .../shared/fiber/ReactFiberScheduler.js | 4 ++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 50f08b31afda..d182a1677668 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -10,21 +10,57 @@ * @flow */ +const { + IndeterminateComponent, + FunctionalComponent, + ClassComponent, + HostRoot, + HostComponent, + HostText, + HostPortal, + CoroutineComponent, + CoroutineHandlerPhase, + YieldComponent, + Fragment, +} = require('ReactTypeOfWork'); + +const getComponentName = require('getComponentName'); + // TODO let isProfiling = true; -function markBeginWork() { - -} +// TODO: individual render methods -function markBailWork() { +function getLabel(fiber) { + switch (fiber.tag) { + case HostRoot: + return '(root)'; + case HostText: + return '(text)'; + case HostPortal: + return '(portal)'; + case YieldComponent: + return '(yield)'; + case Fragment: + return '(fragment)'; + default: + return getComponentName(fiber); + } +} +function markBeginWork(fiber) { + performance.mark(`react:${fiber._debugID}`); } -function markCompleteWork() { +function markBailWork(fiber) { + +} +function markCompleteWork(fiber) { + const label = getLabel(fiber); + performance.measure(label, `react:${fiber._debugID}`); } -exports.markCompleteWork = markBeginWork; -exports.markCompleteWork = markBeginWork; +exports.markBeginWork = markBeginWork; +exports.markBailWork = markBailWork; exports.markCompleteWork = markCompleteWork; \ No newline at end of file diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index bc4f7307d132..a44adde37658 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -546,6 +546,7 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig Date: Tue, 28 Feb 2017 20:30:18 +0000 Subject: [PATCH 03/37] better --- .../shared/fiber/ReactDebugFiberPerf.js | 31 ++++++++++++++++--- .../shared/fiber/ReactFiberBeginWork.js | 9 ++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index d182a1677668..d507b5f69c12 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -48,17 +48,40 @@ function getLabel(fiber) { } } +function getMarkName(fiber) { + return `react:${fiber._debugID}`; +} + +function shouldIgnore(fiber) { + return typeof fiber.type === 'string'; +} + +let bailedFibers = new Set(); + function markBeginWork(fiber) { - performance.mark(`react:${fiber._debugID}`); + if (shouldIgnore(fiber)) { + return; + } + performance.mark(getMarkName(fiber)); } function markBailWork(fiber) { - + if (shouldIgnore(fiber)) { + return; + } + bailedFibers.add(fiber); + performance.clearMarks(getMarkName(fiber)); } function markCompleteWork(fiber) { - const label = getLabel(fiber); - performance.measure(label, `react:${fiber._debugID}`); + if (shouldIgnore(fiber)) { + return; + } + if (bailedFibers.has(fiber)) { + bailedFibers.delete(fiber); + } else { + performance.measure(getLabel(fiber), getMarkName(fiber)); + } } exports.markBeginWork = markBeginWork; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index b88be76bf0ed..c31146e11584 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -70,6 +70,11 @@ if (__DEV__) { var warnedAboutStatelessRefs = {}; } +// TODO +const { + markBailWork, +} = require('ReactDebugFiberPerf'); + module.exports = function( config : HostConfig, hostContext : HostContext, @@ -650,6 +655,8 @@ module.exports = function( */ function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : Fiber | null { + markBailWork(workInProgress); + const priorityLevel = workInProgress.pendingWorkPriority; // 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 @@ -677,6 +684,8 @@ module.exports = function( } function bailoutOnLowPriority(current, workInProgress) { + markBailWork(workInProgress); + // TODO: Handle HostComponent tags here as well and call pushHostContext()? // See PR 8590 discussion for context switch (workInProgress.tag) { From cc2794d26cbda07b9fcbaa1a9da6f92cbeb93d21 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 28 Feb 2017 20:56:59 +0000 Subject: [PATCH 04/37] track commits --- .../shared/fiber/ReactDebugFiberPerf.js | 22 ++++++++++++++++++- .../shared/fiber/ReactFiberScheduler.js | 13 +++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index d507b5f69c12..2d0b3c808930 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -84,6 +84,26 @@ function markCompleteWork(fiber) { } } +function markWillCommit() { + performance.mark('react:commit'); +} + +function markDidCommit() { + performance.measure('React: Commit Tree', 'react:commit'); +} + +function markWillReconcile() { + performance.mark('react:reconcile'); +} + +function markDidReconcile() { + performance.measure('React: Reconcile Tree', 'react:reconcile'); +} + exports.markBeginWork = markBeginWork; exports.markBailWork = markBailWork; -exports.markCompleteWork = markCompleteWork; \ No newline at end of file +exports.markCompleteWork = markCompleteWork; +exports.markWillCommit = markWillCommit; +exports.markDidCommit = markDidCommit; +exports.markWillReconcile = markWillReconcile; +exports.markDidReconcile = markDidReconcile; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index a44adde37658..4eeeff8da50f 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -89,6 +89,10 @@ var { var { markBeginWork, markCompleteWork, + markWillCommit, + markDidCommit, + markWillReconcile, + markDidReconcile, } = require('ReactDebugFiberPerf'); var invariant = require('invariant'); @@ -383,6 +387,7 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Tue, 28 Feb 2017 21:05:55 +0000 Subject: [PATCH 05/37] better --- .../shared/fiber/ReactDebugFiberPerf.js | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 2d0b3c808930..b7fda4d08708 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -31,31 +31,24 @@ let isProfiling = true; // TODO: individual render methods -function getLabel(fiber) { +function getMarkName(fiber) { + return `react:${fiber._debugID}`; +} + +function shouldIgnore(fiber) { switch (fiber.tag) { case HostRoot: - return '(root)'; + case HostComponent: case HostText: - return '(text)'; case HostPortal: - return '(portal)'; case YieldComponent: - return '(yield)'; case Fragment: - return '(fragment)'; + return true; default: - return getComponentName(fiber); + return false; } } -function getMarkName(fiber) { - return `react:${fiber._debugID}`; -} - -function shouldIgnore(fiber) { - return typeof fiber.type === 'string'; -} - let bailedFibers = new Set(); function markBeginWork(fiber) { @@ -80,7 +73,7 @@ function markCompleteWork(fiber) { if (bailedFibers.has(fiber)) { bailedFibers.delete(fiber); } else { - performance.measure(getLabel(fiber), getMarkName(fiber)); + performance.measure(getComponentName(fiber), getMarkName(fiber)); } } @@ -89,7 +82,7 @@ function markWillCommit() { } function markDidCommit() { - performance.measure('React: Commit Tree', 'react:commit'); + performance.measure('Commit React Tree', 'react:commit'); } function markWillReconcile() { @@ -97,7 +90,7 @@ function markWillReconcile() { } function markDidReconcile() { - performance.measure('React: Reconcile Tree', 'react:reconcile'); + performance.measure('Reconcile React Tree', 'react:reconcile'); } exports.markBeginWork = markBeginWork; From b4702f9967dc127cccb779101e24389b9b9352c6 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 1 Mar 2017 22:18:58 +0000 Subject: [PATCH 06/37] wip --- src/renderers/dom/fiber/ReactDOMFiber.js | 2 +- .../shared/fiber/ReactDebugFiberPerf.js | 46 +++++++++++++++++-- .../shared/fiber/ReactFiberScheduler.js | 3 ++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index cebe0414812b..2a4b7b5bb9a5 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -323,7 +323,7 @@ var DOMRenderer = ReactFiberReconciler({ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC, - useSyncScheduling: true, + useSyncScheduling: false, }); diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index b7fda4d08708..6f0185160aa4 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -32,7 +32,7 @@ let isProfiling = true; // TODO: individual render methods function getMarkName(fiber) { - return `react:${fiber._debugID}`; + return `react:${flushIndex}:${fiber._debugID}`; } function shouldIgnore(fiber) { @@ -50,8 +50,12 @@ function shouldIgnore(fiber) { } let bailedFibers = new Set(); +let lastCompletedFiber = null; +let pausedFibers = []; +let flushIndex = 0; function markBeginWork(fiber) { + lastCompletedFiber = null; if (shouldIgnore(fiber)) { return; } @@ -67,14 +71,16 @@ function markBailWork(fiber) { } function markCompleteWork(fiber) { + lastCompletedFiber = fiber; if (shouldIgnore(fiber)) { - return; + return false; } if (bailedFibers.has(fiber)) { bailedFibers.delete(fiber); } else { performance.measure(getComponentName(fiber), getMarkName(fiber)); } + return true; } function markWillCommit() { @@ -82,15 +88,46 @@ function markWillCommit() { } function markDidCommit() { - performance.measure('Commit React Tree', 'react:commit'); + performance.measure('(React) Commit Tree', 'react:commit'); } function markWillReconcile() { + flushIndex++; performance.mark('react:reconcile'); + resumeStack(); } function markDidReconcile() { - performance.measure('Reconcile React Tree', 'react:reconcile'); + rewindStack(); + performance.measure('(React) Reconcile Tree', 'react:reconcile'); +} + +function markReset() { + resetStack(); +} + +function rewindStack() { + while (lastCompletedFiber) { + const parent = lastCompletedFiber.return; + if (parent) { + if (markCompleteWork(parent)) { + pausedFibers.unshift(parent); + } + } + lastCompletedFiber = parent; + } +} + +function resumeStack() { + while (pausedFibers.length) { + const parent = pausedFibers.shift(); + markBeginWork(parent); + } +} + +function resetStack() { + pausedFibers.length = 0; + bailedFibers.clear(); } exports.markBeginWork = markBeginWork; @@ -100,3 +137,4 @@ exports.markWillCommit = markWillCommit; exports.markDidCommit = markDidCommit; exports.markWillReconcile = markWillReconcile; exports.markDidReconcile = markDidReconcile; +exports.markReset = markReset; \ No newline at end of file diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 4eeeff8da50f..9e0d22b8ae63 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -93,6 +93,7 @@ var { markDidCommit, markWillReconcile, markDidReconcile, + markReset, } = require('ReactDebugFiberPerf'); var invariant = require('invariant'); @@ -234,6 +235,8 @@ module.exports = function(config : HostConfig Date: Thu, 2 Mar 2017 02:35:49 +0000 Subject: [PATCH 07/37] Fix --- .../shared/fiber/ReactDebugFiberPerf.js | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 6f0185160aa4..a3b3acd8af77 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -35,6 +35,18 @@ function getMarkName(fiber) { return `react:${flushIndex}:${fiber._debugID}`; } +function setBeginMark(fiber) { + performance.mark(getMarkName(fiber)); +} + +function clearBeginMark(fiber) { + performance.clearMarks(getMarkName(fiber)); +} + +function setCompleteMark(fiber) { + performance.measure(getComponentName(fiber), getMarkName(fiber)); +} + function shouldIgnore(fiber) { switch (fiber.tag) { case HostRoot: @@ -50,16 +62,16 @@ function shouldIgnore(fiber) { } let bailedFibers = new Set(); -let lastCompletedFiber = null; -let pausedFibers = []; +let currentFiber = null; +let stashedFibers = []; let flushIndex = 0; function markBeginWork(fiber) { - lastCompletedFiber = null; + currentFiber = fiber; if (shouldIgnore(fiber)) { return; } - performance.mark(getMarkName(fiber)); + setBeginMark(fiber); } function markBailWork(fiber) { @@ -67,20 +79,19 @@ function markBailWork(fiber) { return; } bailedFibers.add(fiber); - performance.clearMarks(getMarkName(fiber)); + clearBeginMark(fiber); } function markCompleteWork(fiber) { - lastCompletedFiber = fiber; + currentFiber = fiber.return; if (shouldIgnore(fiber)) { - return false; + return; } if (bailedFibers.has(fiber)) { bailedFibers.delete(fiber); - } else { - performance.measure(getComponentName(fiber), getMarkName(fiber)); + return; } - return true; + setCompleteMark(fiber); } function markWillCommit() { @@ -94,11 +105,11 @@ function markDidCommit() { function markWillReconcile() { flushIndex++; performance.mark('react:reconcile'); - resumeStack(); + rewindStack(); } function markDidReconcile() { - rewindStack(); + unwindStack(); performance.measure('(React) Reconcile Tree', 'react:reconcile'); } @@ -106,27 +117,25 @@ function markReset() { resetStack(); } -function rewindStack() { - while (lastCompletedFiber) { - const parent = lastCompletedFiber.return; - if (parent) { - if (markCompleteWork(parent)) { - pausedFibers.unshift(parent); - } +function unwindStack() { + while (currentFiber) { + if (!shouldIgnore(currentFiber) && !bailedFibers.has(currentFiber)) { + setCompleteMark(currentFiber); + stashedFibers.unshift(currentFiber); } - lastCompletedFiber = parent; + currentFiber = currentFiber.return; } } -function resumeStack() { - while (pausedFibers.length) { - const parent = pausedFibers.shift(); - markBeginWork(parent); +function rewindStack() { + while (stashedFibers.length) { + const parent = stashedFibers.shift(); + setBeginMark(parent); } } function resetStack() { - pausedFibers.length = 0; + stashedFibers.length = 0; bailedFibers.clear(); } From 8d0405f3f36b8f6d1005b7099aac972b52cac698 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 2 Mar 2017 04:35:11 +0000 Subject: [PATCH 08/37] Add some lifecycles --- .../shared/fiber/ReactDebugFiberPerf.js | 51 ++++++++++++++++--- .../shared/fiber/ReactFiberBeginWork.js | 8 +++ .../shared/fiber/ReactFiberClassComponent.js | 36 +++++++++++++ .../shared/fiber/ReactFiberCommitWork.js | 25 ++++++++- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index a3b3acd8af77..4faae98565b2 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -31,20 +31,24 @@ let isProfiling = true; // TODO: individual render methods -function getMarkName(fiber) { - return `react:${flushIndex}:${fiber._debugID}`; +function getMarkName(fiber, kind) { + return `react:${flushIndex}:${fiber._debugID}:${kind}`; } function setBeginMark(fiber) { - performance.mark(getMarkName(fiber)); + performance.mark(getMarkName(fiber, 'total')); } function clearBeginMark(fiber) { - performance.clearMarks(getMarkName(fiber)); + performance.clearMarks(getMarkName(fiber, 'total')); } function setCompleteMark(fiber) { - performance.measure(getComponentName(fiber), getMarkName(fiber)); + try { + performance.measure(`<${getComponentName(fiber) || 'Unknown'}>`, getMarkName(fiber, 'total')); + } catch (err) { + // Ignore. + } } function shouldIgnore(fiber) { @@ -65,8 +69,11 @@ let bailedFibers = new Set(); let currentFiber = null; let stashedFibers = []; let flushIndex = 0; +let lifecycleFiber = null; +let lifecyclePhase = null; function markBeginWork(fiber) { + clearLifecycle(); currentFiber = fiber; if (shouldIgnore(fiber)) { return; @@ -94,12 +101,40 @@ function markCompleteWork(fiber) { setCompleteMark(fiber); } +function markWillLifecycle(fiber, phase) { + clearLifecycle(); + lifecycleFiber = fiber; + lifecyclePhase = phase; + performance.mark(getMarkName(fiber, phase)); +} + +function markDidLifecycle() { + try { + performance.measure( + `${getComponentName(lifecycleFiber) || 'Unknown'}.${lifecyclePhase}`, + getMarkName(lifecycleFiber, lifecyclePhase) + ); + } catch (err) { + // Ignore errors. + } + lifecycleFiber = null; + lifecyclePhase = null; +} + +function clearLifecycle() { + if (lifecycleFiber) { + performance.clearMarks(getMarkName(lifecycleFiber, lifecyclePhase)); + } + lifecycleFiber = null; + lifecyclePhase = null; +} + function markWillCommit() { performance.mark('react:commit'); } function markDidCommit() { - performance.measure('(React) Commit Tree', 'react:commit'); + performance.measure('React: Commit Tree', 'react:commit'); } function markWillReconcile() { @@ -110,7 +145,7 @@ function markWillReconcile() { function markDidReconcile() { unwindStack(); - performance.measure('(React) Reconcile Tree', 'react:reconcile'); + performance.measure('React: Reconcile Tree', 'react:reconcile'); } function markReset() { @@ -146,4 +181,6 @@ exports.markWillCommit = markWillCommit; exports.markDidCommit = markDidCommit; exports.markWillReconcile = markWillReconcile; exports.markDidReconcile = markDidReconcile; +exports.markWillLifecycle = markWillLifecycle; +exports.markDidLifecycle = markDidLifecycle; exports.markReset = markReset; \ No newline at end of file diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index c31146e11584..f11be8a76e0e 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -73,6 +73,8 @@ if (__DEV__) { // TODO const { markBailWork, + markWillLifecycle, + markDidLifecycle, } = require('ReactDebugFiberPerf'); module.exports = function( @@ -236,7 +238,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; ReactDebugCurrentFiber.phase = 'render'; + markWillLifecycle(workInProgress, 'render'); nextChildren = fn(nextProps, context); + markDidLifecycle(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = fn(nextProps, context); @@ -289,7 +293,9 @@ module.exports = function( let nextChildren; if (__DEV__) { ReactDebugCurrentFiber.phase = 'render'; + markWillLifecycle(workInProgress, 'render'); nextChildren = instance.render(); + markDidLifecycle(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = instance.render(); @@ -472,7 +478,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; + markWillLifecycle(workInProgress, 'render'); value = fn(props, context); + markDidLifecycle(); } else { value = fn(props, context); } diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 28562795370d..87f259d5a444 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -37,6 +37,12 @@ var emptyObject = require('emptyObject'); var shallowEqual = require('shallowEqual'); var invariant = require('invariant'); +// TODO +const { + markWillLifecycle, + markDidLifecycle, +} = require('ReactDebugFiberPerf'); + const isArray = Array.isArray; if (__DEV__) { @@ -102,7 +108,13 @@ module.exports = function( const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { + if (__DEV__) { + markWillLifecycle(workInProgress, 'shouldComponentUpdate') + } const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext); + if (__DEV__) { + markDidLifecycle() + } if (__DEV__) { warning( @@ -295,7 +307,13 @@ module.exports = function( instance.context = getMaskedContext(workInProgress, unmaskedContext); if (typeof instance.componentWillMount === 'function') { + if (__DEV__) { + markWillLifecycle(workInProgress, 'componentWillMount') + } instance.componentWillMount(); + if (__DEV__) { + markDidLifecycle(); + } // If we had additional state updates during this life-cycle, let's // process them now. const updateQueue = workInProgress.updateQueue; @@ -362,7 +380,13 @@ module.exports = function( newInstance.context = newContext; if (typeof newInstance.componentWillMount === 'function') { + if (__DEV__) { + markWillLifecycle(workInProgress, 'componentWillMount') + } newInstance.componentWillMount(); + if (__DEV__) { + markDidLifecycle(); + } } // If we had additional state updates, process them now. // They may be from componentWillMount() or from error boundary's setState() @@ -408,7 +432,13 @@ module.exports = function( if (oldProps !== newProps || oldContext !== newContext) { if (typeof instance.componentWillReceiveProps === 'function') { + if (__DEV__) { + markWillLifecycle(workInProgress, 'componentWillReceiveProps') + } instance.componentWillReceiveProps(newProps, newContext); + if (__DEV__) { + markDidLifecycle(); + } if (instance.state !== workInProgress.memoizedState) { if (__DEV__) { @@ -463,7 +493,13 @@ module.exports = function( if (shouldUpdate) { markUpdate(workInProgress); if (typeof instance.componentWillUpdate === 'function') { + if (__DEV__) { + markWillLifecycle(workInProgress, 'componentWillUpdate') + } instance.componentWillUpdate(newProps, newState, newContext); + if (__DEV__) { + markDidLifecycle(); + } } } else { markUpdateIfAlreadyInProgress(current, workInProgress); diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index b9bc6d6fb47e..b8b32570681b 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -35,6 +35,13 @@ var { ContentReset, } = require('ReactTypeOfSideEffect'); +// TODO +const { + markWillLifecycle, + markDidLifecycle, +} = require('ReactDebugFiberPerf'); + + var invariant = require('invariant'); module.exports = function( @@ -56,7 +63,11 @@ module.exports = function( // Capture errors so they don't interrupt unmounting. function safelyCallComponentWillUnmount(current, instance) { if (__DEV__) { - const unmountError = invokeGuardedCallback(null, instance.componentWillUnmount, instance); + const unmountError = invokeGuardedCallback(null, () => { + markWillLifecycle(current, 'componentWillUnmount'); + instance.componentWillUnmount(); + markDidLifecycle(); + }); if (unmountError) { captureError(current, unmountError); } @@ -436,13 +447,25 @@ module.exports = function( if (finishedWork.effectTag & Update) { if (current === null) { if (typeof instance.componentDidMount === 'function') { + if (__DEV__) { + markWillLifecycle(finishedWork, 'componentDidMount') + } instance.componentDidMount(); + if (__DEV__) { + markDidLifecycle(); + } } } else { if (typeof instance.componentDidUpdate === 'function') { const prevProps = current.memoizedProps; const prevState = current.memoizedState; + if (__DEV__) { + markWillLifecycle(finishedWork, 'componentDidUpdate') + } instance.componentDidUpdate(prevProps, prevState); + if (__DEV__) { + markDidLifecycle(); + } } } } From 10954303657366701a64dd491af6bab072a4cd49 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 2 Mar 2017 22:26:39 +0000 Subject: [PATCH 09/37] wip --- .../shared/fiber/ReactDebugFiberPerf.js | 262 +++++++++++------- .../shared/fiber/ReactFiberBeginWork.js | 22 +- .../shared/fiber/ReactFiberClassComponent.js | 24 +- .../shared/fiber/ReactFiberCommitWork.js | 16 +- .../shared/fiber/ReactFiberScheduler.js | 32 +-- 5 files changed, 202 insertions(+), 154 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 4faae98565b2..b94986c2e01a 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -11,47 +11,78 @@ */ const { - IndeterminateComponent, - FunctionalComponent, - ClassComponent, HostRoot, HostComponent, HostText, HostPortal, - CoroutineComponent, - CoroutineHandlerPhase, YieldComponent, Fragment, } = require('ReactTypeOfWork'); - const getComponentName = require('getComponentName'); -// TODO -let isProfiling = true; +const supportsUserTiming = + typeof performance !== 'undefined' && + typeof performance.mark === 'function' && + typeof performance.clearMarks === 'function' && + typeof performance.measure === 'function' && + typeof performance.clearMeasures === 'function'; -// TODO: individual render methods +// Keep track of fibers that bailed out because we clear their marks and +// don't measure them. This prevents giant flamecharts where little changed. +let bailedFibers = new Set(); +// When we exit a deferred loop, we might not have finished the work. However +// the next unit of work pointer is still in the middle of the tree. We keep +// track of the parent path when exiting the loop so that we can unwind the +// flamechart measurements, and later rewind them when we resume work. +let stashedFibers = []; +// Keep track of current fiber so that we know the path to unwind on pause. +let currentFiber = null; +// If we're in the middle of user code, which fiber and method is it? +// Reusing `currentFiber` would be confusing for this because user code fiber +// can change during commit phase too, but we don't need to unwind it (since +// lifecycles in the commit phase don't resemble a tree). +let userCodePhase = null; +let userCodeFiber = null; + +function performanceMeasureSafe(label, markName) { + try { + performance.measure(label, markName); + } catch (err) { + // If previous mark was missing for some reason, this will throw. + // This could only happen if React crashed in an unexpected place earlier. + // Don't pile on with more errors. + } + // Clear marks immediately to avoid growing buffer. + performance.clearMarks(markName); + performance.clearMeasures(label); +} -function getMarkName(fiber, kind) { - return `react:${flushIndex}:${fiber._debugID}:${kind}`; +function getMarkName(fiber, phase) { + return `react:${fiber._debugID}:${phase}`; } -function setBeginMark(fiber) { - performance.mark(getMarkName(fiber, 'total')); +function beginMeasurement(fiber, phase) { + const markName = getMarkName(fiber, phase); + performance.mark(markName); } -function clearBeginMark(fiber) { - performance.clearMarks(getMarkName(fiber, 'total')); +function clearPendingMeasurement(fiber, phase) { + const markName = getMarkName(fiber, phase); + performance.clearMarks(markName); } -function setCompleteMark(fiber) { - try { - performance.measure(`<${getComponentName(fiber) || 'Unknown'}>`, getMarkName(fiber, 'total')); - } catch (err) { - // Ignore. - } +function completeMeasurement(fiber, phase) { + const markName = getMarkName(fiber, phase); + const componentName = getComponentName(fiber) || 'Unknown'; + const label = phase === 'total' ? + `<${componentName}>` : + `${componentName}.${phase}`; + performanceMeasureSafe(markName, label); } function shouldIgnore(fiber) { + // Host components should be skipped in the timeline. + // We could check typeof fiber.type, but does this work with RN? switch (fiber.tag) { case HostRoot: case HostComponent: @@ -65,31 +96,72 @@ function shouldIgnore(fiber) { } } -let bailedFibers = new Set(); -let currentFiber = null; -let stashedFibers = []; -let flushIndex = 0; -let lifecycleFiber = null; -let lifecyclePhase = null; +function clearPendingUserCodeMeasurement() { + if (userCodePhase !== null && userCodeFiber !== null) { + clearPendingMeasurement(userCodeFiber, userCodePhase); + } + userCodeFiber = null; + userCodePhase = null; +} + +function unwindStack() { + // Stops all currently active measurements so that they can be resumed + // if we continue in a later deferred loop from the same unit of work. + while (currentFiber) { + if (!shouldIgnore(currentFiber) && !bailedFibers.has(currentFiber)) { + completeMeasurement(currentFiber, 'total'); + stashedFibers.unshift(currentFiber); + } + currentFiber = currentFiber.return; + } +} + +function rewindStack() { + // Resumes all measurements that were active during the last deferred loop. + while (stashedFibers.length) { + const parent = stashedFibers.shift(); + beginMeasurement(parent, 'total'); + } +} -function markBeginWork(fiber) { - clearLifecycle(); +function resetStack() { + // If we started new work, this state is no longer valid. + stashedFibers.length = 0; + bailedFibers.clear(); +} + +exports.markBeforeWork = function markBeforeWork(fiber) { + if (!supportsUserTiming) { + return; + } + clearPendingUserCodeMeasurement(); + // If we pause, this is the fiber to unwind from. currentFiber = fiber; if (shouldIgnore(fiber)) { return; } - setBeginMark(fiber); -} + beginMeasurement(fiber, 'total'); +}; -function markBailWork(fiber) { +exports.markCurrentWorkAsBailed = function markCurrentWorkAsBailed(fiber) { + if (!supportsUserTiming) { + return; + } if (shouldIgnore(fiber)) { return; } + // Remember we shouldn't complete measurement for this fiber. + // Otherwise flamechart will be deep even for small updates. bailedFibers.add(fiber); - clearBeginMark(fiber); -} + clearPendingMeasurement(fiber, 'total'); +}; -function markCompleteWork(fiber) { +exports.markAfterWork = function markAfterWork(fiber) { + if (!supportsUserTiming) { + return; + } + clearPendingUserCodeMeasurement(); + // If we pause, its parent is the fiber to unwind from. currentFiber = fiber.return; if (shouldIgnore(fiber)) { return; @@ -98,89 +170,65 @@ function markCompleteWork(fiber) { bailedFibers.delete(fiber); return; } - setCompleteMark(fiber); -} - -function markWillLifecycle(fiber, phase) { - clearLifecycle(); - lifecycleFiber = fiber; - lifecyclePhase = phase; - performance.mark(getMarkName(fiber, phase)); -} + completeMeasurement(fiber, 'total'); +}; -function markDidLifecycle() { - try { - performance.measure( - `${getComponentName(lifecycleFiber) || 'Unknown'}.${lifecyclePhase}`, - getMarkName(lifecycleFiber, lifecyclePhase) - ); - } catch (err) { - // Ignore errors. +exports.markBeforeUserCode = function markBeforeUserCode(fiber, phase) { + if (!supportsUserTiming) { + return; } - lifecycleFiber = null; - lifecyclePhase = null; -} - -function clearLifecycle() { - if (lifecycleFiber) { - performance.clearMarks(getMarkName(lifecycleFiber, lifecyclePhase)); + clearPendingUserCodeMeasurement(); + userCodeFiber = fiber; + userCodePhase = phase; + beginMeasurement(fiber, phase); +}; + +exports.markAfterUserCode = function markAfterUserCode() { + if (!supportsUserTiming) { + return; } - lifecycleFiber = null; - lifecyclePhase = null; -} - -function markWillCommit() { - performance.mark('react:commit'); -} + completeMeasurement(userCodeFiber, userCodePhase); + userCodePhase = null; + userCodeFiber = null; +}; -function markDidCommit() { - performance.measure('React: Commit Tree', 'react:commit'); -} - -function markWillReconcile() { - flushIndex++; +exports.markBeforeWorkLoop = function markBeforeWorkLoop() { + if (!supportsUserTiming) { + return; + } + // This is top level call. + // Any other measurements are performed within. performance.mark('react:reconcile'); + // Resume any measurements that were in progress during the last loop. rewindStack(); -} +}; -function markDidReconcile() { +exports.markAfterWorkLoop = function markAfterWorkLoop() { + if (!supportsUserTiming) { + return; + } + // Pause any measurements until the next loop. unwindStack(); performance.measure('React: Reconcile Tree', 'react:reconcile'); -} - -function markReset() { - resetStack(); -} +}; -function unwindStack() { - while (currentFiber) { - if (!shouldIgnore(currentFiber) && !bailedFibers.has(currentFiber)) { - setCompleteMark(currentFiber); - stashedFibers.unshift(currentFiber); - } - currentFiber = currentFiber.return; +exports.markBeforeCommit = function markBeforeCommit() { + if (!supportsUserTiming) { + return; } -} + performance.mark('react:commit'); +}; -function rewindStack() { - while (stashedFibers.length) { - const parent = stashedFibers.shift(); - setBeginMark(parent); +exports.markAfterCommit = function markAfterCommit() { + if (!supportsUserTiming) { + return; } -} - -function resetStack() { - stashedFibers.length = 0; - bailedFibers.clear(); -} + performance.measure('React: Commit Tree', 'react:commit'); +}; -exports.markBeginWork = markBeginWork; -exports.markBailWork = markBailWork; -exports.markCompleteWork = markCompleteWork; -exports.markWillCommit = markWillCommit; -exports.markDidCommit = markDidCommit; -exports.markWillReconcile = markWillReconcile; -exports.markDidReconcile = markDidReconcile; -exports.markWillLifecycle = markWillLifecycle; -exports.markDidLifecycle = markDidLifecycle; -exports.markReset = markReset; \ No newline at end of file +exports.markWorkLoopAsRestarted = function markWorkLoopAsRestarted() { + if (!supportsUserTiming) { + return; + } + resetStack(); +}; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index f11be8a76e0e..487a7a3c0c83 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -72,9 +72,9 @@ if (__DEV__) { // TODO const { - markBailWork, - markWillLifecycle, - markDidLifecycle, + markCurrentWorkAsBailed, + markBeforeUserCode, + markAfterUserCode, } = require('ReactDebugFiberPerf'); module.exports = function( @@ -238,9 +238,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; ReactDebugCurrentFiber.phase = 'render'; - markWillLifecycle(workInProgress, 'render'); + markBeforeUserCode(workInProgress, 'render'); nextChildren = fn(nextProps, context); - markDidLifecycle(); + markAfterUserCode(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = fn(nextProps, context); @@ -293,9 +293,9 @@ module.exports = function( let nextChildren; if (__DEV__) { ReactDebugCurrentFiber.phase = 'render'; - markWillLifecycle(workInProgress, 'render'); + markBeforeUserCode(workInProgress, 'render'); nextChildren = instance.render(); - markDidLifecycle(); + markAfterUserCode(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = instance.render(); @@ -478,9 +478,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; - markWillLifecycle(workInProgress, 'render'); + markBeforeUserCode(workInProgress, 'render'); value = fn(props, context); - markDidLifecycle(); + markAfterUserCode(); } else { value = fn(props, context); } @@ -663,7 +663,7 @@ module.exports = function( */ function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : Fiber | null { - markBailWork(workInProgress); + markCurrentWorkAsBailed(workInProgress); const priorityLevel = workInProgress.pendingWorkPriority; // TODO: We should ideally be able to bail out early if the children have no @@ -692,7 +692,7 @@ module.exports = function( } function bailoutOnLowPriority(current, workInProgress) { - markBailWork(workInProgress); + markCurrentWorkAsBailed(workInProgress); // TODO: Handle HostComponent tags here as well and call pushHostContext()? // See PR 8590 discussion for context diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 87f259d5a444..77ae0c54f0f0 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -39,8 +39,8 @@ var invariant = require('invariant'); // TODO const { - markWillLifecycle, - markDidLifecycle, + markBeforeUserCode, + markAfterUserCode, } = require('ReactDebugFiberPerf'); const isArray = Array.isArray; @@ -109,11 +109,11 @@ module.exports = function( const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { if (__DEV__) { - markWillLifecycle(workInProgress, 'shouldComponentUpdate') + markBeforeUserCode(workInProgress, 'shouldComponentUpdate'); } const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext); if (__DEV__) { - markDidLifecycle() + markAfterUserCode(); } if (__DEV__) { @@ -308,11 +308,11 @@ module.exports = function( if (typeof instance.componentWillMount === 'function') { if (__DEV__) { - markWillLifecycle(workInProgress, 'componentWillMount') + markBeforeUserCode(workInProgress, 'componentWillMount'); } instance.componentWillMount(); if (__DEV__) { - markDidLifecycle(); + markAfterUserCode(); } // If we had additional state updates during this life-cycle, let's // process them now. @@ -381,11 +381,11 @@ module.exports = function( if (typeof newInstance.componentWillMount === 'function') { if (__DEV__) { - markWillLifecycle(workInProgress, 'componentWillMount') + markBeforeUserCode(workInProgress, 'componentWillMount'); } newInstance.componentWillMount(); if (__DEV__) { - markDidLifecycle(); + markAfterUserCode(); } } // If we had additional state updates, process them now. @@ -433,11 +433,11 @@ module.exports = function( if (oldProps !== newProps || oldContext !== newContext) { if (typeof instance.componentWillReceiveProps === 'function') { if (__DEV__) { - markWillLifecycle(workInProgress, 'componentWillReceiveProps') + markBeforeUserCode(workInProgress, 'componentWillReceiveProps'); } instance.componentWillReceiveProps(newProps, newContext); if (__DEV__) { - markDidLifecycle(); + markAfterUserCode(); } if (instance.state !== workInProgress.memoizedState) { @@ -494,11 +494,11 @@ module.exports = function( markUpdate(workInProgress); if (typeof instance.componentWillUpdate === 'function') { if (__DEV__) { - markWillLifecycle(workInProgress, 'componentWillUpdate') + markBeforeUserCode(workInProgress, 'componentWillUpdate'); } instance.componentWillUpdate(newProps, newState, newContext); if (__DEV__) { - markDidLifecycle(); + markAfterUserCode(); } } } else { diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index b8b32570681b..b7dfddef9f9d 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -37,8 +37,8 @@ var { // TODO const { - markWillLifecycle, - markDidLifecycle, + markBeforeUserCode, + markAfterUserCode, } = require('ReactDebugFiberPerf'); @@ -64,9 +64,9 @@ module.exports = function( function safelyCallComponentWillUnmount(current, instance) { if (__DEV__) { const unmountError = invokeGuardedCallback(null, () => { - markWillLifecycle(current, 'componentWillUnmount'); + markBeforeUserCode(current, 'componentWillUnmount'); instance.componentWillUnmount(); - markDidLifecycle(); + markAfterUserCode(); }); if (unmountError) { captureError(current, unmountError); @@ -448,11 +448,11 @@ module.exports = function( if (current === null) { if (typeof instance.componentDidMount === 'function') { if (__DEV__) { - markWillLifecycle(finishedWork, 'componentDidMount') + markBeforeUserCode(finishedWork, 'componentDidMount'); } instance.componentDidMount(); if (__DEV__) { - markDidLifecycle(); + markAfterUserCode(); } } } else { @@ -460,11 +460,11 @@ module.exports = function( const prevProps = current.memoizedProps; const prevState = current.memoizedState; if (__DEV__) { - markWillLifecycle(finishedWork, 'componentDidUpdate') + markBeforeUserCode(finishedWork, 'componentDidUpdate') } instance.componentDidUpdate(prevProps, prevState); if (__DEV__) { - markDidLifecycle(); + markAfterUserCode(); } } } diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 9e0d22b8ae63..53b1e3ba4f6e 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -87,13 +87,13 @@ var { // TODO: gate by DEV? var { - markBeginWork, - markCompleteWork, - markWillCommit, - markDidCommit, - markWillReconcile, - markDidReconcile, - markReset, + markBeforeWork, + markAfterWork, + markBeforeCommit, + markAfterCommit, + markBeforeWorkLoop, + markAfterWorkLoop, + markWorkLoopAsRestarted, } = require('ReactDebugFiberPerf'); var invariant = require('invariant'); @@ -236,7 +236,7 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Thu, 2 Mar 2017 22:39:37 +0000 Subject: [PATCH 10/37] Naming --- .../shared/fiber/ReactDebugFiberPerf.js | 60 +++++++++---------- .../shared/fiber/ReactFiberBeginWork.js | 22 +++---- .../shared/fiber/ReactFiberClassComponent.js | 24 ++++---- .../shared/fiber/ReactFiberCommitWork.js | 16 ++--- .../shared/fiber/ReactFiberScheduler.js | 32 +++++----- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index b94986c2e01a..ff3eb33d635c 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -29,12 +29,12 @@ const supportsUserTiming = // Keep track of fibers that bailed out because we clear their marks and // don't measure them. This prevents giant flamecharts where little changed. -let bailedFibers = new Set(); +let skippedFibers = new Set(); // When we exit a deferred loop, we might not have finished the work. However // the next unit of work pointer is still in the middle of the tree. We keep // track of the parent path when exiting the loop so that we can unwind the // flamechart measurements, and later rewind them when we resume work. -let stashedFibers = []; +let pausedFibers = []; // Keep track of current fiber so that we know the path to unwind on pause. let currentFiber = null; // If we're in the middle of user code, which fiber and method is it? @@ -77,7 +77,7 @@ function completeMeasurement(fiber, phase) { const label = phase === 'total' ? `<${componentName}>` : `${componentName}.${phase}`; - performanceMeasureSafe(markName, label); + performanceMeasureSafe(label, markName); } function shouldIgnore(fiber) { @@ -104,33 +104,33 @@ function clearPendingUserCodeMeasurement() { userCodePhase = null; } -function unwindStack() { +function pauseTimers() { // Stops all currently active measurements so that they can be resumed // if we continue in a later deferred loop from the same unit of work. while (currentFiber) { - if (!shouldIgnore(currentFiber) && !bailedFibers.has(currentFiber)) { + if (!shouldIgnore(currentFiber) && !skippedFibers.has(currentFiber)) { completeMeasurement(currentFiber, 'total'); - stashedFibers.unshift(currentFiber); + pausedFibers.unshift(currentFiber); } currentFiber = currentFiber.return; } } -function rewindStack() { +function resumeTimers() { // Resumes all measurements that were active during the last deferred loop. - while (stashedFibers.length) { - const parent = stashedFibers.shift(); + while (pausedFibers.length) { + const parent = pausedFibers.shift(); beginMeasurement(parent, 'total'); } } -function resetStack() { +function resetTimers() { // If we started new work, this state is no longer valid. - stashedFibers.length = 0; - bailedFibers.clear(); + pausedFibers.length = 0; + skippedFibers.clear(); } -exports.markBeforeWork = function markBeforeWork(fiber) { +exports.startWorkTimer = function startWorkTimer(fiber) { if (!supportsUserTiming) { return; } @@ -143,7 +143,7 @@ exports.markBeforeWork = function markBeforeWork(fiber) { beginMeasurement(fiber, 'total'); }; -exports.markCurrentWorkAsBailed = function markCurrentWorkAsBailed(fiber) { +exports.cancelWorkTimer = function cancelWorkTimer(fiber) { if (!supportsUserTiming) { return; } @@ -152,11 +152,11 @@ exports.markCurrentWorkAsBailed = function markCurrentWorkAsBailed(fiber) { } // Remember we shouldn't complete measurement for this fiber. // Otherwise flamechart will be deep even for small updates. - bailedFibers.add(fiber); + skippedFibers.add(fiber); clearPendingMeasurement(fiber, 'total'); }; -exports.markAfterWork = function markAfterWork(fiber) { +exports.stopWorkTimer = function stopWorkTimer(fiber) { if (!supportsUserTiming) { return; } @@ -166,14 +166,14 @@ exports.markAfterWork = function markAfterWork(fiber) { if (shouldIgnore(fiber)) { return; } - if (bailedFibers.has(fiber)) { - bailedFibers.delete(fiber); + if (skippedFibers.has(fiber)) { + skippedFibers.delete(fiber); return; } completeMeasurement(fiber, 'total'); }; -exports.markBeforeUserCode = function markBeforeUserCode(fiber, phase) { +exports.startUserCodeTimer = function startUserCodeTimer(fiber, phase) { if (!supportsUserTiming) { return; } @@ -183,7 +183,7 @@ exports.markBeforeUserCode = function markBeforeUserCode(fiber, phase) { beginMeasurement(fiber, phase); }; -exports.markAfterUserCode = function markAfterUserCode() { +exports.stopUserCodeTimer = function stopUserCodeTimer() { if (!supportsUserTiming) { return; } @@ -192,7 +192,7 @@ exports.markAfterUserCode = function markAfterUserCode() { userCodeFiber = null; }; -exports.markBeforeWorkLoop = function markBeforeWorkLoop() { +exports.startWorkLoopTimer = function startWorkLoopTimer() { if (!supportsUserTiming) { return; } @@ -200,35 +200,35 @@ exports.markBeforeWorkLoop = function markBeforeWorkLoop() { // Any other measurements are performed within. performance.mark('react:reconcile'); // Resume any measurements that were in progress during the last loop. - rewindStack(); + resumeTimers(); }; -exports.markAfterWorkLoop = function markAfterWorkLoop() { +exports.stopWorkLoopTimer = function stopWorkLoopTimer() { if (!supportsUserTiming) { return; } // Pause any measurements until the next loop. - unwindStack(); - performance.measure('React: Reconcile Tree', 'react:reconcile'); + pauseTimers(); + performanceMeasureSafe('React: Reconcile Tree', 'react:reconcile'); }; -exports.markBeforeCommit = function markBeforeCommit() { +exports.startCommitTimer = function startCommitTimer() { if (!supportsUserTiming) { return; } performance.mark('react:commit'); }; -exports.markAfterCommit = function markAfterCommit() { +exports.stopCommitTimer = function stopCommitTimer() { if (!supportsUserTiming) { return; } - performance.measure('React: Commit Tree', 'react:commit'); + performanceMeasureSafe('React: Commit Tree', 'react:commit'); }; -exports.markWorkLoopAsRestarted = function markWorkLoopAsRestarted() { +exports.resetPausedWorkTimers = function resetPausedWorkTimers() { if (!supportsUserTiming) { return; } - resetStack(); + resetTimers(); }; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 487a7a3c0c83..f638d7534013 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -72,9 +72,9 @@ if (__DEV__) { // TODO const { - markCurrentWorkAsBailed, - markBeforeUserCode, - markAfterUserCode, + cancelWorkTimer, + startUserCodeTimer, + stopUserCodeTimer, } = require('ReactDebugFiberPerf'); module.exports = function( @@ -238,9 +238,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; ReactDebugCurrentFiber.phase = 'render'; - markBeforeUserCode(workInProgress, 'render'); + startUserCodeTimer(workInProgress, 'render'); nextChildren = fn(nextProps, context); - markAfterUserCode(); + stopUserCodeTimer(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = fn(nextProps, context); @@ -293,9 +293,9 @@ module.exports = function( let nextChildren; if (__DEV__) { ReactDebugCurrentFiber.phase = 'render'; - markBeforeUserCode(workInProgress, 'render'); + startUserCodeTimer(workInProgress, 'render'); nextChildren = instance.render(); - markAfterUserCode(); + stopUserCodeTimer(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = instance.render(); @@ -478,9 +478,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; - markBeforeUserCode(workInProgress, 'render'); + startUserCodeTimer(workInProgress, 'render'); value = fn(props, context); - markAfterUserCode(); + stopUserCodeTimer(); } else { value = fn(props, context); } @@ -663,7 +663,7 @@ module.exports = function( */ function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : Fiber | null { - markCurrentWorkAsBailed(workInProgress); + cancelWorkTimer(workInProgress); const priorityLevel = workInProgress.pendingWorkPriority; // TODO: We should ideally be able to bail out early if the children have no @@ -692,7 +692,7 @@ module.exports = function( } function bailoutOnLowPriority(current, workInProgress) { - markCurrentWorkAsBailed(workInProgress); + cancelWorkTimer(workInProgress); // TODO: Handle HostComponent tags here as well and call pushHostContext()? // See PR 8590 discussion for context diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 77ae0c54f0f0..885f0b0e7acc 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -39,8 +39,8 @@ var invariant = require('invariant'); // TODO const { - markBeforeUserCode, - markAfterUserCode, + startUserCodeTimer, + stopUserCodeTimer, } = require('ReactDebugFiberPerf'); const isArray = Array.isArray; @@ -109,11 +109,11 @@ module.exports = function( const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { if (__DEV__) { - markBeforeUserCode(workInProgress, 'shouldComponentUpdate'); + startUserCodeTimer(workInProgress, 'shouldComponentUpdate'); } const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } if (__DEV__) { @@ -308,11 +308,11 @@ module.exports = function( if (typeof instance.componentWillMount === 'function') { if (__DEV__) { - markBeforeUserCode(workInProgress, 'componentWillMount'); + startUserCodeTimer(workInProgress, 'componentWillMount'); } instance.componentWillMount(); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } // If we had additional state updates during this life-cycle, let's // process them now. @@ -381,11 +381,11 @@ module.exports = function( if (typeof newInstance.componentWillMount === 'function') { if (__DEV__) { - markBeforeUserCode(workInProgress, 'componentWillMount'); + startUserCodeTimer(workInProgress, 'componentWillMount'); } newInstance.componentWillMount(); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } } // If we had additional state updates, process them now. @@ -433,11 +433,11 @@ module.exports = function( if (oldProps !== newProps || oldContext !== newContext) { if (typeof instance.componentWillReceiveProps === 'function') { if (__DEV__) { - markBeforeUserCode(workInProgress, 'componentWillReceiveProps'); + startUserCodeTimer(workInProgress, 'componentWillReceiveProps'); } instance.componentWillReceiveProps(newProps, newContext); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } if (instance.state !== workInProgress.memoizedState) { @@ -494,11 +494,11 @@ module.exports = function( markUpdate(workInProgress); if (typeof instance.componentWillUpdate === 'function') { if (__DEV__) { - markBeforeUserCode(workInProgress, 'componentWillUpdate'); + startUserCodeTimer(workInProgress, 'componentWillUpdate'); } instance.componentWillUpdate(newProps, newState, newContext); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } } } else { diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index b7dfddef9f9d..e590ad1f9f82 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -37,8 +37,8 @@ var { // TODO const { - markBeforeUserCode, - markAfterUserCode, + startUserCodeTimer, + stopUserCodeTimer, } = require('ReactDebugFiberPerf'); @@ -64,9 +64,9 @@ module.exports = function( function safelyCallComponentWillUnmount(current, instance) { if (__DEV__) { const unmountError = invokeGuardedCallback(null, () => { - markBeforeUserCode(current, 'componentWillUnmount'); + startUserCodeTimer(current, 'componentWillUnmount'); instance.componentWillUnmount(); - markAfterUserCode(); + stopUserCodeTimer(); }); if (unmountError) { captureError(current, unmountError); @@ -448,11 +448,11 @@ module.exports = function( if (current === null) { if (typeof instance.componentDidMount === 'function') { if (__DEV__) { - markBeforeUserCode(finishedWork, 'componentDidMount'); + startUserCodeTimer(finishedWork, 'componentDidMount'); } instance.componentDidMount(); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } } } else { @@ -460,11 +460,11 @@ module.exports = function( const prevProps = current.memoizedProps; const prevState = current.memoizedState; if (__DEV__) { - markBeforeUserCode(finishedWork, 'componentDidUpdate') + startUserCodeTimer(finishedWork, 'componentDidUpdate'); } instance.componentDidUpdate(prevProps, prevState); if (__DEV__) { - markAfterUserCode(); + stopUserCodeTimer(); } } } diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 53b1e3ba4f6e..74a7e7fc6164 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -87,13 +87,13 @@ var { // TODO: gate by DEV? var { - markBeforeWork, - markAfterWork, - markBeforeCommit, - markAfterCommit, - markBeforeWorkLoop, - markAfterWorkLoop, - markWorkLoopAsRestarted, + startWorkTimer, + stopWorkTimer, + startCommitTimer, + stopCommitTimer, + startWorkLoopTimer, + stopWorkLoopTimer, + resetPausedWorkTimers, } = require('ReactDebugFiberPerf'); var invariant = require('invariant'); @@ -236,7 +236,7 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Thu, 2 Mar 2017 23:15:18 +0000 Subject: [PATCH 11/37] Moar emojis --- .../shared/fiber/ReactDebugFiberPerf.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index ff3eb33d635c..a62e273654b3 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -20,6 +20,11 @@ const { } = require('ReactTypeOfWork'); const getComponentName = require('getComponentName'); +// Prefix measurements so that it's possible to filter them. +// Longer prefixes are hard to read in DevTools. +const reactEmoji = '\u269B'; +const reconcileLabel = `${reactEmoji} (React Tree Reconciliation)`; +const commitLabel = `${reactEmoji} (Committing Changes)`; const supportsUserTiming = typeof performance !== 'undefined' && typeof performance.mark === 'function' && @@ -58,7 +63,7 @@ function performanceMeasureSafe(label, markName) { } function getMarkName(fiber, phase) { - return `react:${fiber._debugID}:${phase}`; + return `${reactEmoji} ${fiber._debugID}:${phase}`; } function beginMeasurement(fiber, phase) { @@ -75,8 +80,8 @@ function completeMeasurement(fiber, phase) { const markName = getMarkName(fiber, phase); const componentName = getComponentName(fiber) || 'Unknown'; const label = phase === 'total' ? - `<${componentName}>` : - `${componentName}.${phase}`; + `${reactEmoji} ${componentName}` : + `${reactEmoji} ${componentName}.${phase}`; performanceMeasureSafe(label, markName); } @@ -198,7 +203,7 @@ exports.startWorkLoopTimer = function startWorkLoopTimer() { } // This is top level call. // Any other measurements are performed within. - performance.mark('react:reconcile'); + performance.mark(reconcileLabel); // Resume any measurements that were in progress during the last loop. resumeTimers(); }; @@ -209,21 +214,21 @@ exports.stopWorkLoopTimer = function stopWorkLoopTimer() { } // Pause any measurements until the next loop. pauseTimers(); - performanceMeasureSafe('React: Reconcile Tree', 'react:reconcile'); + performanceMeasureSafe(reconcileLabel, reconcileLabel); }; exports.startCommitTimer = function startCommitTimer() { if (!supportsUserTiming) { return; } - performance.mark('react:commit'); + performance.mark(commitLabel); }; exports.stopCommitTimer = function stopCommitTimer() { if (!supportsUserTiming) { return; } - performanceMeasureSafe('React: Commit Tree', 'react:commit'); + performanceMeasureSafe(commitLabel, commitLabel); }; exports.resetPausedWorkTimers = function resetPausedWorkTimers() { From 5e267e101fcf9ab4fb1fd6ecbaccab2b9866a88c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 2 Mar 2017 23:58:23 +0000 Subject: [PATCH 12/37] Remove stacks in favor of a flag --- .../shared/fiber/ReactDebugFiberPerf.js | 52 ++++++++----------- src/renderers/shared/fiber/ReactFiber.js | 2 + .../shared/fiber/ReactFiberScheduler.js | 3 -- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index a62e273654b3..e5aefd416cdf 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -32,15 +32,8 @@ const supportsUserTiming = typeof performance.measure === 'function' && typeof performance.clearMeasures === 'function'; -// Keep track of fibers that bailed out because we clear their marks and -// don't measure them. This prevents giant flamecharts where little changed. -let skippedFibers = new Set(); -// When we exit a deferred loop, we might not have finished the work. However -// the next unit of work pointer is still in the middle of the tree. We keep -// track of the parent path when exiting the loop so that we can unwind the -// flamechart measurements, and later rewind them when we resume work. -let pausedFibers = []; // Keep track of current fiber so that we know the path to unwind on pause. +// TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them? let currentFiber = null; // If we're in the middle of user code, which fiber and method is it? // Reusing `currentFiber` would be confusing for this because user code fiber @@ -112,27 +105,29 @@ function clearPendingUserCodeMeasurement() { function pauseTimers() { // Stops all currently active measurements so that they can be resumed // if we continue in a later deferred loop from the same unit of work. - while (currentFiber) { - if (!shouldIgnore(currentFiber) && !skippedFibers.has(currentFiber)) { - completeMeasurement(currentFiber, 'total'); - pausedFibers.unshift(currentFiber); + let fiber = currentFiber; + while (fiber) { + if (fiber._debugIsCurrentlyTiming) { + completeMeasurement(fiber, 'total'); } - currentFiber = currentFiber.return; + fiber = fiber.return; } } -function resumeTimers() { - // Resumes all measurements that were active during the last deferred loop. - while (pausedFibers.length) { - const parent = pausedFibers.shift(); - beginMeasurement(parent, 'total'); +function resumeTimersRecursively(fiber) { + if (fiber.return !== null) { + resumeTimersRecursively(fiber.return); + } + if (fiber._debugIsCurrentlyTiming) { + beginMeasurement(fiber, 'total'); } } -function resetTimers() { - // If we started new work, this state is no longer valid. - pausedFibers.length = 0; - skippedFibers.clear(); +function resumeTimers() { + // Resumes all measurements that were active during the last deferred loop. + if (currentFiber !== null) { + resumeTimersRecursively(currentFiber); + } } exports.startWorkTimer = function startWorkTimer(fiber) { @@ -145,6 +140,7 @@ exports.startWorkTimer = function startWorkTimer(fiber) { if (shouldIgnore(fiber)) { return; } + fiber._debugIsCurrentlyTiming = true; beginMeasurement(fiber, 'total'); }; @@ -157,7 +153,7 @@ exports.cancelWorkTimer = function cancelWorkTimer(fiber) { } // Remember we shouldn't complete measurement for this fiber. // Otherwise flamechart will be deep even for small updates. - skippedFibers.add(fiber); + fiber._debugIsCurrentlyTiming = false; clearPendingMeasurement(fiber, 'total'); }; @@ -171,10 +167,10 @@ exports.stopWorkTimer = function stopWorkTimer(fiber) { if (shouldIgnore(fiber)) { return; } - if (skippedFibers.has(fiber)) { - skippedFibers.delete(fiber); + if (!fiber._debugIsCurrentlyTiming) { return; } + fiber._debugIsCurrentlyTiming = false; completeMeasurement(fiber, 'total'); }; @@ -231,9 +227,3 @@ exports.stopCommitTimer = function stopCommitTimer() { performanceMeasureSafe(commitLabel, commitLabel); }; -exports.resetPausedWorkTimers = function resetPausedWorkTimers() { - if (!supportsUserTiming) { - return; - } - resetTimers(); -}; diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index 6432ec33b44f..815016ef30b8 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -60,6 +60,7 @@ export type Fiber = { _debugID ?: DebugID, _debugSource ?: Source | null, _debugOwner ?: Fiber | ReactInstance | null, // Stack compatible + _debugIsCurrentlyTiming ?: boolean, // These first fields are conceptually members of an Instance. This used to // be split into a separate type and intersected with the other Fiber fields, @@ -223,6 +224,7 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber { fiber._debugID = debugCounter++; fiber._debugSource = null; fiber._debugOwner = null; + fiber._debugIsCurrentlyTiming = false; if (typeof Object.preventExtensions === 'function') { Object.preventExtensions(fiber); } diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 74a7e7fc6164..fb0ae7a0aca9 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -93,7 +93,6 @@ var { stopCommitTimer, startWorkLoopTimer, stopWorkLoopTimer, - resetPausedWorkTimers, } = require('ReactDebugFiberPerf'); var invariant = require('invariant'); @@ -235,8 +234,6 @@ module.exports = function(config : HostConfig Date: Fri, 3 Mar 2017 00:31:53 +0000 Subject: [PATCH 13/37] Fix Flow --- .../shared/fiber/ReactDebugFiberPerf.js | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index e5aefd416cdf..c0957dd24837 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -20,6 +20,8 @@ const { } = require('ReactTypeOfWork'); const getComponentName = require('getComponentName'); +import type { Fiber } from 'ReactFiber'; + // Prefix measurements so that it's possible to filter them. // Longer prefixes are hard to read in DevTools. const reactEmoji = '\u269B'; @@ -32,17 +34,30 @@ const supportsUserTiming = typeof performance.measure === 'function' && typeof performance.clearMeasures === 'function'; +// TODO: are we running all those? +type UserCodePhase = + 'constructor' | + 'render' | + 'componentWillMount' | + 'componentWillUnmount' | + 'componentWillReceiveProps' | + 'shouldComponentUpdate' | + 'componentWillUpdate' | + 'componentDidUpdate' | + 'componentDidMount'; +type MeasurementPhase = UserCodePhase | 'total'; + // Keep track of current fiber so that we know the path to unwind on pause. // TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them? -let currentFiber = null; +let currentFiber : Fiber | null = null; // If we're in the middle of user code, which fiber and method is it? // Reusing `currentFiber` would be confusing for this because user code fiber // can change during commit phase too, but we don't need to unwind it (since // lifecycles in the commit phase don't resemble a tree). -let userCodePhase = null; -let userCodeFiber = null; +let userCodePhase : UserCodePhase | null = null; +let userCodeFiber : Fiber | null = null; -function performanceMeasureSafe(label, markName) { +function performanceMeasureSafe(label : string, markName : string) { try { performance.measure(label, markName); } catch (err) { @@ -55,21 +70,22 @@ function performanceMeasureSafe(label, markName) { performance.clearMeasures(label); } -function getMarkName(fiber, phase) { - return `${reactEmoji} ${fiber._debugID}:${phase}`; +function getMarkName(fiber : Fiber, phase : MeasurementPhase) { + const debugID = ((fiber._debugID : any) : number); + return `${reactEmoji} ${debugID}:${phase}`; } -function beginMeasurement(fiber, phase) { +function beginMeasurement(fiber : Fiber, phase : MeasurementPhase) { const markName = getMarkName(fiber, phase); performance.mark(markName); } -function clearPendingMeasurement(fiber, phase) { +function clearPendingMeasurement(fiber : Fiber, phase : MeasurementPhase) { const markName = getMarkName(fiber, phase); performance.clearMarks(markName); } -function completeMeasurement(fiber, phase) { +function completeMeasurement(fiber : Fiber, phase : MeasurementPhase) { const markName = getMarkName(fiber, phase); const componentName = getComponentName(fiber) || 'Unknown'; const label = phase === 'total' ? @@ -78,7 +94,7 @@ function completeMeasurement(fiber, phase) { performanceMeasureSafe(label, markName); } -function shouldIgnore(fiber) { +function shouldIgnore(fiber : Fiber) : boolean { // Host components should be skipped in the timeline. // We could check typeof fiber.type, but does this work with RN? switch (fiber.tag) { @@ -114,7 +130,7 @@ function pauseTimers() { } } -function resumeTimersRecursively(fiber) { +function resumeTimersRecursively(fiber : Fiber) { if (fiber.return !== null) { resumeTimersRecursively(fiber.return); } @@ -130,7 +146,7 @@ function resumeTimers() { } } -exports.startWorkTimer = function startWorkTimer(fiber) { +exports.startWorkTimer = function startWorkTimer(fiber : Fiber) : void { if (!supportsUserTiming) { return; } @@ -144,7 +160,7 @@ exports.startWorkTimer = function startWorkTimer(fiber) { beginMeasurement(fiber, 'total'); }; -exports.cancelWorkTimer = function cancelWorkTimer(fiber) { +exports.cancelWorkTimer = function cancelWorkTimer(fiber : Fiber) : void { if (!supportsUserTiming) { return; } @@ -157,7 +173,7 @@ exports.cancelWorkTimer = function cancelWorkTimer(fiber) { clearPendingMeasurement(fiber, 'total'); }; -exports.stopWorkTimer = function stopWorkTimer(fiber) { +exports.stopWorkTimer = function stopWorkTimer(fiber : Fiber) : void { if (!supportsUserTiming) { return; } @@ -174,7 +190,10 @@ exports.stopWorkTimer = function stopWorkTimer(fiber) { completeMeasurement(fiber, 'total'); }; -exports.startUserCodeTimer = function startUserCodeTimer(fiber, phase) { +exports.startUserCodeTimer = function startUserCodeTimer( + fiber : Fiber, + phase : UserCodePhase, +) : void { if (!supportsUserTiming) { return; } @@ -184,16 +203,18 @@ exports.startUserCodeTimer = function startUserCodeTimer(fiber, phase) { beginMeasurement(fiber, phase); }; -exports.stopUserCodeTimer = function stopUserCodeTimer() { +exports.stopUserCodeTimer = function stopUserCodeTimer() : void { if (!supportsUserTiming) { return; } - completeMeasurement(userCodeFiber, userCodePhase); + if (userCodePhase !== null && userCodeFiber !== null) { + completeMeasurement(userCodeFiber, userCodePhase); + } userCodePhase = null; userCodeFiber = null; }; -exports.startWorkLoopTimer = function startWorkLoopTimer() { +exports.startWorkLoopTimer = function startWorkLoopTimer() : void { if (!supportsUserTiming) { return; } @@ -204,7 +225,7 @@ exports.startWorkLoopTimer = function startWorkLoopTimer() { resumeTimers(); }; -exports.stopWorkLoopTimer = function stopWorkLoopTimer() { +exports.stopWorkLoopTimer = function stopWorkLoopTimer() : void { if (!supportsUserTiming) { return; } @@ -213,14 +234,14 @@ exports.stopWorkLoopTimer = function stopWorkLoopTimer() { performanceMeasureSafe(reconcileLabel, reconcileLabel); }; -exports.startCommitTimer = function startCommitTimer() { +exports.startCommitTimer = function startCommitTimer() : void { if (!supportsUserTiming) { return; } performance.mark(commitLabel); }; -exports.stopCommitTimer = function stopCommitTimer() { +exports.stopCommitTimer = function stopCommitTimer() : void { if (!supportsUserTiming) { return; } From cc141e4295b28e1affc084c47de72eb5e91e951d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 01:47:50 +0000 Subject: [PATCH 14/37] Gate behind __DEV__ --- .../shared/fiber/ReactDebugFiberPerf.js | 432 +++++++++--------- .../shared/fiber/ReactFiberBeginWork.js | 21 +- .../shared/fiber/ReactFiberClassComponent.js | 10 +- .../shared/fiber/ReactFiberCommitWork.js | 13 +- .../shared/fiber/ReactFiberScheduler.js | 50 +- 5 files changed, 276 insertions(+), 250 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index c0957dd24837..5e7ef0d2b684 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -10,31 +10,9 @@ * @flow */ -const { - HostRoot, - HostComponent, - HostText, - HostPortal, - YieldComponent, - Fragment, -} = require('ReactTypeOfWork'); -const getComponentName = require('getComponentName'); - import type { Fiber } from 'ReactFiber'; -// Prefix measurements so that it's possible to filter them. -// Longer prefixes are hard to read in DevTools. -const reactEmoji = '\u269B'; -const reconcileLabel = `${reactEmoji} (React Tree Reconciliation)`; -const commitLabel = `${reactEmoji} (Committing Changes)`; -const supportsUserTiming = - typeof performance !== 'undefined' && - typeof performance.mark === 'function' && - typeof performance.clearMarks === 'function' && - typeof performance.measure === 'function' && - typeof performance.clearMeasures === 'function'; - -// TODO: are we running all those? +// TODO: are we running all of these? type UserCodePhase = 'constructor' | 'render' | @@ -45,206 +23,238 @@ type UserCodePhase = 'componentWillUpdate' | 'componentDidUpdate' | 'componentDidMount'; + type MeasurementPhase = UserCodePhase | 'total'; -// Keep track of current fiber so that we know the path to unwind on pause. -// TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them? -let currentFiber : Fiber | null = null; -// If we're in the middle of user code, which fiber and method is it? -// Reusing `currentFiber` would be confusing for this because user code fiber -// can change during commit phase too, but we don't need to unwind it (since -// lifecycles in the commit phase don't resemble a tree). -let userCodePhase : UserCodePhase | null = null; -let userCodeFiber : Fiber | null = null; - -function performanceMeasureSafe(label : string, markName : string) { - try { - performance.measure(label, markName); - } catch (err) { - // If previous mark was missing for some reason, this will throw. - // This could only happen if React crashed in an unexpected place earlier. - // Don't pile on with more errors. - } - // Clear marks immediately to avoid growing buffer. - performance.clearMarks(markName); - performance.clearMeasures(label); -} +// Trust the developer to only use this with a __DEV__ check +let ReactDebugFiberPerf = ((null: any): typeof ReactDebugFiberPerf); -function getMarkName(fiber : Fiber, phase : MeasurementPhase) { - const debugID = ((fiber._debugID : any) : number); - return `${reactEmoji} ${debugID}:${phase}`; -} +if (__DEV__) { + const { + HostRoot, + HostComponent, + HostText, + HostPortal, + YieldComponent, + Fragment, + } = require('ReactTypeOfWork'); -function beginMeasurement(fiber : Fiber, phase : MeasurementPhase) { - const markName = getMarkName(fiber, phase); - performance.mark(markName); -} + const getComponentName = require('getComponentName'); -function clearPendingMeasurement(fiber : Fiber, phase : MeasurementPhase) { - const markName = getMarkName(fiber, phase); - performance.clearMarks(markName); -} + // Prefix measurements so that it's possible to filter them. + // Longer prefixes are hard to read in DevTools. + const reactEmoji = '\u269B'; + const reconcileLabel = `${reactEmoji} (React Tree Reconciliation)`; + const commitLabel = `${reactEmoji} (Committing Changes)`; + const supportsUserTiming = + typeof performance !== 'undefined' && + typeof performance.mark === 'function' && + typeof performance.clearMarks === 'function' && + typeof performance.measure === 'function' && + typeof performance.clearMeasures === 'function'; -function completeMeasurement(fiber : Fiber, phase : MeasurementPhase) { - const markName = getMarkName(fiber, phase); - const componentName = getComponentName(fiber) || 'Unknown'; - const label = phase === 'total' ? - `${reactEmoji} ${componentName}` : - `${reactEmoji} ${componentName}.${phase}`; - performanceMeasureSafe(label, markName); -} + // Keep track of current fiber so that we know the path to unwind on pause. + // TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them? + let currentFiber : Fiber | null = null; + // If we're in the middle of user code, which fiber and method is it? + // Reusing `currentFiber` would be confusing for this because user code fiber + // can change during commit phase too, but we don't need to unwind it (since + // lifecycles in the commit phase don't resemble a tree). + let userCodePhase : UserCodePhase | null = null; + let userCodeFiber : Fiber | null = null; -function shouldIgnore(fiber : Fiber) : boolean { - // Host components should be skipped in the timeline. - // We could check typeof fiber.type, but does this work with RN? - switch (fiber.tag) { - case HostRoot: - case HostComponent: - case HostText: - case HostPortal: - case YieldComponent: - case Fragment: - return true; - default: - return false; - } -} + const performanceMeasureSafe = (label : string, markName : string) => { + try { + performance.measure(label, markName); + } catch (err) { + // If previous mark was missing for some reason, this will throw. + // This could only happen if React crashed in an unexpected place earlier. + // Don't pile on with more errors. + } + // Clear marks immediately to avoid growing buffer. + performance.clearMarks(markName); + performance.clearMeasures(label); + }; -function clearPendingUserCodeMeasurement() { - if (userCodePhase !== null && userCodeFiber !== null) { - clearPendingMeasurement(userCodeFiber, userCodePhase); - } - userCodeFiber = null; - userCodePhase = null; -} + const getMarkName = (fiber : Fiber, phase : MeasurementPhase) => { + const debugID = ((fiber._debugID : any) : number); + return `${reactEmoji} ${debugID}:${phase}`; + }; + + const beginMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const markName = getMarkName(fiber, phase); + performance.mark(markName); + }; + + const clearPendingMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const markName = getMarkName(fiber, phase); + performance.clearMarks(markName); + }; + + const completeMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const markName = getMarkName(fiber, phase); + const componentName = getComponentName(fiber) || 'Unknown'; + const label = phase === 'total' ? + `${reactEmoji} ${componentName}` : + `${reactEmoji} ${componentName}.${phase}`; + performanceMeasureSafe(label, markName); + }; + + const shouldIgnore = (fiber : Fiber) : boolean => { + // Host components should be skipped in the timeline. + // We could check typeof fiber.type, but does this work with RN? + switch (fiber.tag) { + case HostRoot: + case HostComponent: + case HostText: + case HostPortal: + case YieldComponent: + case Fragment: + return true; + default: + return false; + } + }; + + const clearPendingUserCodeMeasurement = () => { + if (userCodePhase !== null && userCodeFiber !== null) { + clearPendingMeasurement(userCodeFiber, userCodePhase); + } + userCodeFiber = null; + userCodePhase = null; + }; -function pauseTimers() { - // Stops all currently active measurements so that they can be resumed - // if we continue in a later deferred loop from the same unit of work. - let fiber = currentFiber; - while (fiber) { + const pauseTimers = () => { + // Stops all currently active measurements so that they can be resumed + // if we continue in a later deferred loop from the same unit of work. + let fiber = currentFiber; + while (fiber) { + if (fiber._debugIsCurrentlyTiming) { + completeMeasurement(fiber, 'total'); + } + fiber = fiber.return; + } + }; + + const resumeTimersRecursively = (fiber : Fiber) => { + if (fiber.return !== null) { + resumeTimersRecursively(fiber.return); + } if (fiber._debugIsCurrentlyTiming) { - completeMeasurement(fiber, 'total'); + beginMeasurement(fiber, 'total'); } - fiber = fiber.return; - } -} + }; -function resumeTimersRecursively(fiber : Fiber) { - if (fiber.return !== null) { - resumeTimersRecursively(fiber.return); - } - if (fiber._debugIsCurrentlyTiming) { - beginMeasurement(fiber, 'total'); - } -} + const resumeTimers = () => { + // Resumes all measurements that were active during the last deferred loop. + if (currentFiber !== null) { + resumeTimersRecursively(currentFiber); + } + }; -function resumeTimers() { - // Resumes all measurements that were active during the last deferred loop. - if (currentFiber !== null) { - resumeTimersRecursively(currentFiber); - } -} + ReactDebugFiberPerf = { + startWorkTimer(fiber : Fiber) : void { + if (!supportsUserTiming) { + return; + } + clearPendingUserCodeMeasurement(); + // If we pause, this is the fiber to unwind from. + currentFiber = fiber; + if (shouldIgnore(fiber)) { + return; + } + fiber._debugIsCurrentlyTiming = true; + beginMeasurement(fiber, 'total'); + }, + + cancelWorkTimer(fiber : Fiber) : void { + if (!supportsUserTiming) { + return; + } + if (shouldIgnore(fiber)) { + return; + } + // Remember we shouldn't complete measurement for this fiber. + // Otherwise flamechart will be deep even for small updates. + fiber._debugIsCurrentlyTiming = false; + clearPendingMeasurement(fiber, 'total'); + }, -exports.startWorkTimer = function startWorkTimer(fiber : Fiber) : void { - if (!supportsUserTiming) { - return; - } - clearPendingUserCodeMeasurement(); - // If we pause, this is the fiber to unwind from. - currentFiber = fiber; - if (shouldIgnore(fiber)) { - return; - } - fiber._debugIsCurrentlyTiming = true; - beginMeasurement(fiber, 'total'); -}; - -exports.cancelWorkTimer = function cancelWorkTimer(fiber : Fiber) : void { - if (!supportsUserTiming) { - return; - } - if (shouldIgnore(fiber)) { - return; - } - // Remember we shouldn't complete measurement for this fiber. - // Otherwise flamechart will be deep even for small updates. - fiber._debugIsCurrentlyTiming = false; - clearPendingMeasurement(fiber, 'total'); -}; - -exports.stopWorkTimer = function stopWorkTimer(fiber : Fiber) : void { - if (!supportsUserTiming) { - return; - } - clearPendingUserCodeMeasurement(); - // If we pause, its parent is the fiber to unwind from. - currentFiber = fiber.return; - if (shouldIgnore(fiber)) { - return; - } - if (!fiber._debugIsCurrentlyTiming) { - return; - } - fiber._debugIsCurrentlyTiming = false; - completeMeasurement(fiber, 'total'); -}; - -exports.startUserCodeTimer = function startUserCodeTimer( - fiber : Fiber, - phase : UserCodePhase, -) : void { - if (!supportsUserTiming) { - return; - } - clearPendingUserCodeMeasurement(); - userCodeFiber = fiber; - userCodePhase = phase; - beginMeasurement(fiber, phase); -}; - -exports.stopUserCodeTimer = function stopUserCodeTimer() : void { - if (!supportsUserTiming) { - return; - } - if (userCodePhase !== null && userCodeFiber !== null) { - completeMeasurement(userCodeFiber, userCodePhase); - } - userCodePhase = null; - userCodeFiber = null; -}; - -exports.startWorkLoopTimer = function startWorkLoopTimer() : void { - if (!supportsUserTiming) { - return; - } - // This is top level call. - // Any other measurements are performed within. - performance.mark(reconcileLabel); - // Resume any measurements that were in progress during the last loop. - resumeTimers(); -}; - -exports.stopWorkLoopTimer = function stopWorkLoopTimer() : void { - if (!supportsUserTiming) { - return; - } - // Pause any measurements until the next loop. - pauseTimers(); - performanceMeasureSafe(reconcileLabel, reconcileLabel); -}; - -exports.startCommitTimer = function startCommitTimer() : void { - if (!supportsUserTiming) { - return; - } - performance.mark(commitLabel); -}; - -exports.stopCommitTimer = function stopCommitTimer() : void { - if (!supportsUserTiming) { - return; - } - performanceMeasureSafe(commitLabel, commitLabel); -}; + stopWorkTimer(fiber : Fiber) : void { + if (!supportsUserTiming) { + return; + } + clearPendingUserCodeMeasurement(); + // If we pause, its parent is the fiber to unwind from. + currentFiber = fiber.return; + if (shouldIgnore(fiber)) { + return; + } + if (!fiber._debugIsCurrentlyTiming) { + return; + } + fiber._debugIsCurrentlyTiming = false; + completeMeasurement(fiber, 'total'); + }, + + startUserCodeTimer( + fiber : Fiber, + phase : UserCodePhase, + ) : void { + if (!supportsUserTiming) { + return; + } + clearPendingUserCodeMeasurement(); + userCodeFiber = fiber; + userCodePhase = phase; + beginMeasurement(fiber, phase); + }, + + stopUserCodeTimer() : void { + if (!supportsUserTiming) { + return; + } + if (userCodePhase !== null && userCodeFiber !== null) { + completeMeasurement(userCodeFiber, userCodePhase); + } + userCodePhase = null; + userCodeFiber = null; + }, + + startWorkLoopTimer() : void { + if (!supportsUserTiming) { + return; + } + // This is top level call. + // Any other measurements are performed within. + performance.mark(reconcileLabel); + // Resume any measurements that were in progress during the last loop. + resumeTimers(); + }, + + stopWorkLoopTimer() : void { + if (!supportsUserTiming) { + return; + } + // Pause any measurements until the next loop. + pauseTimers(); + performanceMeasureSafe(reconcileLabel, reconcileLabel); + }, + + startCommitTimer() : void { + if (!supportsUserTiming) { + return; + } + performance.mark(commitLabel); + }, + + stopCommitTimer() : void { + if (!supportsUserTiming) { + return; + } + performanceMeasureSafe(commitLabel, commitLabel); + }, + }; +} +module.exports = ReactDebugFiberPerf; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 1a1823fd4d63..0214fc670a43 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -66,17 +66,16 @@ var invariant = require('fbjs/lib/invariant'); if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var { + cancelWorkTimer, + startUserCodeTimer, + stopUserCodeTimer, + } = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); + var warnedAboutStatelessRefs = {}; } -// TODO -const { - cancelWorkTimer, - startUserCodeTimer, - stopUserCodeTimer, -} = require('ReactDebugFiberPerf'); - module.exports = function( config : HostConfig, hostContext : HostContext, @@ -665,7 +664,9 @@ module.exports = function( */ function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : Fiber | null { - cancelWorkTimer(workInProgress); + if (__DEV__) { + cancelWorkTimer(workInProgress); + } const priorityLevel = workInProgress.pendingWorkPriority; // TODO: We should ideally be able to bail out early if the children have no @@ -694,7 +695,9 @@ module.exports = function( } function bailoutOnLowPriority(current, workInProgress) { - cancelWorkTimer(workInProgress); + if (__DEV__) { + cancelWorkTimer(workInProgress); + } // TODO: Handle HostComponent tags here as well and call pushHostContext()? // See PR 8590 discussion for context diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index a437c943f65b..c7ea17f8ac1b 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -37,15 +37,13 @@ var emptyObject = require('fbjs/lib/emptyObject'); var shallowEqual = require('fbjs/lib/shallowEqual'); var invariant = require('fbjs/lib/invariant'); -// TODO -const { - startUserCodeTimer, - stopUserCodeTimer, -} = require('ReactDebugFiberPerf'); - const isArray = Array.isArray; if (__DEV__) { + var { + startUserCodeTimer, + stopUserCodeTimer, + } = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); var warnOnInvalidCallback = function(callback : mixed, callerName : string) { warning( diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 543849167c5d..027ba2d3c118 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -35,14 +35,15 @@ var { ContentReset, } = require('ReactTypeOfSideEffect'); -// TODO -const { - startUserCodeTimer, - stopUserCodeTimer, -} = require('ReactDebugFiberPerf'); - var invariant = require('fbjs/lib/invariant'); +if (__DEV__) { + var { + startUserCodeTimer, + stopUserCodeTimer, + } = require('ReactDebugFiberPerf'); +} + module.exports = function( config : HostConfig, captureError : (failedFiber : Fiber, error: Error) => Fiber | null diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 966b01db3e80..3c8004e5eab4 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -89,22 +89,20 @@ var { resetContext, } = require('ReactFiberContext'); -// TODO: gate by DEV? -var { - startWorkTimer, - stopWorkTimer, - startCommitTimer, - stopCommitTimer, - startWorkLoopTimer, - stopWorkLoopTimer, -} = require('ReactDebugFiberPerf'); - var invariant = require('fbjs/lib/invariant'); if (__DEV__) { var warning = require('fbjs/lib/warning'); var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var { + startWorkTimer, + stopWorkTimer, + startWorkLoopTimer, + stopWorkLoopTimer, + startCommitTimer, + stopCommitTimer, + } = require('ReactDebugFiberPerf'); var warnAboutUpdateOnUnmounted = function(instance : ReactClass) { const ctor = instance.constructor; @@ -391,7 +389,9 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Fri, 3 Mar 2017 01:51:52 +0000 Subject: [PATCH 15/37] Revert flag for testing --- src/renderers/dom/fiber/ReactDOMFiber.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index 8958fab52f70..cd0988f51207 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -330,7 +330,7 @@ var DOMRenderer = ReactFiberReconciler({ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC, - useSyncScheduling: false, + useSyncScheduling: true, }); From 094b9f9ea8dcb8796c6d85f883f7e683e7bf8930 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 02:19:15 +0000 Subject: [PATCH 16/37] Measure all lifecycles --- src/renderers/shared/fiber/ReactDebugFiberPerf.js | 4 ++-- src/renderers/shared/fiber/ReactFiberClassComponent.js | 6 ++++++ src/renderers/shared/fiber/ReactFiberContext.js | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 5e7ef0d2b684..2297f61b0f2c 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -12,7 +12,6 @@ import type { Fiber } from 'ReactFiber'; -// TODO: are we running all of these? type UserCodePhase = 'constructor' | 'render' | @@ -22,7 +21,8 @@ type UserCodePhase = 'shouldComponentUpdate' | 'componentWillUpdate' | 'componentDidUpdate' | - 'componentDidMount'; + 'componentDidMount' | + 'getChildContext'; type MeasurementPhase = UserCodePhase | 'total'; diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index c7ea17f8ac1b..bcd0df3b5fcf 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -271,7 +271,13 @@ module.exports = function( const unmaskedContext = getUnmaskedContext(workInProgress); const needsContext = isContextConsumer(workInProgress); const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject; + if (__DEV__) { + startUserCodeTimer(workInProgress, 'constructor'); + } const instance = new ctor(props, context); + if (__DEV__) { + stopUserCodeTimer(); + } adoptClassInstance(workInProgress, instance); checkClassInstance(workInProgress); diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index d921285a6b35..dd740ad17a59 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -36,6 +36,10 @@ if (__DEV__) { var checkReactTypeSpec = require('checkReactTypeSpec'); var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var { + startUserCodeTimer, + stopUserCodeTimer, + } = require('ReactDebugFiberPerf'); var warnedAboutMissingGetChildContext = {}; } @@ -172,7 +176,9 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin let childContext; if (__DEV__) { ReactDebugCurrentFiber.phase = 'getChildContext'; + startUserCodeTimer(fiber, 'getChildContext'); childContext = instance.getChildContext(); + stopUserCodeTimer(); ReactDebugCurrentFiber.phase = null; } else { childContext = instance.getChildContext(); From 8abea2f7f236be6389024b08c662636a207d2deb Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 04:01:34 +0000 Subject: [PATCH 17/37] Push it to the limits --- .../shared/fiber/ReactDebugFiberPerf.js | 79 +++++++++++-------- .../shared/fiber/ReactFiberBeginWork.js | 16 ++-- .../shared/fiber/ReactFiberClassComponent.js | 28 +++---- .../shared/fiber/ReactFiberCommitWork.js | 46 ++++++++--- .../shared/fiber/ReactFiberCompleteWork.js | 19 ++++- .../shared/fiber/ReactFiberContext.js | 8 +- .../shared/fiber/ReactFiberScheduler.js | 33 ++++++++ 7 files changed, 158 insertions(+), 71 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 2297f61b0f2c..2ca093993819 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -12,7 +12,7 @@ import type { Fiber } from 'ReactFiber'; -type UserCodePhase = +type MeasurementPhase = 'constructor' | 'render' | 'componentWillMount' | @@ -22,9 +22,16 @@ type UserCodePhase = 'componentWillUpdate' | 'componentDidUpdate' | 'componentDidMount' | - 'getChildContext'; - -type MeasurementPhase = UserCodePhase | 'total'; + 'getChildContext' | + '[attach ref]' | + '[call callbacks]' | + '[clear]' | + '[create]' | + '[compute diff]' | + '[detach ref]' | + '[mount]' | + '[update]' | + '[unmount]'; // Trust the developer to only use this with a __DEV__ check let ReactDebugFiberPerf = ((null: any): typeof ReactDebugFiberPerf); @@ -60,8 +67,8 @@ if (__DEV__) { // Reusing `currentFiber` would be confusing for this because user code fiber // can change during commit phase too, but we don't need to unwind it (since // lifecycles in the commit phase don't resemble a tree). - let userCodePhase : UserCodePhase | null = null; - let userCodeFiber : Fiber | null = null; + let currentPhase : MeasurementPhase | null = null; + let currentPhaseFiber : Fiber | null = null; const performanceMeasureSafe = (label : string, markName : string) => { try { @@ -76,26 +83,30 @@ if (__DEV__) { performance.clearMeasures(label); }; + const getGenericPhase = (fiber : Fiber) : MeasurementPhase => { + return fiber.alternate === null ? '[mount]' : '[update]'; + } + const getMarkName = (fiber : Fiber, phase : MeasurementPhase) => { const debugID = ((fiber._debugID : any) : number); return `${reactEmoji} ${debugID}:${phase}`; }; - const beginMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const startMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { const markName = getMarkName(fiber, phase); performance.mark(markName); }; - const clearPendingMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const cancelMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { const markName = getMarkName(fiber, phase); performance.clearMarks(markName); }; - const completeMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const stopMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { const markName = getMarkName(fiber, phase); const componentName = getComponentName(fiber) || 'Unknown'; - const label = phase === 'total' ? - `${reactEmoji} ${componentName}` : + const label = phase[0] === '[' ? + `${reactEmoji} ${componentName} ${phase}` : `${reactEmoji} ${componentName}.${phase}`; performanceMeasureSafe(label, markName); }; @@ -116,12 +127,12 @@ if (__DEV__) { } }; - const clearPendingUserCodeMeasurement = () => { - if (userCodePhase !== null && userCodeFiber !== null) { - clearPendingMeasurement(userCodeFiber, userCodePhase); + const clearPendingPhaseMeasurement = () => { + if (currentPhase !== null && currentPhaseFiber !== null) { + cancelMeasurement(currentPhaseFiber, currentPhase); } - userCodeFiber = null; - userCodePhase = null; + currentPhaseFiber = null; + currentPhase = null; }; const pauseTimers = () => { @@ -130,7 +141,7 @@ if (__DEV__) { let fiber = currentFiber; while (fiber) { if (fiber._debugIsCurrentlyTiming) { - completeMeasurement(fiber, 'total'); + stopMeasurement(fiber, getGenericPhase(fiber)); } fiber = fiber.return; } @@ -141,7 +152,7 @@ if (__DEV__) { resumeTimersRecursively(fiber.return); } if (fiber._debugIsCurrentlyTiming) { - beginMeasurement(fiber, 'total'); + startMeasurement(fiber, getGenericPhase(fiber)); } }; @@ -157,14 +168,14 @@ if (__DEV__) { if (!supportsUserTiming) { return; } - clearPendingUserCodeMeasurement(); + clearPendingPhaseMeasurement(); // If we pause, this is the fiber to unwind from. currentFiber = fiber; if (shouldIgnore(fiber)) { return; } fiber._debugIsCurrentlyTiming = true; - beginMeasurement(fiber, 'total'); + startMeasurement(fiber, getGenericPhase(fiber)); }, cancelWorkTimer(fiber : Fiber) : void { @@ -177,14 +188,14 @@ if (__DEV__) { // Remember we shouldn't complete measurement for this fiber. // Otherwise flamechart will be deep even for small updates. fiber._debugIsCurrentlyTiming = false; - clearPendingMeasurement(fiber, 'total'); + cancelMeasurement(fiber, getGenericPhase(fiber)); }, stopWorkTimer(fiber : Fiber) : void { if (!supportsUserTiming) { return; } - clearPendingUserCodeMeasurement(); + clearPendingPhaseMeasurement(); // If we pause, its parent is the fiber to unwind from. currentFiber = fiber.return; if (shouldIgnore(fiber)) { @@ -194,31 +205,31 @@ if (__DEV__) { return; } fiber._debugIsCurrentlyTiming = false; - completeMeasurement(fiber, 'total'); + stopMeasurement(fiber, getGenericPhase(fiber)); }, - startUserCodeTimer( + startPhaseTimer( fiber : Fiber, - phase : UserCodePhase, + phase : MeasurementPhase, ) : void { if (!supportsUserTiming) { return; } - clearPendingUserCodeMeasurement(); - userCodeFiber = fiber; - userCodePhase = phase; - beginMeasurement(fiber, phase); + clearPendingPhaseMeasurement(); + currentPhaseFiber = fiber; + currentPhase = phase; + startMeasurement(fiber, phase); }, - stopUserCodeTimer() : void { + stopPhaseTimer() : void { if (!supportsUserTiming) { return; } - if (userCodePhase !== null && userCodeFiber !== null) { - completeMeasurement(userCodeFiber, userCodePhase); + if (currentPhase !== null && currentPhaseFiber !== null) { + stopMeasurement(currentPhaseFiber, currentPhase); } - userCodePhase = null; - userCodeFiber = null; + currentPhase = null; + currentPhaseFiber = null; }, startWorkLoopTimer() : void { diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 0214fc670a43..6656c0b91ec7 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -68,8 +68,8 @@ if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var { cancelWorkTimer, - startUserCodeTimer, - stopUserCodeTimer, + startPhaseTimer, + stopPhaseTimer, } = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); @@ -237,9 +237,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; ReactDebugCurrentFiber.phase = 'render'; - startUserCodeTimer(workInProgress, 'render'); + startPhaseTimer(workInProgress, 'render'); nextChildren = fn(nextProps, context); - stopUserCodeTimer(); + stopPhaseTimer(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = fn(nextProps, context); @@ -292,9 +292,9 @@ module.exports = function( let nextChildren; if (__DEV__) { ReactDebugCurrentFiber.phase = 'render'; - startUserCodeTimer(workInProgress, 'render'); + startPhaseTimer(workInProgress, 'render'); nextChildren = instance.render(); - stopUserCodeTimer(); + stopPhaseTimer(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = instance.render(); @@ -479,9 +479,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; - startUserCodeTimer(workInProgress, 'render'); + startPhaseTimer(workInProgress, 'render'); value = fn(props, context); - stopUserCodeTimer(); + stopPhaseTimer(); } else { value = fn(props, context); } diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index bcd0df3b5fcf..5480da945c4f 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -41,8 +41,8 @@ const isArray = Array.isArray; if (__DEV__) { var { - startUserCodeTimer, - stopUserCodeTimer, + startPhaseTimer, + stopPhaseTimer, } = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); var warnOnInvalidCallback = function(callback : mixed, callerName : string) { @@ -107,11 +107,11 @@ module.exports = function( const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { if (__DEV__) { - startUserCodeTimer(workInProgress, 'shouldComponentUpdate'); + startPhaseTimer(workInProgress, 'shouldComponentUpdate'); } const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } if (__DEV__) { @@ -272,11 +272,11 @@ module.exports = function( const needsContext = isContextConsumer(workInProgress); const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject; if (__DEV__) { - startUserCodeTimer(workInProgress, 'constructor'); + startPhaseTimer(workInProgress, 'constructor'); } const instance = new ctor(props, context); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } adoptClassInstance(workInProgress, instance); checkClassInstance(workInProgress); @@ -312,11 +312,11 @@ module.exports = function( if (typeof instance.componentWillMount === 'function') { if (__DEV__) { - startUserCodeTimer(workInProgress, 'componentWillMount'); + startPhaseTimer(workInProgress, 'componentWillMount'); } instance.componentWillMount(); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } // If we had additional state updates during this life-cycle, let's // process them now. @@ -385,11 +385,11 @@ module.exports = function( if (typeof newInstance.componentWillMount === 'function') { if (__DEV__) { - startUserCodeTimer(workInProgress, 'componentWillMount'); + startPhaseTimer(workInProgress, 'componentWillMount'); } newInstance.componentWillMount(); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } } // If we had additional state updates, process them now. @@ -437,11 +437,11 @@ module.exports = function( if (oldProps !== newProps || oldContext !== newContext) { if (typeof instance.componentWillReceiveProps === 'function') { if (__DEV__) { - startUserCodeTimer(workInProgress, 'componentWillReceiveProps'); + startPhaseTimer(workInProgress, 'componentWillReceiveProps'); } instance.componentWillReceiveProps(newProps, newContext); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } if (instance.state !== workInProgress.memoizedState) { @@ -498,11 +498,11 @@ module.exports = function( markUpdate(workInProgress); if (typeof instance.componentWillUpdate === 'function') { if (__DEV__) { - startUserCodeTimer(workInProgress, 'componentWillUpdate'); + startPhaseTimer(workInProgress, 'componentWillUpdate'); } instance.componentWillUpdate(newProps, newState, newContext); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } } } else { diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 027ba2d3c118..8c6cf8555e79 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -39,8 +39,8 @@ var invariant = require('fbjs/lib/invariant'); if (__DEV__) { var { - startUserCodeTimer, - stopUserCodeTimer, + startPhaseTimer, + stopPhaseTimer, } = require('ReactDebugFiberPerf'); } @@ -64,9 +64,9 @@ module.exports = function( function safelyCallComponentWillUnmount(current, instance) { if (__DEV__) { const unmountError = invokeGuardedCallback(null, () => { - startUserCodeTimer(current, 'componentWillUnmount'); + startPhaseTimer(current, 'componentWillUnmount'); instance.componentWillUnmount(); - stopUserCodeTimer(); + stopPhaseTimer(); }); if (unmountError) { captureError(current, unmountError); @@ -84,7 +84,11 @@ module.exports = function( const ref = current.ref; if (ref !== null) { if (__DEV__) { - const refError = invokeGuardedCallback(null, ref, null, null); + const refError = invokeGuardedCallback(null, () => { + startPhaseTimer(current, '[detach ref]'); + ref(null); + stopPhaseTimer(); + }, null, null); if (refError !== null) { captureError(current, refError); } @@ -103,7 +107,13 @@ module.exports = function( if (current) { const currentRef = current.ref; if (currentRef !== null && currentRef !== finishedWork.ref) { + if (__DEV__) { + startPhaseTimer(current, '[detach ref]'); + } currentRef(null); + if (__DEV__) { + stopPhaseTimer(); + } } } } @@ -448,11 +458,11 @@ module.exports = function( if (current === null) { if (typeof instance.componentDidMount === 'function') { if (__DEV__) { - startUserCodeTimer(finishedWork, 'componentDidMount'); + startPhaseTimer(finishedWork, 'componentDidMount'); } instance.componentDidMount(); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } } } else { @@ -460,17 +470,23 @@ module.exports = function( const prevProps = current.memoizedProps; const prevState = current.memoizedState; if (__DEV__) { - startUserCodeTimer(finishedWork, 'componentDidUpdate'); + startPhaseTimer(finishedWork, 'componentDidUpdate'); } instance.componentDidUpdate(prevProps, prevState); if (__DEV__) { - stopUserCodeTimer(); + stopPhaseTimer(); } } } } if ((finishedWork.effectTag & Callback) && finishedWork.updateQueue !== null) { + if (__DEV__) { + startPhaseTimer(finishedWork, '[call callbacks]'); + } commitCallbacks(finishedWork, finishedWork.updateQueue, instance); + if (__DEV__) { + stopPhaseTimer(); + } } return; } @@ -478,7 +494,13 @@ module.exports = function( const updateQueue = finishedWork.updateQueue; if (updateQueue !== null) { const instance = finishedWork.child && finishedWork.child.stateNode; + if (__DEV__) { + startPhaseTimer(finishedWork, '[call callbacks]'); + } commitCallbacks(finishedWork, updateQueue, instance); + if (__DEV__) { + stopPhaseTimer(); + } } return; } @@ -525,7 +547,13 @@ module.exports = function( const ref = finishedWork.ref; if (ref !== null) { const instance = getPublicInstance(finishedWork.stateNode); + if (__DEV__) { + startPhaseTimer(finishedWork, '[attach ref]'); + } ref(instance); + if (__DEV__) { + stopPhaseTimer(); + } } } diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 8a0493618321..df60ff9d2625 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -44,6 +44,10 @@ var { if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var { + startPhaseTimer, + stopPhaseTimer, + } = require('ReactDebugFiberPerf'); } var invariant = require('fbjs/lib/invariant'); @@ -218,6 +222,10 @@ module.exports = function( // Even better would be if children weren't special cased at all tho. const instance : I = workInProgress.stateNode; const currentHostContext = getHostContext(); + + if (__DEV__) { + startPhaseTimer(workInProgress, '[compute diff]'); + } const updatePayload = prepareUpdate( instance, type, @@ -226,6 +234,9 @@ module.exports = function( rootContainerInstance, currentHostContext ); + if (__DEV__) { + stopPhaseTimer(); + } // TODO: Type this specific to this type of component. workInProgress.updateQueue = (updatePayload : any); @@ -250,6 +261,9 @@ module.exports = function( // "stack" as the parent. Then append children as we go in beginWork // or completeWork depending on we want to add then top->down or // bottom->up. Top->down is faster in IE11. + if (__DEV__) { + startPhaseTimer(workInProgress, '[create]'); + } const instance = createInstance( type, newProps, @@ -257,15 +271,16 @@ module.exports = function( currentHostContext, workInProgress ); - appendAllChildren(instance, workInProgress); - // Certain renderers require commit-time effects for initial mount. // (eg DOM renderer supports auto-focus for certain elements). // Make sure such renderers get scheduled for later work. if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) { markUpdate(workInProgress); } + if (__DEV__) { + stopPhaseTimer(); + } workInProgress.stateNode = instance; if (workInProgress.ref !== null) { diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index dd740ad17a59..7e0122fd9ff0 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -37,8 +37,8 @@ if (__DEV__) { var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var { - startUserCodeTimer, - stopUserCodeTimer, + startPhaseTimer, + stopPhaseTimer, } = require('ReactDebugFiberPerf'); var warnedAboutMissingGetChildContext = {}; } @@ -176,9 +176,9 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin let childContext; if (__DEV__) { ReactDebugCurrentFiber.phase = 'getChildContext'; - startUserCodeTimer(fiber, 'getChildContext'); + startPhaseTimer(fiber, 'getChildContext'); childContext = instance.getChildContext(); - stopUserCodeTimer(); + stopPhaseTimer(); ReactDebugCurrentFiber.phase = null; } else { childContext = instance.getChildContext(); diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 3c8004e5eab4..3bd9951b2152 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -98,6 +98,8 @@ if (__DEV__) { var { startWorkTimer, stopWorkTimer, + startPhaseTimer, + stopPhaseTimer, startWorkLoopTimer, stopWorkLoopTimer, startCommitTimer, @@ -303,7 +305,13 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Fri, 3 Mar 2017 04:19:53 +0000 Subject: [PATCH 18/37] Polish --- .../shared/fiber/ReactDebugFiberPerf.js | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 2ca093993819..e0bc178d8e8b 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -83,31 +83,38 @@ if (__DEV__) { performance.clearMeasures(label); }; - const getGenericPhase = (fiber : Fiber) : MeasurementPhase => { - return fiber.alternate === null ? '[mount]' : '[update]'; - } - - const getMarkName = (fiber : Fiber, phase : MeasurementPhase) => { + const getMarkName = (fiber : Fiber, phase : MeasurementPhase | null) => { const debugID = ((fiber._debugID : any) : number); - return `${reactEmoji} ${debugID}:${phase}`; + return `${reactEmoji} ${debugID}:${phase || 'total'}`; }; - const startMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const startMeasurement = (fiber : Fiber, phase : MeasurementPhase | null) => { const markName = getMarkName(fiber, phase); performance.mark(markName); }; - const cancelMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const cancelMeasurement = (fiber : Fiber, phase : MeasurementPhase | null) => { const markName = getMarkName(fiber, phase); performance.clearMarks(markName); }; - const stopMeasurement = (fiber : Fiber, phase : MeasurementPhase) => { + const stopMeasurement = (fiber : Fiber, phase : MeasurementPhase | null) => { const markName = getMarkName(fiber, phase); const componentName = getComponentName(fiber) || 'Unknown'; - const label = phase[0] === '[' ? - `${reactEmoji} ${componentName} ${phase}` : - `${reactEmoji} ${componentName}.${phase}`; + let label; + if (phase === null) { + // These are composite component total time measurements. + // We'll make mounts visually different from updates with a suffix. + // Don't append a suffix for updates to avoid clutter. + label = `${reactEmoji} ${componentName}${fiber.alternate ? '' : ' [create]'}`; + } else if (phase[0] === '[') { + // Specific phases (e.g. "div [create]", or "MyButton [attach ref]"). + // May apply to host components. + label = `${reactEmoji} ${componentName} ${phase}`; + } else { + // Composite component methods. + label = `${reactEmoji} ${componentName}.${phase}`; + } performanceMeasureSafe(label, markName); }; @@ -141,7 +148,7 @@ if (__DEV__) { let fiber = currentFiber; while (fiber) { if (fiber._debugIsCurrentlyTiming) { - stopMeasurement(fiber, getGenericPhase(fiber)); + stopMeasurement(fiber, null); } fiber = fiber.return; } @@ -152,7 +159,7 @@ if (__DEV__) { resumeTimersRecursively(fiber.return); } if (fiber._debugIsCurrentlyTiming) { - startMeasurement(fiber, getGenericPhase(fiber)); + startMeasurement(fiber, null); } }; @@ -175,7 +182,7 @@ if (__DEV__) { return; } fiber._debugIsCurrentlyTiming = true; - startMeasurement(fiber, getGenericPhase(fiber)); + startMeasurement(fiber, null); }, cancelWorkTimer(fiber : Fiber) : void { @@ -188,7 +195,7 @@ if (__DEV__) { // Remember we shouldn't complete measurement for this fiber. // Otherwise flamechart will be deep even for small updates. fiber._debugIsCurrentlyTiming = false; - cancelMeasurement(fiber, getGenericPhase(fiber)); + cancelMeasurement(fiber, null); }, stopWorkTimer(fiber : Fiber) : void { @@ -205,7 +212,7 @@ if (__DEV__) { return; } fiber._debugIsCurrentlyTiming = false; - stopMeasurement(fiber, getGenericPhase(fiber)); + stopMeasurement(fiber, null); }, startPhaseTimer( From 456e1f86d4ad5ca58190fad7c47e1ddb57670af1 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 13:29:42 +0000 Subject: [PATCH 19/37] Indent --- src/renderers/shared/fiber/ReactFiberCompleteWork.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index df60ff9d2625..b221f1f99c9d 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -234,7 +234,7 @@ module.exports = function( rootContainerInstance, currentHostContext ); - if (__DEV__) { + if (__DEV__) { stopPhaseTimer(); } From 68abb8e44299435590a5464d5ec8d3a46d63da39 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 16:54:14 +0000 Subject: [PATCH 20/37] Refactor and track cascading updates --- .../shared/fiber/ReactDebugFiberPerf.js | 127 ++++++++++++------ .../shared/fiber/ReactFiberScheduler.js | 5 + 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index e0bc178d8e8b..ed6dcdcae873 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -51,8 +51,7 @@ if (__DEV__) { // Prefix measurements so that it's possible to filter them. // Longer prefixes are hard to read in DevTools. const reactEmoji = '\u269B'; - const reconcileLabel = `${reactEmoji} (React Tree Reconciliation)`; - const commitLabel = `${reactEmoji} (Committing Changes)`; + const warningEmoji = '\u26A0\uFE0F'; const supportsUserTiming = typeof performance !== 'undefined' && typeof performance.mark === 'function' && @@ -69,53 +68,69 @@ if (__DEV__) { // lifecycles in the commit phase don't resemble a tree). let currentPhase : MeasurementPhase | null = null; let currentPhaseFiber : Fiber | null = null; + // Did a lifecycle hook schedule an update? This is often a performance problem, + // so we will keep track of it, and include it in the report. + let hasScheduledUpdateInCurrentPhase : boolean = false; + // Track commits caused by cascading updates. + let commitCountInCurrentWorkLoop : number = 0; - const performanceMeasureSafe = (label : string, markName : string) => { + const formatMarkName = (markName : string) => { + return `${reactEmoji} ${markName}`; + }; + + const formatLabel = (label : string, warning : string | null) => { + const prefix = warning ? `${warningEmoji} ` : `${reactEmoji} `; + const suffix = warning ? ` Warning: ${warning}` : ''; + return `${prefix}${label}${suffix}`; + }; + + const beginMark = (markName : string) => { + performance.mark(formatMarkName(markName)); + }; + + const clearMark = (markName : string) => { + performance.clearMarks(formatMarkName(markName)); + }; + + const endMark = (label : string, markName : string, warning : string | null) => { + const formattedMarkName = formatMarkName(markName); + const formattedLabel = formatLabel(label, warning); try { - performance.measure(label, markName); + performance.measure(formattedLabel, formattedMarkName); } catch (err) { // If previous mark was missing for some reason, this will throw. // This could only happen if React crashed in an unexpected place earlier. // Don't pile on with more errors. } // Clear marks immediately to avoid growing buffer. - performance.clearMarks(markName); - performance.clearMeasures(label); + performance.clearMarks(formattedMarkName); + performance.clearMeasures(formattedLabel); }; - const getMarkName = (fiber : Fiber, phase : MeasurementPhase | null) => { + const getFiberMarkName = (fiber : Fiber, phase : MeasurementPhase | null) => { const debugID = ((fiber._debugID : any) : number); - return `${reactEmoji} ${debugID}:${phase || 'total'}`; + return `${debugID}:${phase || 'total'}`; }; - const startMeasurement = (fiber : Fiber, phase : MeasurementPhase | null) => { - const markName = getMarkName(fiber, phase); - performance.mark(markName); - }; - - const cancelMeasurement = (fiber : Fiber, phase : MeasurementPhase | null) => { - const markName = getMarkName(fiber, phase); - performance.clearMarks(markName); - }; - - const stopMeasurement = (fiber : Fiber, phase : MeasurementPhase | null) => { - const markName = getMarkName(fiber, phase); + const getFiberLabel = (fiber : Fiber, phase : MeasurementPhase | null) => { const componentName = getComponentName(fiber) || 'Unknown'; - let label; - if (phase === null) { + if (phase === null && fiber.alternate === null) { // These are composite component total time measurements. // We'll make mounts visually different from updates with a suffix. // Don't append a suffix for updates to avoid clutter. - label = `${reactEmoji} ${componentName}${fiber.alternate ? '' : ' [create]'}`; + phase = '[create]'; + } + if (phase === null) { + // Composite component total time for updates. + return componentName; } else if (phase[0] === '[') { // Specific phases (e.g. "div [create]", or "MyButton [attach ref]"). // May apply to host components. - label = `${reactEmoji} ${componentName} ${phase}`; + return `${componentName} ${phase}`; } else { // Composite component methods. - label = `${reactEmoji} ${componentName}.${phase}`; + return `${componentName}.${phase}`; } - performanceMeasureSafe(label, markName); }; const shouldIgnore = (fiber : Fiber) : boolean => { @@ -136,10 +151,12 @@ if (__DEV__) { const clearPendingPhaseMeasurement = () => { if (currentPhase !== null && currentPhaseFiber !== null) { - cancelMeasurement(currentPhaseFiber, currentPhase); + const markName = getFiberMarkName(currentPhaseFiber, currentPhase); + clearMark(markName); } currentPhaseFiber = null; currentPhase = null; + hasScheduledUpdateInCurrentPhase = false; }; const pauseTimers = () => { @@ -148,7 +165,9 @@ if (__DEV__) { let fiber = currentFiber; while (fiber) { if (fiber._debugIsCurrentlyTiming) { - stopMeasurement(fiber, null); + const markName = getFiberMarkName(fiber, null); + const label = getFiberLabel(fiber, null); + endMark(label, markName, null); } fiber = fiber.return; } @@ -159,7 +178,8 @@ if (__DEV__) { resumeTimersRecursively(fiber.return); } if (fiber._debugIsCurrentlyTiming) { - startMeasurement(fiber, null); + const markName = getFiberMarkName(fiber, null); + beginMark(markName); } }; @@ -171,6 +191,10 @@ if (__DEV__) { }; ReactDebugFiberPerf = { + recordScheduleUpdate() : void { + hasScheduledUpdateInCurrentPhase = true; + }, + startWorkTimer(fiber : Fiber) : void { if (!supportsUserTiming) { return; @@ -182,7 +206,8 @@ if (__DEV__) { return; } fiber._debugIsCurrentlyTiming = true; - startMeasurement(fiber, null); + const markName = getFiberMarkName(fiber, null); + beginMark(markName); }, cancelWorkTimer(fiber : Fiber) : void { @@ -195,7 +220,8 @@ if (__DEV__) { // Remember we shouldn't complete measurement for this fiber. // Otherwise flamechart will be deep even for small updates. fiber._debugIsCurrentlyTiming = false; - cancelMeasurement(fiber, null); + const markName = getFiberMarkName(fiber, null); + clearMark(markName); }, stopWorkTimer(fiber : Fiber) : void { @@ -212,7 +238,9 @@ if (__DEV__) { return; } fiber._debugIsCurrentlyTiming = false; - stopMeasurement(fiber, null); + const markName = getFiberMarkName(fiber, null); + const label = getFiberLabel(fiber, null); + endMark(label, markName, null); }, startPhaseTimer( @@ -225,7 +253,8 @@ if (__DEV__) { clearPendingPhaseMeasurement(); currentPhaseFiber = fiber; currentPhase = phase; - startMeasurement(fiber, phase); + const markName = getFiberMarkName(fiber, phase); + beginMark(markName); }, stopPhaseTimer() : void { @@ -233,7 +262,12 @@ if (__DEV__) { return; } if (currentPhase !== null && currentPhaseFiber !== null) { - stopMeasurement(currentPhaseFiber, currentPhase); + const markName = getFiberMarkName(currentPhaseFiber, currentPhase); + const label = getFiberLabel(currentPhaseFiber, currentPhase); + const warning = hasScheduledUpdateInCurrentPhase ? + 'Scheduled a cascading update' : + null; + endMark(label, markName, warning); } currentPhase = null; currentPhaseFiber = null; @@ -243,9 +277,10 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + commitCountInCurrentWorkLoop = 0; // This is top level call. // Any other measurements are performed within. - performance.mark(reconcileLabel); + beginMark('(React Tree Reconciliation)'); // Resume any measurements that were in progress during the last loop. resumeTimers(); }, @@ -256,21 +291,37 @@ if (__DEV__) { } // Pause any measurements until the next loop. pauseTimers(); - performanceMeasureSafe(reconcileLabel, reconcileLabel); + const warning = commitCountInCurrentWorkLoop > 1 ? + 'There were cascading updates' : + null; + endMark( + '(React Tree Reconciliation)', + '(React Tree Reconciliation)', + warning + ); + commitCountInCurrentWorkLoop = 0; }, startCommitTimer() : void { if (!supportsUserTiming) { return; } - performance.mark(commitLabel); + beginMark('(Committing Changes)'); }, stopCommitTimer() : void { if (!supportsUserTiming) { return; } - performanceMeasureSafe(commitLabel, commitLabel); + commitCountInCurrentWorkLoop++; + const warning = commitCountInCurrentWorkLoop > 1 ? + 'Caused by a cascading update' : + null; + endMark( + '(Committing Changes)', + '(Committing Changes)', + warning + ); }, }; } diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 3bd9951b2152..503f5fc76bb0 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -96,6 +96,7 @@ if (__DEV__) { var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var { + recordScheduleUpdate, startWorkTimer, stopWorkTimer, startPhaseTimer, @@ -1214,6 +1215,10 @@ module.exports = function(config : HostConfig Date: Fri, 3 Mar 2017 17:42:39 +0000 Subject: [PATCH 21/37] More prominent warnings --- .../shared/fiber/ReactDebugFiberPerf.js | 74 ++++++++++++++++--- .../shared/fiber/ReactFiberScheduler.js | 14 ++++ 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index ed6dcdcae873..3af69e464d36 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -68,11 +68,13 @@ if (__DEV__) { // lifecycles in the commit phase don't resemble a tree). let currentPhase : MeasurementPhase | null = null; let currentPhaseFiber : Fiber | null = null; - // Did a lifecycle hook schedule an update? This is often a performance problem, + // Did lifecycle hook schedule an update? This is often a performance problem, // so we will keep track of it, and include it in the report. - let hasScheduledUpdateInCurrentPhase : boolean = false; // Track commits caused by cascading updates. let commitCountInCurrentWorkLoop : number = 0; + let isCommitting : boolean = false; + let hasScheduledUpdateInCurrentCommit : boolean = false; + let hasScheduledUpdateInCurrentPhase : boolean = false; const formatMarkName = (markName : string) => { return `${reactEmoji} ${markName}`; @@ -192,7 +194,12 @@ if (__DEV__) { ReactDebugFiberPerf = { recordScheduleUpdate() : void { - hasScheduledUpdateInCurrentPhase = true; + if (isCommitting) { + hasScheduledUpdateInCurrentCommit = true; + } + if (currentPhase !== null) { + hasScheduledUpdateInCurrentPhase = true; + } }, startWorkTimer(fiber : Fiber) : void { @@ -289,23 +296,25 @@ if (__DEV__) { if (!supportsUserTiming) { return; } - // Pause any measurements until the next loop. - pauseTimers(); const warning = commitCountInCurrentWorkLoop > 1 ? 'There were cascading updates' : null; + commitCountInCurrentWorkLoop = 0; + // Pause any measurements until the next loop. + pauseTimers(); endMark( '(React Tree Reconciliation)', '(React Tree Reconciliation)', - warning + warning, ); - commitCountInCurrentWorkLoop = 0; }, startCommitTimer() : void { if (!supportsUserTiming) { return; } + isCommitting = true; + hasScheduledUpdateInCurrentCommit = false; beginMark('(Committing Changes)'); }, @@ -313,14 +322,57 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + + let warning = null; + if (hasScheduledUpdateInCurrentCommit) { + warning = 'Lifecycle hook scheduled a cascading update'; + } else if (commitCountInCurrentWorkLoop > 0) { + warning = 'Caused by a cascading update in earlier commit'; + } + hasScheduledUpdateInCurrentCommit = false; commitCountInCurrentWorkLoop++; - const warning = commitCountInCurrentWorkLoop > 1 ? - 'Caused by a cascading update' : - null; + isCommitting = false; + endMark( '(Committing Changes)', '(Committing Changes)', - warning + warning, + ); + }, + + startCommitHostEffectsTimer() : void { + if (!supportsUserTiming) { + return; + } + beginMark('(Committing Host Effects)'); + }, + + stopCommitHostEffectsTimer() : void { + if (!supportsUserTiming) { + return; + } + endMark( + '(Committing Host Effects)', + '(Committing Host Effects)', + null, + ); + }, + + startCommitLifeCyclesTimer() : void { + if (!supportsUserTiming) { + return; + } + beginMark('(Calling Lifecycle Methods)'); + }, + + stopCommitLifeCyclesTimer() : void { + if (!supportsUserTiming) { + return; + } + endMark( + '(Calling Lifecycle Methods)', + '(Calling Lifecycle Methods)', + null, ); }, }; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 503f5fc76bb0..26e035284729 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -105,6 +105,10 @@ if (__DEV__) { stopWorkLoopTimer, startCommitTimer, stopCommitTimer, + startCommitHostEffectsTimer, + stopCommitHostEffectsTimer, + startCommitLifeCyclesTimer, + stopCommitLifeCyclesTimer, } = require('ReactDebugFiberPerf'); var warnAboutUpdateOnUnmounted = function(instance : ReactClass) { @@ -466,6 +470,9 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Fri, 3 Mar 2017 18:31:10 +0000 Subject: [PATCH 22/37] Make mark names themselves readable This is useful for RN Systrace which doesn't let us assign labels after the fact. --- .../shared/fiber/ReactDebugFiberPerf.js | 83 ++++++++++++------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 3af69e464d36..5f14f7a0efee 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -109,14 +109,19 @@ if (__DEV__) { performance.clearMeasures(formattedLabel); }; - const getFiberMarkName = (fiber : Fiber, phase : MeasurementPhase | null) => { - const debugID = ((fiber._debugID : any) : number); - return `${debugID}:${phase || 'total'}`; + const getFiberMarkName = ( + label : string, + debugID : number, + ) => { + return `${label} (#${debugID})`; }; - const getFiberLabel = (fiber : Fiber, phase : MeasurementPhase | null) => { - const componentName = getComponentName(fiber) || 'Unknown'; - if (phase === null && fiber.alternate === null) { + const getFiberLabel = ( + componentName : string, + isMounted : boolean, + phase : MeasurementPhase | null, + ) => { + if (phase === null && !isMounted) { // These are composite component total time measurements. // We'll make mounts visually different from updates with a suffix. // Don't append a suffix for updates to avoid clutter. @@ -135,7 +140,38 @@ if (__DEV__) { } }; - const shouldIgnore = (fiber : Fiber) : boolean => { + const beginFiberMark = (fiber : Fiber, phase : MeasurementPhase | null) => { + const componentName = getComponentName(fiber) || 'Unknown'; + const debugID = ((fiber._debugID : any) : number); + const isMounted = fiber.alternate !== null; + const label = getFiberLabel(componentName, isMounted, phase); + const markName = getFiberMarkName(label, debugID); + beginMark(markName); + }; + + const clearFiberMark = (fiber : Fiber, phase : MeasurementPhase | null) => { + const componentName = getComponentName(fiber) || 'Unknown'; + const debugID = ((fiber._debugID : any) : number); + const isMounted = fiber.alternate !== null; + const label = getFiberLabel(componentName, isMounted, phase); + const markName = getFiberMarkName(label, debugID); + clearMark(markName); + }; + + const endFiberMark = ( + fiber : Fiber, + phase : MeasurementPhase | null, + warning : string | null, + ) => { + const componentName = getComponentName(fiber) || 'Unknown'; + const debugID = ((fiber._debugID : any) : number); + const isMounted = fiber.alternate !== null; + const label = getFiberLabel(componentName, isMounted, phase); + const markName = getFiberMarkName(label, debugID); + endMark(label, markName, warning); + }; + + const shouldIgnoreFiber = (fiber : Fiber) : boolean => { // Host components should be skipped in the timeline. // We could check typeof fiber.type, but does this work with RN? switch (fiber.tag) { @@ -153,8 +189,7 @@ if (__DEV__) { const clearPendingPhaseMeasurement = () => { if (currentPhase !== null && currentPhaseFiber !== null) { - const markName = getFiberMarkName(currentPhaseFiber, currentPhase); - clearMark(markName); + clearFiberMark(currentPhaseFiber, currentPhase); } currentPhaseFiber = null; currentPhase = null; @@ -167,9 +202,7 @@ if (__DEV__) { let fiber = currentFiber; while (fiber) { if (fiber._debugIsCurrentlyTiming) { - const markName = getFiberMarkName(fiber, null); - const label = getFiberLabel(fiber, null); - endMark(label, markName, null); + endFiberMark(fiber, null, null); } fiber = fiber.return; } @@ -180,8 +213,7 @@ if (__DEV__) { resumeTimersRecursively(fiber.return); } if (fiber._debugIsCurrentlyTiming) { - const markName = getFiberMarkName(fiber, null); - beginMark(markName); + beginFiberMark(fiber, null); } }; @@ -209,26 +241,24 @@ if (__DEV__) { clearPendingPhaseMeasurement(); // If we pause, this is the fiber to unwind from. currentFiber = fiber; - if (shouldIgnore(fiber)) { + if (shouldIgnoreFiber(fiber)) { return; } fiber._debugIsCurrentlyTiming = true; - const markName = getFiberMarkName(fiber, null); - beginMark(markName); + beginFiberMark(fiber, null); }, cancelWorkTimer(fiber : Fiber) : void { if (!supportsUserTiming) { return; } - if (shouldIgnore(fiber)) { + if (shouldIgnoreFiber(fiber)) { return; } // Remember we shouldn't complete measurement for this fiber. // Otherwise flamechart will be deep even for small updates. fiber._debugIsCurrentlyTiming = false; - const markName = getFiberMarkName(fiber, null); - clearMark(markName); + clearFiberMark(fiber, null); }, stopWorkTimer(fiber : Fiber) : void { @@ -238,16 +268,14 @@ if (__DEV__) { clearPendingPhaseMeasurement(); // If we pause, its parent is the fiber to unwind from. currentFiber = fiber.return; - if (shouldIgnore(fiber)) { + if (shouldIgnoreFiber(fiber)) { return; } if (!fiber._debugIsCurrentlyTiming) { return; } fiber._debugIsCurrentlyTiming = false; - const markName = getFiberMarkName(fiber, null); - const label = getFiberLabel(fiber, null); - endMark(label, markName, null); + endFiberMark(fiber, null, null); }, startPhaseTimer( @@ -260,8 +288,7 @@ if (__DEV__) { clearPendingPhaseMeasurement(); currentPhaseFiber = fiber; currentPhase = phase; - const markName = getFiberMarkName(fiber, phase); - beginMark(markName); + beginFiberMark(currentPhaseFiber, currentPhase); }, stopPhaseTimer() : void { @@ -269,12 +296,10 @@ if (__DEV__) { return; } if (currentPhase !== null && currentPhaseFiber !== null) { - const markName = getFiberMarkName(currentPhaseFiber, currentPhase); - const label = getFiberLabel(currentPhaseFiber, currentPhase); const warning = hasScheduledUpdateInCurrentPhase ? 'Scheduled a cascading update' : null; - endMark(label, markName, warning); + endFiberMark(currentPhaseFiber, currentPhase, warning); } currentPhase = null; currentPhaseFiber = null; From d4df2b7c641608235154f85228da566d7a031bc8 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 19:12:55 +0000 Subject: [PATCH 23/37] Keep track of how many effects we call --- src/renderers/shared/fiber/ReactDebugFiberPerf.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 5f14f7a0efee..b4a02035b694 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -75,6 +75,7 @@ if (__DEV__) { let isCommitting : boolean = false; let hasScheduledUpdateInCurrentCommit : boolean = false; let hasScheduledUpdateInCurrentPhase : boolean = false; + let effectCountInCurrentCommit : number = 0; const formatMarkName = (markName : string) => { return `${reactEmoji} ${markName}`; @@ -285,6 +286,9 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + if (isCommitting) { + effectCountInCurrentCommit++; + } clearPendingPhaseMeasurement(); currentPhaseFiber = fiber; currentPhase = phase; @@ -369,6 +373,7 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + effectCountInCurrentCommit = 0; beginMark('(Committing Host Effects)'); }, @@ -376,8 +381,10 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + const count = effectCountInCurrentCommit; + effectCountInCurrentCommit = 0; endMark( - '(Committing Host Effects)', + `(Committing Host Effect: ${count} Total)`, '(Committing Host Effects)', null, ); @@ -387,6 +394,7 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + effectCountInCurrentCommit = 0; beginMark('(Calling Lifecycle Methods)'); }, @@ -394,8 +402,10 @@ if (__DEV__) { if (!supportsUserTiming) { return; } + const count = effectCountInCurrentCommit; + effectCountInCurrentCommit = 0; endMark( - '(Calling Lifecycle Methods)', + `(Calling Lifecycle Methods: ${count} Total)`, '(Calling Lifecycle Methods)', null, ); From c97f53efcadd9c401e6059d9c636c9d461a64f0f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 3 Mar 2017 19:23:41 +0000 Subject: [PATCH 24/37] Fix typo --- src/renderers/shared/fiber/ReactDebugFiberPerf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index b4a02035b694..8f21bf41a597 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -384,7 +384,7 @@ if (__DEV__) { const count = effectCountInCurrentCommit; effectCountInCurrentCommit = 0; endMark( - `(Committing Host Effect: ${count} Total)`, + `(Committing Host Effects: ${count} Total)`, '(Committing Host Effects)', null, ); From 2039f01ae748100bef9010d8c1b24b58a3a13ee7 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 4 Mar 2017 03:54:41 +0000 Subject: [PATCH 25/37] Do less work to reduce the overhead --- .../shared/fiber/ReactDebugFiberPerf.js | 80 ++++++++----------- .../shared/fiber/ReactFiberBeginWork.js | 12 +-- .../shared/fiber/ReactFiberClassComponent.js | 6 -- .../shared/fiber/ReactFiberCommitWork.js | 30 +------ .../shared/fiber/ReactFiberCompleteWork.js | 17 +--- .../shared/fiber/ReactFiberScheduler.js | 36 ++------- 6 files changed, 44 insertions(+), 137 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 8f21bf41a597..daf6cd5a2e6c 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -22,16 +22,7 @@ type MeasurementPhase = 'componentWillUpdate' | 'componentDidUpdate' | 'componentDidMount' | - 'getChildContext' | - '[attach ref]' | - '[call callbacks]' | - '[clear]' | - '[create]' | - '[compute diff]' | - '[detach ref]' | - '[mount]' | - '[update]' | - '[unmount]'; + 'getChildContext'; // Trust the developer to only use this with a __DEV__ check let ReactDebugFiberPerf = ((null: any): typeof ReactDebugFiberPerf); @@ -71,11 +62,14 @@ if (__DEV__) { // Did lifecycle hook schedule an update? This is often a performance problem, // so we will keep track of it, and include it in the report. // Track commits caused by cascading updates. - let commitCountInCurrentWorkLoop : number = 0; let isCommitting : boolean = false; let hasScheduledUpdateInCurrentCommit : boolean = false; let hasScheduledUpdateInCurrentPhase : boolean = false; + let commitCountInCurrentWorkLoop : number = 0; let effectCountInCurrentCommit : number = 0; + // During commits, we only show a measurement once per method name + // to avoid stretch the commit phase with measurement overhead. + const labelsInCurrentCommit : Set = new Set(); const formatMarkName = (markName : string) => { return `${reactEmoji} ${markName}`; @@ -110,10 +104,7 @@ if (__DEV__) { performance.clearMeasures(formattedLabel); }; - const getFiberMarkName = ( - label : string, - debugID : number, - ) => { + const getFiberMarkName = (label : string, debugID : number) => { return `${label} (#${debugID})`; }; @@ -122,32 +113,35 @@ if (__DEV__) { isMounted : boolean, phase : MeasurementPhase | null, ) => { - if (phase === null && !isMounted) { - // These are composite component total time measurements. - // We'll make mounts visually different from updates with a suffix. - // Don't append a suffix for updates to avoid clutter. - phase = '[create]'; - } if (phase === null) { - // Composite component total time for updates. - return componentName; - } else if (phase[0] === '[') { - // Specific phases (e.g. "div [create]", or "MyButton [attach ref]"). - // May apply to host components. - return `${componentName} ${phase}`; + // These are composite component total time measurements. + return `${componentName} [${isMounted ? 'update' : 'mount'}]`; } else { // Composite component methods. return `${componentName}.${phase}`; } }; - const beginFiberMark = (fiber : Fiber, phase : MeasurementPhase | null) => { + const beginFiberMark = ( + fiber : Fiber, + phase : MeasurementPhase | null, + ) : boolean => { const componentName = getComponentName(fiber) || 'Unknown'; const debugID = ((fiber._debugID : any) : number); const isMounted = fiber.alternate !== null; const label = getFiberLabel(componentName, isMounted, phase); + + if (isCommitting && labelsInCurrentCommit.has(label)) { + // During the commit phase, we don't show duplicate labels because + // there is a fixed overhead for every measurement, and we don't + // want to stretch the commit phase beyond necessary. + return false; + } + labelsInCurrentCommit.add(label); + const markName = getFiberMarkName(label, debugID); beginMark(markName); + return true; }; const clearFiberMark = (fiber : Fiber, phase : MeasurementPhase | null) => { @@ -226,6 +220,10 @@ if (__DEV__) { }; ReactDebugFiberPerf = { + recordEffect() : void { + effectCountInCurrentCommit++; + }, + recordScheduleUpdate() : void { if (isCommitting) { hasScheduledUpdateInCurrentCommit = true; @@ -236,24 +234,19 @@ if (__DEV__) { }, startWorkTimer(fiber : Fiber) : void { - if (!supportsUserTiming) { + if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { return; } - clearPendingPhaseMeasurement(); // If we pause, this is the fiber to unwind from. currentFiber = fiber; - if (shouldIgnoreFiber(fiber)) { + if (!beginFiberMark(fiber, null)) { return; } fiber._debugIsCurrentlyTiming = true; - beginFiberMark(fiber, null); }, cancelWorkTimer(fiber : Fiber) : void { - if (!supportsUserTiming) { - return; - } - if (shouldIgnoreFiber(fiber)) { + if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { return; } // Remember we shouldn't complete measurement for this fiber. @@ -263,15 +256,11 @@ if (__DEV__) { }, stopWorkTimer(fiber : Fiber) : void { - if (!supportsUserTiming) { + if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { return; } - clearPendingPhaseMeasurement(); // If we pause, its parent is the fiber to unwind from. currentFiber = fiber.return; - if (shouldIgnoreFiber(fiber)) { - return; - } if (!fiber._debugIsCurrentlyTiming) { return; } @@ -286,13 +275,12 @@ if (__DEV__) { if (!supportsUserTiming) { return; } - if (isCommitting) { - effectCountInCurrentCommit++; - } clearPendingPhaseMeasurement(); + if (!beginFiberMark(fiber, phase)) { + return; + } currentPhaseFiber = fiber; currentPhase = phase; - beginFiberMark(currentPhaseFiber, currentPhase); }, stopPhaseTimer() : void { @@ -344,6 +332,7 @@ if (__DEV__) { } isCommitting = true; hasScheduledUpdateInCurrentCommit = false; + labelsInCurrentCommit.clear(); beginMark('(Committing Changes)'); }, @@ -361,6 +350,7 @@ if (__DEV__) { hasScheduledUpdateInCurrentCommit = false; commitCountInCurrentWorkLoop++; isCommitting = false; + labelsInCurrentCommit.clear(); endMark( '(Committing Changes)', diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 6656c0b91ec7..3fa3cfda99c4 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -66,11 +66,7 @@ var invariant = require('fbjs/lib/invariant'); if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); - var { - cancelWorkTimer, - startPhaseTimer, - stopPhaseTimer, - } = require('ReactDebugFiberPerf'); + var {cancelWorkTimer} = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); var warnedAboutStatelessRefs = {}; @@ -237,9 +233,7 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; ReactDebugCurrentFiber.phase = 'render'; - startPhaseTimer(workInProgress, 'render'); nextChildren = fn(nextProps, context); - stopPhaseTimer(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = fn(nextProps, context); @@ -292,9 +286,7 @@ module.exports = function( let nextChildren; if (__DEV__) { ReactDebugCurrentFiber.phase = 'render'; - startPhaseTimer(workInProgress, 'render'); nextChildren = instance.render(); - stopPhaseTimer(); ReactDebugCurrentFiber.phase = null; } else { nextChildren = instance.render(); @@ -479,9 +471,7 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; - startPhaseTimer(workInProgress, 'render'); value = fn(props, context); - stopPhaseTimer(); } else { value = fn(props, context); } diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 5480da945c4f..f79d16ac3bba 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -271,13 +271,7 @@ module.exports = function( const unmaskedContext = getUnmaskedContext(workInProgress); const needsContext = isContextConsumer(workInProgress); const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject; - if (__DEV__) { - startPhaseTimer(workInProgress, 'constructor'); - } const instance = new ctor(props, context); - if (__DEV__) { - stopPhaseTimer(); - } adoptClassInstance(workInProgress, instance); checkClassInstance(workInProgress); diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 8c6cf8555e79..f7fa89b85940 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -84,11 +84,7 @@ module.exports = function( const ref = current.ref; if (ref !== null) { if (__DEV__) { - const refError = invokeGuardedCallback(null, () => { - startPhaseTimer(current, '[detach ref]'); - ref(null); - stopPhaseTimer(); - }, null, null); + const refError = invokeGuardedCallback(null, ref, null, null); if (refError !== null) { captureError(current, refError); } @@ -107,13 +103,7 @@ module.exports = function( if (current) { const currentRef = current.ref; if (currentRef !== null && currentRef !== finishedWork.ref) { - if (__DEV__) { - startPhaseTimer(current, '[detach ref]'); - } currentRef(null); - if (__DEV__) { - stopPhaseTimer(); - } } } } @@ -480,13 +470,7 @@ module.exports = function( } } if ((finishedWork.effectTag & Callback) && finishedWork.updateQueue !== null) { - if (__DEV__) { - startPhaseTimer(finishedWork, '[call callbacks]'); - } commitCallbacks(finishedWork, finishedWork.updateQueue, instance); - if (__DEV__) { - stopPhaseTimer(); - } } return; } @@ -494,13 +478,7 @@ module.exports = function( const updateQueue = finishedWork.updateQueue; if (updateQueue !== null) { const instance = finishedWork.child && finishedWork.child.stateNode; - if (__DEV__) { - startPhaseTimer(finishedWork, '[call callbacks]'); - } commitCallbacks(finishedWork, updateQueue, instance); - if (__DEV__) { - stopPhaseTimer(); - } } return; } @@ -547,13 +525,7 @@ module.exports = function( const ref = finishedWork.ref; if (ref !== null) { const instance = getPublicInstance(finishedWork.stateNode); - if (__DEV__) { - startPhaseTimer(finishedWork, '[attach ref]'); - } ref(instance); - if (__DEV__) { - stopPhaseTimer(); - } } } diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index b221f1f99c9d..3d04f7d0ed44 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -44,10 +44,6 @@ var { if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); - var { - startPhaseTimer, - stopPhaseTimer, - } = require('ReactDebugFiberPerf'); } var invariant = require('fbjs/lib/invariant'); @@ -223,9 +219,6 @@ module.exports = function( const instance : I = workInProgress.stateNode; const currentHostContext = getHostContext(); - if (__DEV__) { - startPhaseTimer(workInProgress, '[compute diff]'); - } const updatePayload = prepareUpdate( instance, type, @@ -234,9 +227,6 @@ module.exports = function( rootContainerInstance, currentHostContext ); - if (__DEV__) { - stopPhaseTimer(); - } // TODO: Type this specific to this type of component. workInProgress.updateQueue = (updatePayload : any); @@ -261,9 +251,6 @@ module.exports = function( // "stack" as the parent. Then append children as we go in beginWork // or completeWork depending on we want to add then top->down or // bottom->up. Top->down is faster in IE11. - if (__DEV__) { - startPhaseTimer(workInProgress, '[create]'); - } const instance = createInstance( type, newProps, @@ -271,6 +258,7 @@ module.exports = function( currentHostContext, workInProgress ); + appendAllChildren(instance, workInProgress); // Certain renderers require commit-time effects for initial mount. // (eg DOM renderer supports auto-focus for certain elements). @@ -278,9 +266,6 @@ module.exports = function( if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) { markUpdate(workInProgress); } - if (__DEV__) { - stopPhaseTimer(); - } workInProgress.stateNode = instance; if (workInProgress.ref !== null) { diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 26e035284729..3eac43e54931 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -96,6 +96,7 @@ if (__DEV__) { var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var { + recordEffect, recordScheduleUpdate, startWorkTimer, stopWorkTimer, @@ -307,16 +308,11 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig Date: Tue, 7 Mar 2017 03:42:20 +0000 Subject: [PATCH 26/37] Fix lint --- src/renderers/shared/fiber/ReactFiberScheduler.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index a3425285d6e9..4df506a0567e 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -100,8 +100,6 @@ if (__DEV__) { recordScheduleUpdate, startWorkTimer, stopWorkTimer, - startPhaseTimer, - stopPhaseTimer, startWorkLoopTimer, stopWorkLoopTimer, startCommitTimer, @@ -379,7 +377,6 @@ module.exports = function(config : HostConfig Date: Tue, 7 Mar 2017 03:57:57 +0000 Subject: [PATCH 27/37] Remove closure --- .../shared/fiber/ReactFiberCommitWork.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index cdb10cdead6c..5647aea7fbfe 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -60,14 +60,22 @@ module.exports = function( getPublicInstance, } = config; + function callComponentWillUnmountWithTimerInDev(current, instance) { + startPhaseTimer(current, 'componentWillUnmount'); + instance.componentWillUnmount(); + stopPhaseTimer(); + } + // Capture errors so they don't interrupt unmounting. function safelyCallComponentWillUnmount(current, instance) { if (__DEV__) { - const unmountError = invokeGuardedCallback(null, () => { - startPhaseTimer(current, 'componentWillUnmount'); - instance.componentWillUnmount(); - stopPhaseTimer(); - }); + const unmountError = invokeGuardedCallback( + null, + callComponentWillUnmountWithTimerInDev, + null, + current, + instance, + ); if (unmountError) { captureError(current, unmountError); } From 70d25c65c325d1bff1238ead1d065f379e83ea47 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 7 Mar 2017 03:59:17 +0000 Subject: [PATCH 28/37] Remove unintentional formatting changes --- src/renderers/shared/fiber/ReactFiberCompleteWork.js | 2 +- src/renderers/shared/fiber/ReactFiberScheduler.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index b37a1ca8d463..aef565e1d2e8 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -222,7 +222,6 @@ module.exports = function( // Even better would be if children weren't special cased at all tho. const instance : I = workInProgress.stateNode; const currentHostContext = getHostContext(); - const updatePayload = prepareUpdate( instance, type, @@ -267,6 +266,7 @@ module.exports = function( ); appendAllChildren(instance, workInProgress); + // Certain renderers require commit-time effects for initial mount. // (eg DOM renderer supports auto-focus for certain elements). // Make sure such renderers get scheduled for later work. diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 4df506a0567e..fc91812be5e2 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -362,7 +362,6 @@ module.exports = function(config : HostConfig Date: Tue, 7 Mar 2017 04:32:31 +0000 Subject: [PATCH 29/37] Add tests --- package.json | 2 +- .../shared/fiber/ReactDebugFiberPerf.js | 2 +- .../shared/fiber/ReactFiberScheduler.js | 16 +- .../__tests__/ReactIncrementalPerf-test.js | 463 ++++++++++++++++++ .../ReactIncrementalPerf-test.js.snap | 239 +++++++++ 5 files changed, 716 insertions(+), 6 deletions(-) create mode 100644 src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js create mode 100644 src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap diff --git a/package.json b/package.json index c91e902a6c17..7c2aa09b5c3b 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "./scripts/jest/environment.js" ], "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/", + "testRegex": "/__tests__/.*\\.js$", "moduleFileExtensions": [ "js", "json", diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index daf6cd5a2e6c..239f0e386708 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -42,7 +42,7 @@ if (__DEV__) { // Prefix measurements so that it's possible to filter them. // Longer prefixes are hard to read in DevTools. const reactEmoji = '\u269B'; - const warningEmoji = '\u26A0\uFE0F'; + const warningEmoji = '\uD83D\uDED1'; const supportsUserTiming = typeof performance !== 'undefined' && typeof performance.mark === 'function' && diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index fc91812be5e2..8de85c6b58b1 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -372,23 +372,28 @@ module.exports = function(config : HostConfig(config : HostConfig { + let React; + let ReactCoroutine; + let ReactFeatureFlags; + let ReactNoop; + let ReactPortal; + + let root; + let activeMeasure; + let knownMarks; + let knownMeasures; + + function resetFlamechart() { + root = { + children: [], + indent: -1, + markName: null, + label: null, + parent: null, + toString() { + return this.children.map(c => c.toString()).join('\n'); + }, + }; + activeMeasure = root; + knownMarks = new Set(); + knownMeasures = new Set(); + } + + function addComment(comment) { + activeMeasure.children.push( + `${' '.repeat(activeMeasure.indent + 1)}// ${comment}` + ); + } + + function getFlameChart() { + // Make sure we unwind the measurement stack every time. + expect(activeMeasure.indent).toBe(-1); + expect(activeMeasure).toBe(root); + // We should always clean them up because browsers + // buffer user timing measurements forever. + expect(knownMarks.size).toBe(0); + expect(knownMeasures.size).toBe(0); + return root.toString(); + } + + function createUserTimingPolyfill() { + // This is not a true polyfill, but it gives us enough + // to capture measurements in a readable tree-like output. + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API + return { + mark(markName) { + const measure = { + children: [], + indent: activeMeasure.indent + 1, + markName: markName, + // Will be assigned on measure() call: + label: null, + parent: activeMeasure, + toString() { + return [ + ' '.repeat(this.indent) + this.label, + ...this.children.map(c => c.toString()), + ].join('\n') + ( + // Extra newline after each root reconciliation + this.indent === 0 ? '\n' : '' + ); + }, + }; + // Step one level deeper + activeMeasure.children.push(measure); + activeMeasure = measure; + knownMarks.add(markName); + }, + // We don't use the overload with three arguments. + measure(label, markName) { + if (markName !== activeMeasure.markName) { + throw new Error('Unexpected measure() call.'); + } + // Step one level up + activeMeasure.label = label; + activeMeasure = activeMeasure.parent; + knownMeasures.add(label); + }, + clearMarks(markName) { + if (markName === activeMeasure.markName) { + // Step one level up if we're in this measure + activeMeasure = activeMeasure.parent; + activeMeasure.children.length--; + } + knownMarks.delete(markName); + }, + clearMeasures(label) { + knownMeasures.delete(label); + }, + }; + } + + beforeEach(() => { + jest.resetModules(); + resetFlamechart(); + global.performance = createUserTimingPolyfill(); + + // Import after the polyfill is set up: + React = require('React'); + ReactCoroutine = require('ReactCoroutine'); + ReactFeatureFlags = require('ReactFeatureFlags'); + ReactNoop = require('ReactNoop'); + ReactPortal = require('ReactPortal'); + ReactFeatureFlags.disableNewFiberFeatures = false; + }); + + afterEach(() => { + delete global.performance; + }); + + function Parent(props) { + return
{props.children}
; + } + + function Child(props) { + return
{props.children}
; + } + + it('measures a simple reconciliation', () => { + ReactNoop.render(); + addComment('Mount'); + ReactNoop.flush(); + + ReactNoop.render(); + addComment('Update'); + ReactNoop.flush(); + + ReactNoop.render(null); + addComment('Unmount'); + ReactNoop.flush(); + + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('skips parents during setState', () => { + class A extends React.Component { + render() { + return
{this.props.children}
; + } + } + + class B extends React.Component { + render() { + return
{this.props.children}
; + } + } + + let a; + let b; + ReactNoop.render( + + + + a = inst} /> + + + + b = inst} /> + + + ); + ReactNoop.flush(); + resetFlamechart(); + + a.setState({}); + b.setState({}); + addComment('Should include just A and B, no Parents'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('warns on cascading renders from setState', () => { + class Cascading extends React.Component { + componentDidMount() { + this.setState({}); + } + + render() { + return
{this.props.children}
; + } + } + ReactNoop.render(); + addComment('Should print a warning'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('warns on cascading renders from top-level render', () => { + class Cascading extends React.Component { + componentDidMount() { + ReactNoop.renderToRootWithID(, 'b'); + addComment('Scheduling another root from componentDidMount'); + ReactNoop.flush(); + } + + render() { + return
{this.props.children}
; + } + } + + ReactNoop.renderToRootWithID(, 'a'); + addComment('Rendering the first root'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('captures all lifecycles', () => { + class AllLifecycles extends React.Component { + static childContextTypes = { + foo: React.PropTypes.any, + }; + shouldComponentUpdate() { + return true; + } + getChildContext() { + return {foo: 42}; + } + componentWillMount() {} + componentDidMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + componentDidUpdate() {} + componentWillUnmount() {} + render() { + return
; + } + } + ReactNoop.render(); + addComment('Mount'); + ReactNoop.flush(); + ReactNoop.render(); + addComment('Update'); + ReactNoop.flush(); + ReactNoop.render(null); + addComment('Unmount'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('measures deprioritized work', () => { + ReactNoop.performAnimationWork(() => { + ReactNoop.render( + + + + ); + }); + addComment('Flush the parent'); + ReactNoop.flushAnimationPri(); + addComment('Flush the child'); + ReactNoop.flushDeferredPri(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('measures deferred work in chunks', () => { + class A extends React.Component { + render() { + return
{this.props.children}
; + } + } + + class B extends React.Component { + render() { + return
{this.props.children}
; + } + } + + ReactNoop.render( + +
+ + + + + + + ); + addComment('Start mounting Parent and A'); + ReactNoop.flushDeferredPri(40); + addComment('Mount B just a little (but not enough to memoize)'); + ReactNoop.flushDeferredPri(10); + addComment('Complete B and Parent'); + ReactNoop.flushDeferredPri(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('recovers from fatal errors', () => { + function Baddie() { + throw new Error('Game over'); + } + + ReactNoop.render(); + try { + addComment('Will fatal'); + ReactNoop.flush(); + } catch (err) { + expect(err.message).toBe('Game over'); + } + ReactNoop.render(); + addComment('Will reconcile from a clean state'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('recovers from caught errors', () => { + function Baddie() { + throw new Error('Game over'); + } + function ErrorReport() { + return
; + } + + class Boundary extends React.Component { + state = {error: null}; + unstable_handleError(error) { + this.setState({error}); + } + render() { + if (this.state.error) { + return ; + } + return this.props.children; + } + } + ReactNoop.render( + + + + + + + + ); + addComment('Stop on Baddie and restart from Boundary'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('deduplicate lifecycle names during commit to reduce overhead', () => { + class A extends React.Component { + componentDidUpdate() {} + render() { + return
; + } + } + class B extends React.Component { + componentDidUpdate(prevProps) { + if (this.props.cascade && !prevProps.cascade) { + this.setState({}); + } + } + render() { + return
; + } + } + + ReactNoop.render( + + + + + + + ); + ReactNoop.flush(); + resetFlamechart(); + + ReactNoop.render( + + + + + + + ); + addComment('The commit phase should mention A and B just once'); + ReactNoop.flush(); + ReactNoop.render( + + + + + + + ); + addComment('Because of deduplication, we don\'t know B was cascading,'); + addComment('but we should still see the warning for the commit phase.'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('supports coroutines', () => { + function Continuation({ isSame }) { + return ; + } + + function CoChild({ bar }) { + return ReactCoroutine.createYield({ + props: { + bar: bar, + }, + continuation: Continuation, + }); + } + + function Indirection() { + return [, ]; + } + + function HandleYields(props, yields) { + return yields.map(y => + + ); + } + + function CoParent(props) { + return ReactCoroutine.createCoroutine( + props.children, + HandleYields, + props + ); + } + + function App() { + return
; + } + + ReactNoop.render(); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('supports portals', () => { + const noopContainer = {children: []}; + ReactNoop.render( + + {ReactPortal.createPortal(, noopContainer, null)} + + ); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); +}); diff --git a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap new file mode 100644 index 000000000000..d737fd69364a --- /dev/null +++ b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -0,0 +1,239 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` +"// Mount +⚛ (React Tree Reconciliation) + ⚛ AllLifecycles [mount] + ⚛ AllLifecycles.componentWillMount + ⚛ AllLifecycles.getChildContext + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + ⚛ AllLifecycles.componentDidMount + +// Update +⚛ (React Tree Reconciliation) + ⚛ AllLifecycles [update] + ⚛ AllLifecycles.componentWillReceiveProps + ⚛ AllLifecycles.shouldComponentUpdate + ⚛ AllLifecycles.componentWillUpdate + ⚛ AllLifecycles.getChildContext + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) + ⚛ AllLifecycles.componentDidUpdate + +// Unmount +⚛ (React Tree Reconciliation) + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ AllLifecycles.componentWillUnmount + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf deduplicate lifecycle names during commit to reduce overhead 1`] = ` +"// The commit phase should mention A and B just once +⚛ (React Tree Reconciliation) + ⚛ Parent [update] + ⚛ A [update] + ⚛ B [update] + ⚛ A [update] + ⚛ B [update] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 9 Total) + ⚛ (Calling Lifecycle Methods: 9 Total) + ⚛ A.componentDidUpdate + ⚛ B.componentDidUpdate + +// Because of deduplication, we don't know B was cascading, +// but we should still see the warning for the commit phase. +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Parent [update] + ⚛ A [update] + ⚛ B [update] + ⚛ A [update] + ⚛ B [update] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 9 Total) + ⚛ (Calling Lifecycle Methods: 9 Total) + ⚛ A.componentDidUpdate + ⚛ B.componentDidUpdate + ⚛ B [update] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 3 Total) + ⚛ B.componentDidUpdate +" +`; + +exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` +"// Mount +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +// Update +⚛ (React Tree Reconciliation) + ⚛ Parent [update] + ⚛ Child [update] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) + +// Unmount +⚛ (React Tree Reconciliation) + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = ` +"// Start mounting Parent and A +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ A [mount] + ⚛ Child [mount] + +// Mount B just a little (but not enough to memoize) +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ B [mount] + +// Complete B and Parent +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ B [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` +"// Flush the parent +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +// Flush the child +⚛ (React Tree Reconciliation) + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) +" +`; + +exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` +"// Stop on Baddie and restart from Boundary +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Parent [mount] + ⚛ Boundary [mount] + ⚛ Parent [mount] + ⚛ Baddie [mount] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + ⚛ Boundary [update] + ⚛ ErrorReport [mount] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) +" +`; + +exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` +"// Will fatal +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Baddie [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + +// Will reconcile from a clean state +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf skips parents during setState 1`] = ` +"// Should include just A and B, no Parents +⚛ (React Tree Reconciliation) + ⚛ A [update] + ⚛ B [update] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 6 Total) + ⚛ (Calling Lifecycle Methods: 6 Total) +" +`; + +exports[`ReactDebugFiberPerf supports coroutines 1`] = ` +"⚛ (React Tree Reconciliation) + ⚛ App [mount] + ⚛ CoParent [mount] + ⚛ HandleYields [mount] + ⚛ Indirection [mount] + ⚛ CoChild [mount] + ⚛ CoChild [mount] + ⚛ Continuation [mount] + ⚛ Continuation [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf supports portals 1`] = ` +"⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) +" +`; + +exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` +"// Should print a warning +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Parent [mount] + ⚛ Cascading [mount] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + 🛑 Cascading.componentDidMount Warning: Scheduled a cascading update + ⚛ Cascading [update] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) +" +`; + +exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = ` +"// Rendering the first root +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Cascading [mount] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + 🛑 Cascading.componentDidMount Warning: Scheduled a cascading update + // Scheduling another root from componentDidMount + ⚛ Child [mount] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; From a2f94965dd1421ff37e214e3fc023b162b43227f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 02:20:54 +0000 Subject: [PATCH 30/37] Fix test regex and record tests --- package.json | 2 +- scripts/fiber/tests-failing.txt | 1 + scripts/fiber/tests-passing-except-dev.txt | 1 - scripts/fiber/tests-passing.txt | 14 ++++++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7c2aa09b5c3b..a236574febd6 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "./scripts/jest/environment.js" ], "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/.*\\.js$", + "testRegex": "/__tests__/.*(\\.js|coffee|ts)$", "moduleFileExtensions": [ "js", "json", diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index 9c2bebbd84db..c0151e1c7c28 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -3,6 +3,7 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js src/renderers/__tests__/ReactComponentTreeHook-test.js * can be retrieved by ID +* works src/renderers/__tests__/ReactHostOperationHistoryHook-test.js * gets recorded during an update diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index bd22db6cbaa9..e8eb7af61b2a 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -54,7 +54,6 @@ src/renderers/__tests__/ReactComponentTreeHook-test.js * reports update counts * does not report top-level wrapper as a root * registers inlined text nodes -* works src/renderers/__tests__/ReactComponentTreeHook-test.native.js * uses displayName or Unknown for classic components diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index c2cfe1b9350b..21d81e3adec1 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1565,6 +1565,20 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js * should ignore errors thrown in log method to prevent cycle * should relay info about error boundary and retry attempts if applicable +src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js +* measures a simple reconciliation +* skips parents during setState +* warns on cascading renders from setState +* warns on cascading renders from top-level render +* captures all lifecycles +* measures deprioritized work +* measures deferred work in chunks +* recovers from fatal errors +* recovers from caught errors +* deduplicate lifecycle names during commit to reduce overhead +* supports coroutines +* supports portals + src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js * handles isMounted even when the initial render is deferred * handles isMounted when an unmount is deferred From 491f6aafb5dd361a5988e3714dba9713ff6fdb6d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 02:40:39 +0000 Subject: [PATCH 31/37] Disable irrelevant tests needed for ReactPerf --- scripts/fiber/tests-failing.txt | 16 - scripts/fiber/tests-passing-except-dev.txt | 128 ----- scripts/fiber/tests-passing.txt | 40 -- .../__tests__/ReactComponentTreeHook-test.js | 485 +++++++++--------- .../ReactComponentTreeHook-test.native.js | 10 +- .../ReactHostOperationHistoryHook-test.js | 7 +- src/renderers/__tests__/ReactPerf-test.js | 7 +- 7 files changed, 270 insertions(+), 423 deletions(-) diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index c0151e1c7c28..325811e74910 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -1,22 +1,6 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js * should pass previous context to lifecycles -src/renderers/__tests__/ReactComponentTreeHook-test.js -* can be retrieved by ID -* works - -src/renderers/__tests__/ReactHostOperationHistoryHook-test.js -* gets recorded during an update - -src/renderers/__tests__/ReactPerf-test.js -* should count no-op update as waste -* should count no-op update in child as waste -* should include stats for components unmounted during measurement -* should include lifecycle methods in measurements -* should include render time of functional components -* should not count time in a portal towards lifecycle method -* should work when measurement starts during reconciliation - src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js * gives source code refs for unknown prop warning (ssr) * gives source code refs for unknown prop warning for exact elements (ssr) diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index e8eb7af61b2a..ea25216a39b1 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -1,131 +1,3 @@ -src/renderers/__tests__/ReactComponentTreeHook-test.js -* uses displayName or Unknown for classic components -* uses displayName, name, or ReactComponent for modern components -* uses displayName, name, or Object for factory components -* uses displayName, name, or StatelessComponent for functional components -* reports a host tree correctly -* reports a simple tree with composites correctly -* reports a tree with composites correctly -* ignores null children -* ignores false children -* reports text nodes as children -* reports a single text node as a child -* reports a single number node as a child -* reports a zero as a child -* skips empty nodes for multiple children -* reports html content as no children -* updates text of a single text child -* updates from no children to a single text child -* updates from a single text child to no children -* updates from html content to a single text child -* updates from a single text child to html content -* updates from no children to multiple text children -* updates from multiple text children to no children -* updates from html content to multiple text children -* updates from multiple text children to html content -* updates from html content to no children -* updates from no children to html content -* updates from one text child to multiple text children -* updates from multiple text children to one text child -* updates text nodes when reordering -* updates host nodes when reordering with keys -* updates host nodes when reordering without keys -* updates a single composite child of a different type -* updates a single composite child of the same type -* updates from no children to a single composite child -* updates from a single composite child to no children -* updates mixed children -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* tracks owner correctly -* purges unmounted components automatically -* reports update counts -* does not report top-level wrapper as a root -* registers inlined text nodes - -src/renderers/__tests__/ReactComponentTreeHook-test.native.js -* uses displayName or Unknown for classic components -* uses displayName, name, or ReactComponent for modern components -* uses displayName, name, or Object for factory components -* uses displayName, name, or StatelessComponent for functional components -* reports a host tree correctly -* reports a simple tree with composites correctly -* reports a tree with composites correctly -* ignores null children -* ignores false children -* reports text nodes as children -* reports a single text node as a child -* reports a single number node as a child -* reports a zero as a child -* skips empty nodes for multiple children -* updates text of a single text child -* updates from no children to a single text child -* updates from a single text child to no children -* updates from no children to multiple text children -* updates from multiple text children to no children -* updates from one text child to multiple text children -* updates from multiple text children to one text child -* updates text nodes when reordering -* updates host nodes when reordering with keys -* updates host nodes when reordering with keys -* updates a single composite child of a different type -* updates a single composite child of the same type -* updates from no children to a single composite child -* updates from a single composite child to no children -* updates mixed children -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* tracks owner correctly -* purges unmounted components automatically -* reports update counts -* does not report top-level wrapper as a root - -src/renderers/__tests__/ReactHostOperationHistoryHook-test.js -* gets recorded for host roots -* gets recorded for composite roots -* gets recorded when a native is mounted deeply instead of null -* gets recorded during mount -* gets recorded during an update -* gets ignored if the styles are shallowly equal -* gets recorded during mount -* gets recorded during mount -* gets recorded during mount -* gets recorded during an update from text content -* gets recorded during an update from html -* gets recorded during an update from children -* gets recorded when composite renders to a different type -* gets recorded when composite renders to null after a native -* gets recorded during an update from text content -* gets recorded during an update from html -* gets recorded during an update from children -* gets reported when a child is inserted -* gets reported when a child is inserted -* gets reported when a child is removed - src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js * should not warn when server-side rendering `onScroll` * should warn about incorrect casing on properties (ssr) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 21d81e3adec1..f42927d0b322 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -561,8 +561,6 @@ src/renderers/__tests__/ReactComponentLifeCycle-test.js src/renderers/__tests__/ReactComponentTreeHook-test.js * gets created -* is created during mounting -* is created when calling renderToString during render src/renderers/__tests__/ReactCompositeComponent-test.js * should support module pattern components @@ -673,17 +671,6 @@ src/renderers/__tests__/ReactErrorBoundaries-test.js * renders empty output if error boundary does not handle the error * passes first error when two errors happen in commit -src/renderers/__tests__/ReactHostOperationHistoryHook-test.js -* gets ignored for composite roots that return null -* gets recorded during an update -* gets recorded as a removal during an update -* gets recorded during an update -* gets recorded during an update -* gets ignored if new text is equal -* gets ignored if new text is equal -* gets ignored if the type has not changed -* gets ignored if new html is equal - src/renderers/__tests__/ReactIdentity-test.js * should allow key property to express identity * should use composite identity @@ -750,33 +737,6 @@ src/renderers/__tests__/ReactMultiChildText-test.js * should throw if rendering both HTML and children * should render between nested components and inline children -src/renderers/__tests__/ReactPerf-test.js -* should not count initial render as waste -* should not count unmount as waste -* should not count content update as waste -* should not count child addition as waste -* should not count child removal as waste -* should not count property update as waste -* should not count style update as waste -* should not count property removal as waste -* should not count raw HTML update as waste -* should not count child reordering as waste -* should not count text update as waste -* should not count replacing null with a host as waste -* should not count replacing a host with null as waste -* warns once when using getMeasurementsSummaryMap -* warns once when using printDOM -* returns isRunning state -* start has no effect when already running -* stop has no effect when already stopped -* should print console error only once -* should not print errant warnings if render() throws -* should not print errant warnings if componentWillMount() throws -* should not print errant warnings if componentDidMount() throws -* should not print errant warnings if portal throws in render() -* should not print errant warnings if portal throws in componentWillMount() -* should not print errant warnings if portal throws in componentDidMount() - src/renderers/__tests__/ReactStatelessComponent-test.js * should render stateless component * should update stateless component diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js index a9c19c13eb96..da91ab262767 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.js @@ -11,6 +11,9 @@ 'use strict'; +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; + describe('ReactComponentTreeHook', () => { var React; var ReactDOM; @@ -30,6 +33,148 @@ describe('ReactComponentTreeHook', () => { ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); }); + // This is the only part used both by Stack and Fiber. + describe('stack addenda', () => { + it('gets created', () => { + function getAddendum(element) { + var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); + return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); + } + + var Anon = React.createClass({displayName: null, render: () => null}); + var Orange = React.createClass({render: () => null}); + + expectDev(getAddendum()).toBe( + '' + ); + expectDev(getAddendum(
)).toBe( + '\n in div (at **)' + ); + expectDev(getAddendum()).toBe( + '\n in Unknown (at **)' + ); + expectDev(getAddendum()).toBe( + '\n in Orange (at **)' + ); + expectDev(getAddendum(React.createElement(Orange))).toBe( + '\n in Orange' + ); + + var renders = 0; + var rOwnedByQ; + + function Q() { + return (rOwnedByQ = React.createElement(R)); + } + function R() { + return
; + } + class S extends React.Component { + componentDidMount() { + // Check that the parent path is still fetched when only S itself is on + // the stack. + this.forceUpdate(); + } + render() { + expectDev(getAddendum()).toBe( + '\n in S (at **)' + + '\n in div (at **)' + + '\n in R (created by Q)' + + '\n in Q (at **)' + ); + expectDev(getAddendum()).toBe( + '\n in span (at **)' + + '\n in S (at **)' + + '\n in div (at **)' + + '\n in R (created by Q)' + + '\n in Q (at **)' + ); + expectDev(getAddendum(React.createElement('span'))).toBe( + '\n in span (created by S)' + + '\n in S (at **)' + + '\n in div (at **)' + + '\n in R (created by Q)' + + '\n in Q (at **)' + ); + renders++; + return null; + } + } + ReactDOM.render(, document.createElement('div')); + expectDev(renders).toBe(2); + + // Make sure owner is fetched for the top element too. + expectDev(getAddendum(rOwnedByQ)).toBe( + '\n in R (created by Q)' + ); + }); + + // These are features and regression tests that only affect + // the Stack implementation of the stack addendum. + if (!ReactDOMFeatureFlags.useFiber) { + it('can be retrieved by ID', () => { + function getAddendum(id) { + var addendum = ReactComponentTreeHook.getStackAddendumByID(id); + return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); + } + + class Q extends React.Component { + render() { + return null; + } + } + + var q = ReactDOM.render(, document.createElement('div')); + expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe( + '\n in Q (at **)' + ); + + spyOn(console, 'error'); + getAddendum(-17); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: ReactComponentTreeHook: Missing React element for ' + + 'debugID -17 when building stack' + ); + }); + + it('is created during mounting', () => { + // https://github.com/facebook/react/issues/7187 + var el = document.createElement('div'); + var portalEl = document.createElement('div'); + class Foo extends React.Component { + componentWillMount() { + ReactDOM.render(
, portalEl); + } + render() { + return
; + } + } + ReactDOM.render(, el); + }); + + it('is created when calling renderToString during render', () => { + // https://github.com/facebook/react/issues/7190 + var el = document.createElement('div'); + class Foo extends React.Component { + render() { + return ( +
+
+ {ReactDOMServer.renderToString(
)} +
+
+ ); + } + } + ReactDOM.render(, el); + }); + } + }); + + // The rest of this file is not relevant for Fiber. + // TODO: remove tests below when we delete Stack. + function assertTreeMatches(pairs) { if (!Array.isArray(pairs[0])) { pairs = [pairs]; @@ -106,7 +251,7 @@ describe('ReactComponentTreeHook', () => { }); } - describe('mount', () => { + describeStack('mount', () => { it('uses displayName or Unknown for classic components', () => { class Foo extends React.Component { render() { @@ -520,7 +665,7 @@ describe('ReactComponentTreeHook', () => { }); }); - describe('update', () => { + describeStack('update', () => { describe('host component', () => { it('updates text of a single text child', () => { var elementBefore =
Hi.
; @@ -1601,276 +1746,144 @@ describe('ReactComponentTreeHook', () => { }); }); - it('tracks owner correctly', () => { - class Foo extends React.Component { - render() { - return

Hi.

; + describeStack('misc', () => { + it('tracks owner correctly', () => { + class Foo extends React.Component { + render() { + return

Hi.

; + } + } + function Bar({children}) { + return
{children} Mom
; } - } - function Bar({children}) { - return
{children} Mom
; - } - // Note that owner is not calculated for text nodes - // because they are not created from real elements. - var element =
; - var tree = { - displayName: 'article', - children: [{ - displayName: 'Foo', + // Note that owner is not calculated for text nodes + // because they are not created from real elements. + var element =
; + var tree = { + displayName: 'article', children: [{ - displayName: 'Bar', - ownerDisplayName: 'Foo', + displayName: 'Foo', children: [{ - displayName: 'div', - ownerDisplayName: 'Bar', + displayName: 'Bar', + ownerDisplayName: 'Foo', children: [{ - displayName: 'h1', - ownerDisplayName: 'Foo', + displayName: 'div', + ownerDisplayName: 'Bar', children: [{ + displayName: 'h1', + ownerDisplayName: 'Foo', + children: [{ + displayName: '#text', + text: 'Hi.', + }], + }, { displayName: '#text', - text: 'Hi.', + text: ' Mom', }], - }, { - displayName: '#text', - text: ' Mom', }], }], }], - }], - }; - assertTreeMatches([element, tree]); - }); + }; + assertTreeMatches([element, tree]); + }); - it('purges unmounted components automatically', () => { - var node = document.createElement('div'); - var renderBar = true; - var fooInstance; - var barInstance; + it('purges unmounted components automatically', () => { + var node = document.createElement('div'); + var renderBar = true; + var fooInstance; + var barInstance; - class Foo extends React.Component { - render() { - fooInstance = ReactInstanceMap.get(this); - return renderBar ? : null; + class Foo extends React.Component { + render() { + fooInstance = ReactInstanceMap.get(this); + return renderBar ? : null; + } } - } - class Bar extends React.Component { - render() { - barInstance = ReactInstanceMap.get(this); - return null; + class Bar extends React.Component { + render() { + barInstance = ReactInstanceMap.get(this); + return null; + } } - } - - ReactDOM.render(, node); - ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { - displayName: 'Bar', - parentDisplayName: 'Foo', - parentID: fooInstance._debugID, - children: [], - }, 'Foo'); - - renderBar = false; - ReactDOM.render(, node); - ReactDOM.render(, node); - ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { - displayName: 'Unknown', - children: [], - parentID: null, - }, 'Foo'); - - ReactDOM.unmountComponentAtNode(node); - ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { - displayName: 'Unknown', - children: [], - parentID: null, - }, 'Foo'); - }); - - it('reports update counts', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var divID = ReactComponentTreeHook.getRootIDs()[0]; - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - - ReactDOM.render(, node); - var spanID = ReactComponentTreeHook.getRootIDs()[0]; - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - - ReactDOM.render(, node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1); - - ReactDOM.render(, node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - }); - - it('does not report top-level wrapper as a root', () => { - var node = document.createElement('div'); - - ReactDOM.render(
, node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); - - ReactDOM.render(
, node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); - expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); - }); + ReactDOM.render(, node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { + displayName: 'Bar', + parentDisplayName: 'Foo', + parentID: fooInstance._debugID, + children: [], + }, 'Foo'); - it('registers inlined text nodes', () => { - var node = document.createElement('div'); + renderBar = false; + ReactDOM.render(, node); + ReactDOM.render(, node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { + displayName: 'Unknown', + children: [], + parentID: null, + }, 'Foo'); - ReactDOM.render(
hi
, node); - expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual(['div', '#text']); + ReactDOM.unmountComponentAtNode(node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { + displayName: 'Unknown', + children: [], + parentID: null, + }, 'Foo'); + }); - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); - }); + it('reports update counts', () => { + var node = document.createElement('div'); - describe('stack addenda', () => { - it('gets created', () => { - function getAddendum(element) { - var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); - } + ReactDOM.render(
, node); + var divID = ReactComponentTreeHook.getRootIDs()[0]; + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - var Anon = React.createClass({displayName: null, render: () => null}); - var Orange = React.createClass({render: () => null}); + ReactDOM.render(, node); + var spanID = ReactComponentTreeHook.getRootIDs()[0]; + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - expectDev(getAddendum()).toBe( - '' - ); - expectDev(getAddendum(
)).toBe( - '\n in div (at **)' - ); - expectDev(getAddendum()).toBe( - '\n in Unknown (at **)' - ); - expectDev(getAddendum()).toBe( - '\n in Orange (at **)' - ); - expectDev(getAddendum(React.createElement(Orange))).toBe( - '\n in Orange' - ); + ReactDOM.render(, node); + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1); - var renders = 0; - var rOwnedByQ; + ReactDOM.render(, node); + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2); - function Q() { - return (rOwnedByQ = React.createElement(R)); - } - function R() { - return
; - } - class S extends React.Component { - componentDidMount() { - // Check that the parent path is still fetched when only S itself is on - // the stack. - this.forceUpdate(); - } - render() { - expectDev(getAddendum()).toBe( - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)' - ); - expectDev(getAddendum()).toBe( - '\n in span (at **)' + - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)' - ); - expectDev(getAddendum(React.createElement('span'))).toBe( - '\n in span (created by S)' + - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)' - ); - renders++; - return null; - } - } - ReactDOM.render(, document.createElement('div')); - expectDev(renders).toBe(2); - - // Make sure owner is fetched for the top element too. - expectDev(getAddendum(rOwnedByQ)).toBe( - '\n in R (created by Q)' - ); + ReactDOM.unmountComponentAtNode(node); + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); }); - it('can be retrieved by ID', () => { - function getAddendum(id) { - var addendum = ReactComponentTreeHook.getStackAddendumByID(id); - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); - } + it('does not report top-level wrapper as a root', () => { + var node = document.createElement('div'); - class Q extends React.Component { - render() { - return null; - } - } + ReactDOM.render(
, node); + expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); - var q = ReactDOM.render(, document.createElement('div')); - expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe( - '\n in Q (at **)' - ); + ReactDOM.render(
, node); + expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); - spyOn(console, 'error'); - getAddendum(-17); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactComponentTreeHook: Missing React element for ' + - 'debugID -17 when building stack' - ); + ReactDOM.unmountComponentAtNode(node); + expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); + expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); - it('is created during mounting', () => { - // https://github.com/facebook/react/issues/7187 - var el = document.createElement('div'); - var portalEl = document.createElement('div'); - class Foo extends React.Component { - componentWillMount() { - ReactDOM.render(
, portalEl); - } - render() { - return
; - } - } - ReactDOM.render(, el); - }); + it('registers inlined text nodes', () => { + var node = document.createElement('div'); - it('is created when calling renderToString during render', () => { - // https://github.com/facebook/react/issues/7190 - var el = document.createElement('div'); - class Foo extends React.Component { - render() { - return ( -
-
- {ReactDOMServer.renderToString(
)} -
-
- ); - } - } - ReactDOM.render(, el); + ReactDOM.render(
hi
, node); + expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual(['div', '#text']); + + ReactDOM.unmountComponentAtNode(node); + expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); }); - describe('in environment without Map, Set and Array.from', () => { + describeStack('in environment without Map, Set and Array.from', () => { var realMap; var realSet; var realArrayFrom; diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.native.js b/src/renderers/__tests__/ReactComponentTreeHook-test.native.js index 23c934c27498..17a8fa49868b 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.native.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.native.js @@ -11,7 +11,15 @@ 'use strict'; -describe('ReactComponentTreeHook', () => { +var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); +var describeStack = ReactNativeFeatureFlags.useFiber ? describe.skip : describe; + +// These tests are only relevant for the Stack version of the tree hook. +// This file is for RN. There is a sibling file that has some tree hook +// tests that are still relevant in Fiber. +// TODO: remove this file when we delete Stack. + +describeStack('ReactComponentTreeHook', () => { var React; var ReactNative; var ReactInstanceMap; diff --git a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js index b830342b758a..686d4ae8e400 100644 --- a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js +++ b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js @@ -11,7 +11,12 @@ 'use strict'; -describe('ReactHostOperationHistoryHook', () => { +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; + +// This is only used by ReactPerf which is currently not supported on Fiber. +// Use browser timeline integration instead. +describeStack('ReactHostOperationHistoryHook', () => { var React; var ReactPerf; var ReactDOM; diff --git a/src/renderers/__tests__/ReactPerf-test.js b/src/renderers/__tests__/ReactPerf-test.js index 7d5cb809c28c..2abc0d17dff7 100644 --- a/src/renderers/__tests__/ReactPerf-test.js +++ b/src/renderers/__tests__/ReactPerf-test.js @@ -11,7 +11,12 @@ 'use strict'; -describe('ReactPerf', () => { +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; + +// ReactPerf is currently not supported on Fiber. +// Use browser timeline integration instead. +describeStack('ReactPerf', () => { var React; var ReactDOM; var ReactPerf; From 0f763869ff27d358c886a846f6f26bf45c941988 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 02:50:17 +0000 Subject: [PATCH 32/37] Fix typo --- scripts/fiber/tests-passing.txt | 2 +- .../shared/fiber/__tests__/ReactIncrementalPerf-test.js | 2 +- .../__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index f42927d0b322..8bdd9f575792 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1535,7 +1535,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js * measures deferred work in chunks * recovers from fatal errors * recovers from caught errors -* deduplicate lifecycle names during commit to reduce overhead +* deduplicates lifecycle names during commit to reduce overhead * supports coroutines * supports portals diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js index 4377185a90fb..77144914363d 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js @@ -356,7 +356,7 @@ describe('ReactDebugFiberPerf', () => { expect(getFlameChart()).toMatchSnapshot(); }); - it('deduplicate lifecycle names during commit to reduce overhead', () => { + it('deduplicates lifecycle names during commit to reduce overhead', () => { class A extends React.Component { componentDidUpdate() {} render() { diff --git a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap index d737fd69364a..4f339904d5fd 100644 --- a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap +++ b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -32,7 +32,7 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` " `; -exports[`ReactDebugFiberPerf deduplicate lifecycle names during commit to reduce overhead 1`] = ` +exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = ` "// The commit phase should mention A and B just once ⚛ (React Tree Reconciliation) ⚛ Parent [update] From 2202fc03cd8c2589d1240c83ff21b83b34e43fb4 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 02:54:38 +0000 Subject: [PATCH 33/37] Fix lint and flow --- src/renderers/__tests__/ReactHostOperationHistoryHook-test.js | 2 -- src/renderers/shared/fiber/ReactDebugFiberPerf.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js index 686d4ae8e400..d6c12a6f4c77 100644 --- a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js +++ b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js @@ -21,7 +21,6 @@ describeStack('ReactHostOperationHistoryHook', () => { var ReactPerf; var ReactDOM; var ReactDOMComponentTree; - var ReactDOMFeatureFlags; var ReactHostOperationHistoryHook; beforeEach(() => { @@ -31,7 +30,6 @@ describeStack('ReactHostOperationHistoryHook', () => { ReactPerf = require('react-dom/lib/ReactPerf'); ReactDOM = require('react-dom'); ReactDOMComponentTree = require('ReactDOMComponentTree'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); ReactPerf.start(); diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 239f0e386708..d8343dde68d2 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -13,8 +13,6 @@ import type { Fiber } from 'ReactFiber'; type MeasurementPhase = - 'constructor' | - 'render' | 'componentWillMount' | 'componentWillUnmount' | 'componentWillReceiveProps' | From cc001d34370a414b813705a7343f7ba62121ef6d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 03:06:19 +0000 Subject: [PATCH 34/37] Don't treat cWM or cWRP as cascading --- .../shared/fiber/ReactDebugFiberPerf.js | 6 ++++- .../__tests__/ReactIncrementalPerf-test.js | 23 +++++++++++++++++++ .../ReactIncrementalPerf-test.js.snap | 21 +++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index d8343dde68d2..0c16eca8c08b 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -226,7 +226,11 @@ if (__DEV__) { if (isCommitting) { hasScheduledUpdateInCurrentCommit = true; } - if (currentPhase !== null) { + if ( + currentPhase !== null && + currentPhase !== 'componentWillMount' && + currentPhase !== 'componentWillReceiveProps' + ) { hasScheduledUpdateInCurrentPhase = true; } }, diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js index 77144914363d..8e3c1fa29653 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js @@ -222,6 +222,29 @@ describe('ReactDebugFiberPerf', () => { expect(getFlameChart()).toMatchSnapshot(); }); + it('does not treat setState from cWM or cWRP as cascading', () => { + class NotCascading extends React.Component { + componentWillMount() { + this.setState({}); + } + + componentWillReceiveProps() { + this.setState({}); + } + + render() { + return
{this.props.children}
; + } + } + ReactNoop.render(); + addComment('Should not print a warning'); + ReactNoop.flush(); + ReactNoop.render(); + addComment('Should not print a warning'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + it('captures all lifecycles', () => { class AllLifecycles extends React.Component { static childContextTypes = { diff --git a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap index 4f339904d5fd..4d5132388456 100644 --- a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap +++ b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -67,6 +67,27 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc " `; +exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = ` +"// Should not print a warning +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ NotCascading [mount] + ⚛ NotCascading.componentWillMount + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +// Should not print a warning +⚛ (React Tree Reconciliation) + ⚛ Parent [update] + ⚛ NotCascading [update] + ⚛ NotCascading.componentWillReceiveProps + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) +" +`; + exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` "// Mount ⚛ (React Tree Reconciliation) From d92381602cdd67b9d8a098e542f5a1b140879f74 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 03:08:04 +0000 Subject: [PATCH 35/37] Whitespace --- .../shared/fiber/__tests__/ReactIncrementalPerf-test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js index 8e3c1fa29653..98780e516c6e 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js @@ -192,11 +192,11 @@ describe('ReactDebugFiberPerf', () => { componentDidMount() { this.setState({}); } - render() { return
{this.props.children}
; } } + ReactNoop.render(); addComment('Should print a warning'); ReactNoop.flush(); @@ -210,7 +210,6 @@ describe('ReactDebugFiberPerf', () => { addComment('Scheduling another root from componentDidMount'); ReactNoop.flush(); } - render() { return
{this.props.children}
; } @@ -227,15 +226,14 @@ describe('ReactDebugFiberPerf', () => { componentWillMount() { this.setState({}); } - componentWillReceiveProps() { this.setState({}); } - render() { return
{this.props.children}
; } } + ReactNoop.render(); addComment('Should not print a warning'); ReactNoop.flush(); @@ -349,6 +347,7 @@ describe('ReactDebugFiberPerf', () => { function Baddie() { throw new Error('Game over'); } + function ErrorReport() { return
; } @@ -365,6 +364,7 @@ describe('ReactDebugFiberPerf', () => { return this.props.children; } } + ReactNoop.render( @@ -386,6 +386,7 @@ describe('ReactDebugFiberPerf', () => { return
; } } + class B extends React.Component { componentDidUpdate(prevProps) { if (this.props.cascade && !prevProps.cascade) { From 65e3b1e4d2670eddbc85bcbd63edbf0ee6d12256 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 10:22:23 +0000 Subject: [PATCH 36/37] Update tests --- scripts/fiber/tests-passing.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 8bdd9f575792..e6ffdc28ab98 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1530,6 +1530,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js * skips parents during setState * warns on cascading renders from setState * warns on cascading renders from top-level render +* does not treat setState from cWM or cWRP as cascading * captures all lifecycles * measures deprioritized work * measures deferred work in chunks From 21269e914fe2a2b9fafc8e9fe4d71de796f37c9f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 18:23:49 +0000 Subject: [PATCH 37/37] Gate callComponentWillUnmountWithTimerInDev definition by DEV --- src/renderers/shared/fiber/ReactFiberCommitWork.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 5647aea7fbfe..30a2f4962cb4 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -60,10 +60,12 @@ module.exports = function( getPublicInstance, } = config; - function callComponentWillUnmountWithTimerInDev(current, instance) { - startPhaseTimer(current, 'componentWillUnmount'); - instance.componentWillUnmount(); - stopPhaseTimer(); + if (__DEV__) { + var callComponentWillUnmountWithTimerInDev = function(current, instance) { + startPhaseTimer(current, 'componentWillUnmount'); + instance.componentWillUnmount(); + stopPhaseTimer(); + }; } // Capture errors so they don't interrupt unmounting.