From 5f8f1f6c1603e993ec9f7dab22274ff230ffc19a Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 28 Apr 2016 20:43:49 +0100 Subject: [PATCH 01/11] Delete the old ReactPerf --- src/renderers/dom/ReactDOM.js | 5 +- .../dom/client/ReactDOMIDOperations.js | 5 - src/renderers/dom/client/ReactMount.js | 6 - .../dom/client/utils/DOMChildrenOperations.js | 5 - .../dom/shared/CSSPropertyOperations.js | 5 - .../dom/shared/DOMPropertyOperations.js | 7 - .../ReactComponentBrowserEnvironment.js | 9 - src/renderers/dom/shared/ReactDOMComponent.js | 6 - .../dom/shared/ReactDOMTextComponent.js | 10 - .../dom/shared/ReactDefaultInjection.js | 9 - src/renderers/dom/shared/ReactInjection.js | 2 - .../native/ReactNativeDOMIDOperations.js | 20 +- src/renderers/native/ReactNativeMount.js | 30 +- .../reconciler/ReactCompositeComponent.js | 11 - .../shared/reconciler/ReactUpdates.js | 6 - src/test/ReactDefaultPerf.js | 361 ------------------ src/test/ReactDefaultPerfAnalysis.js | 214 ----------- src/test/ReactPerf.js | 100 ----- src/test/__tests__/ReactDefaultPerf-test.js | 299 --------------- 19 files changed, 15 insertions(+), 1095 deletions(-) delete mode 100644 src/test/ReactDefaultPerf.js delete mode 100644 src/test/ReactDefaultPerfAnalysis.js delete mode 100644 src/test/ReactPerf.js delete mode 100644 src/test/__tests__/ReactDefaultPerf-test.js diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index 75644b1209d..7e7fbe46397 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -16,7 +16,6 @@ var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDefaultInjection = require('ReactDefaultInjection'); var ReactMount = require('ReactMount'); -var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdates = require('ReactUpdates'); var ReactVersion = require('ReactVersion'); @@ -28,11 +27,9 @@ var warning = require('warning'); ReactDefaultInjection.inject(); -var render = ReactPerf.measure('React', 'render', ReactMount.render); - var React = { findDOMNode: findDOMNode, - render: render, + render: ReactMount.render, unmountComponentAtNode: ReactMount.unmountComponentAtNode, version: ReactVersion, diff --git a/src/renderers/dom/client/ReactDOMIDOperations.js b/src/renderers/dom/client/ReactDOMIDOperations.js index cb03d3f06ad..fd8c16797b1 100644 --- a/src/renderers/dom/client/ReactDOMIDOperations.js +++ b/src/renderers/dom/client/ReactDOMIDOperations.js @@ -13,7 +13,6 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactPerf = require('ReactPerf'); /** * Operations used to process updates to DOM nodes. @@ -32,8 +31,4 @@ var ReactDOMIDOperations = { }, }; -ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', { - dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates', -}); - module.exports = ReactDOMIDOperations; diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 564edc7e1a3..eebc607c131 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -22,7 +22,6 @@ var ReactElement = require('ReactElement'); var ReactFeatureFlags = require('ReactFeatureFlags'); var ReactInstrumentation = require('ReactInstrumentation'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); -var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); @@ -711,9 +710,4 @@ var ReactMount = { }, }; -ReactPerf.measureMethods(ReactMount, 'ReactMount', { - _renderNewRootComponent: '_renderNewRootComponent', - _mountImageIntoNode: '_mountImageIntoNode', -}); - module.exports = ReactMount; diff --git a/src/renderers/dom/client/utils/DOMChildrenOperations.js b/src/renderers/dom/client/utils/DOMChildrenOperations.js index 3072a8d5b5e..303cd94b305 100644 --- a/src/renderers/dom/client/utils/DOMChildrenOperations.js +++ b/src/renderers/dom/client/utils/DOMChildrenOperations.js @@ -16,7 +16,6 @@ var Danger = require('Danger'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactInstrumentation = require('ReactInstrumentation'); -var ReactPerf = require('ReactPerf'); var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); var setInnerHTML = require('setInnerHTML'); @@ -239,8 +238,4 @@ var DOMChildrenOperations = { }; -ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', { - replaceDelimitedText: 'replaceDelimitedText', -}); - module.exports = DOMChildrenOperations; diff --git a/src/renderers/dom/shared/CSSPropertyOperations.js b/src/renderers/dom/shared/CSSPropertyOperations.js index 721fb72a0f2..4a16d050e6a 100644 --- a/src/renderers/dom/shared/CSSPropertyOperations.js +++ b/src/renderers/dom/shared/CSSPropertyOperations.js @@ -14,7 +14,6 @@ var CSSProperty = require('CSSProperty'); var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactInstrumentation = require('ReactInstrumentation'); -var ReactPerf = require('ReactPerf'); var camelizeStyleName = require('camelizeStyleName'); var dangerousStyleValue = require('dangerousStyleValue'); @@ -238,8 +237,4 @@ var CSSPropertyOperations = { }; -ReactPerf.measureMethods(CSSPropertyOperations, 'CSSPropertyOperations', { - setValueForStyles: 'setValueForStyles', -}); - module.exports = CSSPropertyOperations; diff --git a/src/renderers/dom/shared/DOMPropertyOperations.js b/src/renderers/dom/shared/DOMPropertyOperations.js index 9c67c50cfbf..eee9647ef62 100644 --- a/src/renderers/dom/shared/DOMPropertyOperations.js +++ b/src/renderers/dom/shared/DOMPropertyOperations.js @@ -15,7 +15,6 @@ var DOMProperty = require('DOMProperty'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDOMInstrumentation = require('ReactDOMInstrumentation'); var ReactInstrumentation = require('ReactInstrumentation'); -var ReactPerf = require('ReactPerf'); var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser'); var warning = require('warning'); @@ -250,10 +249,4 @@ var DOMPropertyOperations = { }; -ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', { - setValueForProperty: 'setValueForProperty', - setValueForAttribute: 'setValueForAttribute', - deleteValueForProperty: 'deleteValueForProperty', -}); - module.exports = DOMPropertyOperations; diff --git a/src/renderers/dom/shared/ReactComponentBrowserEnvironment.js b/src/renderers/dom/shared/ReactComponentBrowserEnvironment.js index cf5df870d39..112c7d6241f 100644 --- a/src/renderers/dom/shared/ReactComponentBrowserEnvironment.js +++ b/src/renderers/dom/shared/ReactComponentBrowserEnvironment.js @@ -13,7 +13,6 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var ReactDOMIDOperations = require('ReactDOMIDOperations'); -var ReactPerf = require('ReactPerf'); /** * Abstracts away all functionality of the reconciler that requires knowledge of @@ -40,12 +39,4 @@ var ReactComponentBrowserEnvironment = { }; -ReactPerf.measureMethods( - ReactComponentBrowserEnvironment, - 'ReactComponentBrowserEnvironment', - { - replaceNodeWithMarkup: 'replaceNodeWithMarkup', - } -); - module.exports = ReactComponentBrowserEnvironment; diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 396371fb15c..b8fea43a024 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -34,7 +34,6 @@ var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChild = require('ReactMultiChild'); -var ReactPerf = require('ReactPerf'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); var emptyFunction = require('emptyFunction'); @@ -1129,11 +1128,6 @@ ReactDOMComponent.Mixin = { }; -ReactPerf.measureMethods(ReactDOMComponent.Mixin, 'ReactDOMComponent', { - mountComponent: 'mountComponent', - receiveComponent: 'receiveComponent', -}); - Object.assign( ReactDOMComponent.prototype, ReactDOMComponent.Mixin, diff --git a/src/renderers/dom/shared/ReactDOMTextComponent.js b/src/renderers/dom/shared/ReactDOMTextComponent.js index 97ed919d78e..a46c188930f 100644 --- a/src/renderers/dom/shared/ReactDOMTextComponent.js +++ b/src/renderers/dom/shared/ReactDOMTextComponent.js @@ -15,7 +15,6 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var DOMLazyTree = require('DOMLazyTree'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactInstrumentation = require('ReactInstrumentation'); -var ReactPerf = require('ReactPerf'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('invariant'); @@ -188,13 +187,4 @@ Object.assign(ReactDOMTextComponent.prototype, { }); -ReactPerf.measureMethods( - ReactDOMTextComponent.prototype, - 'ReactDOMTextComponent', - { - mountComponent: 'mountComponent', - receiveComponent: 'receiveComponent', - } -); - module.exports = ReactDOMTextComponent; diff --git a/src/renderers/dom/shared/ReactDefaultInjection.js b/src/renderers/dom/shared/ReactDefaultInjection.js index 09cbf25e485..f71a9b9dab5 100644 --- a/src/renderers/dom/shared/ReactDefaultInjection.js +++ b/src/renderers/dom/shared/ReactDefaultInjection.js @@ -15,7 +15,6 @@ var BeforeInputEventPlugin = require('BeforeInputEventPlugin'); var ChangeEventPlugin = require('ChangeEventPlugin'); var DefaultEventPluginOrder = require('DefaultEventPluginOrder'); var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin'); -var ExecutionEnvironment = require('ExecutionEnvironment'); var HTMLDOMPropertyConfig = require('HTMLDOMPropertyConfig'); var ReactComponentBrowserEnvironment = require('ReactComponentBrowserEnvironment'); @@ -91,14 +90,6 @@ function inject() { ); ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment); - - if (__DEV__) { - var url = (ExecutionEnvironment.canUseDOM && window.location.href) || ''; - if ((/[?&]react_perf\b/).test(url)) { - var ReactDefaultPerf = require('ReactDefaultPerf'); - ReactDefaultPerf.start(); - } - } } module.exports = { diff --git a/src/renderers/dom/shared/ReactInjection.js b/src/renderers/dom/shared/ReactInjection.js index 26085eb2f73..51fb065518a 100644 --- a/src/renderers/dom/shared/ReactInjection.js +++ b/src/renderers/dom/shared/ReactInjection.js @@ -19,7 +19,6 @@ var ReactClass = require('ReactClass'); var ReactEmptyComponent = require('ReactEmptyComponent'); var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); var ReactNativeComponent = require('ReactNativeComponent'); -var ReactPerf = require('ReactPerf'); var ReactUpdates = require('ReactUpdates'); var ReactInjection = { @@ -31,7 +30,6 @@ var ReactInjection = { EventPluginUtils: EventPluginUtils.injection, EventEmitter: ReactBrowserEventEmitter.injection, NativeComponent: ReactNativeComponent.injection, - Perf: ReactPerf.injection, Updates: ReactUpdates.injection, }; diff --git a/src/renderers/native/ReactNativeDOMIDOperations.js b/src/renderers/native/ReactNativeDOMIDOperations.js index 0bb92fef552..8d07d2d6abc 100644 --- a/src/renderers/native/ReactNativeDOMIDOperations.js +++ b/src/renderers/native/ReactNativeDOMIDOperations.js @@ -13,7 +13,6 @@ var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); -var ReactPerf = require('ReactPerf'); var UIManager = require('UIManager'); /** @@ -71,12 +70,7 @@ var dangerouslyProcessChildrenUpdates = function(inst, childrenUpdates) { * `ReactComponent.DOMIDOperations`. */ var ReactNativeDOMIDOperations = { - dangerouslyProcessChildrenUpdates: ReactPerf.measure( - // FIXME(frantic): #4441289 Hack to avoid modifying react-tools - 'ReactDOMIDOperations', - 'dangerouslyProcessChildrenUpdates', - dangerouslyProcessChildrenUpdates - ), + dangerouslyProcessChildrenUpdates, /** * Replaces a view that exists in the document with markup. @@ -84,14 +78,10 @@ var ReactNativeDOMIDOperations = { * @param {string} id ID of child to be replaced. * @param {string} markup Mount image to replace child with id. */ - dangerouslyReplaceNodeWithMarkupByID: ReactPerf.measure( - 'ReactDOMIDOperations', - 'dangerouslyReplaceNodeWithMarkupByID', - function(id, mountImage) { - var oldTag = id; - UIManager.replaceExistingNonRootView(oldTag, mountImage); - } - ), + dangerouslyReplaceNodeWithMarkupByID: function(id, mountImage) { + var oldTag = id; + UIManager.replaceExistingNonRootView(oldTag, mountImage); + }, }; module.exports = ReactNativeDOMIDOperations; diff --git a/src/renderers/native/ReactNativeMount.js b/src/renderers/native/ReactNativeMount.js index 6800b49cb9a..2e578b8fde4 100644 --- a/src/renderers/native/ReactNativeMount.js +++ b/src/renderers/native/ReactNativeMount.js @@ -15,7 +15,6 @@ var ReactElement = require('ReactElement'); var ReactInstrumentation = require('ReactInstrumentation'); var ReactNativeContainerInfo = require('ReactNativeContainerInfo'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); @@ -171,20 +170,15 @@ var ReactNativeMount = { * @param {View} view View tree image. * @param {number} containerViewID View to insert sub-view into. */ - _mountImageIntoNode: ReactPerf.measure( - // FIXME(frantic): #4441289 Hack to avoid modifying react-tools - 'ReactComponentBrowserEnvironment', - 'mountImageIntoNode', - function(mountImage, containerID) { - // Since we now know that the `mountImage` has been mounted, we can - // mark it as such. - var childTag = mountImage; - UIManager.setChildren( - containerID, - [childTag] - ); - } - ), + _mountImageIntoNode: function(mountImage, containerID) { + // Since we now know that the `mountImage` has been mounted, we can + // mark it as such. + var childTag = mountImage; + UIManager.setChildren( + containerID, + [childTag] + ); + }, /** * Standard unmounting of the component that is rendered into `containerID`, @@ -242,10 +236,4 @@ var ReactNativeMount = { }; -ReactNativeMount.renderComponent = ReactPerf.measure( - 'ReactMount', - '_renderNewRootComponent', - ReactNativeMount.renderComponent -); - module.exports = ReactNativeMount; diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index f1835938b96..be951bd56f1 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -18,7 +18,6 @@ var ReactErrorUtils = require('ReactErrorUtils'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactInstrumentation = require('ReactInstrumentation'); var ReactNodeTypes = require('ReactNodeTypes'); -var ReactPerf = require('ReactPerf'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var ReactReconciler = require('ReactReconciler'); @@ -1210,16 +1209,6 @@ var ReactCompositeComponentMixin = { }; -ReactPerf.measureMethods( - ReactCompositeComponentMixin, - 'ReactCompositeComponent', - { - mountComponent: 'mountComponent', - updateComponent: 'updateComponent', - _renderValidatedComponent: '_renderValidatedComponent', - } -); - var ReactCompositeComponent = { Mixin: ReactCompositeComponentMixin, diff --git a/src/renderers/shared/reconciler/ReactUpdates.js b/src/renderers/shared/reconciler/ReactUpdates.js index 225d7c50f89..d8471372b7f 100644 --- a/src/renderers/shared/reconciler/ReactUpdates.js +++ b/src/renderers/shared/reconciler/ReactUpdates.js @@ -15,7 +15,6 @@ var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); var ReactFeatureFlags = require('ReactFeatureFlags'); var ReactInstrumentation = require('ReactInstrumentation'); -var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var Transaction = require('Transaction'); @@ -222,11 +221,6 @@ var flushBatchedUpdates = function() { ReactInstrumentation.debugTool.onEndFlush(); } }; -flushBatchedUpdates = ReactPerf.measure( - 'ReactUpdates', - 'flushBatchedUpdates', - flushBatchedUpdates -); /** * Mark a component as needing a rerender, adding an optional callback to a diff --git a/src/test/ReactDefaultPerf.js b/src/test/ReactDefaultPerf.js deleted file mode 100644 index 5cb43e9048e..00000000000 --- a/src/test/ReactDefaultPerf.js +++ /dev/null @@ -1,361 +0,0 @@ -/** - * 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 ReactDefaultPerf - */ - -'use strict'; - -var DOMProperty = require('DOMProperty'); -var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactDefaultPerfAnalysis = require('ReactDefaultPerfAnalysis'); -var ReactMount = require('ReactMount'); -var ReactPerf = require('ReactPerf'); - -var performanceNow = require('performanceNow'); -var warning = require('warning'); - -function roundFloat(val) { - return Math.floor(val * 100) / 100; -} - -function addValue(obj, key, val) { - obj[key] = (obj[key] || 0) + val; -} - -// Composite/text components don't have any built-in ID: we have to make our own -var compositeIDMap; -var compositeIDCounter = 17000; -function getIDOfComposite(inst) { - if (!compositeIDMap) { - compositeIDMap = new WeakMap(); - } - if (compositeIDMap.has(inst)) { - return compositeIDMap.get(inst); - } else { - var id = compositeIDCounter++; - compositeIDMap.set(inst, id); - return id; - } -} - -function getID(inst) { - if (inst.hasOwnProperty('_rootNodeID')) { - return inst._rootNodeID; - } else { - return getIDOfComposite(inst); - } -} - -function stripComplexValues(key, value) { - if (typeof value !== 'object' || Array.isArray(value) || value == null) { - return value; - } - var prototype = Object.getPrototypeOf(value); - if (!prototype || prototype === Object.prototype) { - return value; - } - return ''; -} - -// This implementation of ReactPerf is going away some time mid 15.x. -// While we plan to keep most of the API, the actual format of measurements -// will change dramatically. To signal this, we wrap them into an opaque-ish -// object to discourage reaching into it until the API stabilizes. -function wrapLegacyMeasurements(measurements) { - return { __unstable_this_format_will_change: measurements }; -} -function unwrapLegacyMeasurements(measurements) { - return measurements && measurements.__unstable_this_format_will_change || measurements; -} - -var warnedAboutPrintDOM = false; -var warnedAboutGetMeasurementsSummaryMap = false; - -var ReactDefaultPerf = { - _allMeasurements: [], // last item in the list is the current one - _mountStack: [0], - _compositeStack: [], - _injected: false, - - start: function() { - if (!ReactDefaultPerf._injected) { - ReactPerf.injection.injectMeasure(ReactDefaultPerf.measure); - } - - ReactDefaultPerf._allMeasurements.length = 0; - ReactPerf.enableMeasure = true; - }, - - stop: function() { - ReactPerf.enableMeasure = false; - }, - - getLastMeasurements: function() { - return wrapLegacyMeasurements(ReactDefaultPerf._allMeasurements); - }, - - printExclusive: function(measurements) { - measurements = unwrapLegacyMeasurements(measurements || ReactDefaultPerf._allMeasurements); - var summary = ReactDefaultPerfAnalysis.getExclusiveSummary(measurements); - console.table(summary.map(function(item) { - return { - 'Component class name': item.componentName, - 'Total inclusive time (ms)': roundFloat(item.inclusive), - 'Exclusive mount time (ms)': roundFloat(item.exclusive), - 'Exclusive render time (ms)': roundFloat(item.render), - 'Mount time per instance (ms)': roundFloat(item.exclusive / item.count), - 'Render time per instance (ms)': roundFloat(item.render / item.count), - 'Instances': item.count, - }; - })); - // TODO: ReactDefaultPerfAnalysis.getTotalTime() does not return the correct - // number. - }, - - printInclusive: function(measurements) { - measurements = unwrapLegacyMeasurements(measurements || ReactDefaultPerf._allMeasurements); - var summary = ReactDefaultPerfAnalysis.getInclusiveSummary(measurements); - console.table(summary.map(function(item) { - return { - 'Owner > component': item.componentName, - 'Inclusive time (ms)': roundFloat(item.time), - 'Instances': item.count, - }; - })); - console.log( - 'Total time:', - ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms' - ); - }, - - getMeasurementsSummaryMap: function(measurements) { - warning( - warnedAboutGetMeasurementsSummaryMap, - '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + - '`ReactPerf.getWasted(...)` instead.' - ); - warnedAboutGetMeasurementsSummaryMap = true; - return ReactDefaultPerf.getWasted(measurements); - }, - - getWasted: function(measurements) { - measurements = unwrapLegacyMeasurements(measurements); - var summary = ReactDefaultPerfAnalysis.getInclusiveSummary( - measurements, - true - ); - return summary.map(function(item) { - return { - 'Owner > component': item.componentName, - 'Wasted time (ms)': item.time, - 'Instances': item.count, - }; - }); - }, - - printWasted: function(measurements) { - measurements = unwrapLegacyMeasurements(measurements || ReactDefaultPerf._allMeasurements); - console.table(ReactDefaultPerf.getWasted(measurements)); - console.log( - 'Total time:', - ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms' - ); - }, - - printDOM: function(measurements) { - warning( - warnedAboutPrintDOM, - '`ReactPerf.printDOM(...)` is deprecated. Use ' + - '`ReactPerf.printOperations(...)` instead.' - ); - warnedAboutPrintDOM = true; - return ReactDefaultPerf.printOperations(measurements); - }, - - printOperations: function(measurements) { - measurements = unwrapLegacyMeasurements(measurements || ReactDefaultPerf._allMeasurements); - var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements); - console.table(summary.map(function(item) { - var result = {}; - result[DOMProperty.ID_ATTRIBUTE_NAME] = item.id; - result.type = item.type; - result.args = JSON.stringify(item.args, stripComplexValues); - return result; - })); - console.log( - 'Total time:', - ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms' - ); - }, - - _recordWrite: function(id, fnName, totalTime, args) { - // TODO: totalTime isn't that useful since it doesn't count paints/reflows - var entry = - ReactDefaultPerf - ._allMeasurements[ReactDefaultPerf._allMeasurements.length - 1]; - var writes = entry.writes; - writes[id] = writes[id] || []; - writes[id].push({ - type: fnName, - time: totalTime, - args: args, - }); - }, - - measure: function(moduleName, fnName, func) { - return function(...args) { - var totalTime; - var rv; - var start; - - var entry = ReactDefaultPerf._allMeasurements[ - ReactDefaultPerf._allMeasurements.length - 1 - ]; - - if (fnName === '_renderNewRootComponent' || - fnName === 'flushBatchedUpdates') { - // A "measurement" is a set of metrics recorded for each flush. We want - // to group the metrics for a given flush together so we can look at the - // components that rendered and the DOM operations that actually - // happened to determine the amount of "wasted work" performed. - ReactDefaultPerf._allMeasurements.push(entry = { - exclusive: {}, - inclusive: {}, - render: {}, - counts: {}, - writes: {}, - displayNames: {}, - hierarchy: {}, - totalTime: 0, - created: {}, - }); - start = performanceNow(); - rv = func.apply(this, args); - entry.totalTime = performanceNow() - start; - return rv; - } else if (fnName === '_mountImageIntoNode' || - moduleName === 'ReactDOMIDOperations' || - moduleName === 'CSSPropertyOperations' || - moduleName === 'DOMChildrenOperations' || - moduleName === 'DOMPropertyOperations' || - moduleName === 'ReactComponentBrowserEnvironment') { - start = performanceNow(); - rv = func.apply(this, args); - totalTime = performanceNow() - start; - - if (fnName === '_mountImageIntoNode') { - ReactDefaultPerf._recordWrite('', fnName, totalTime, args[0]); - } else if (fnName === 'dangerouslyProcessChildrenUpdates') { - // special format - args[1].forEach(function(update) { - var writeArgs = {}; - if (update.fromIndex !== null) { - writeArgs.fromIndex = update.fromIndex; - } - if (update.toIndex !== null) { - writeArgs.toIndex = update.toIndex; - } - if (update.content !== null) { - writeArgs.content = update.content; - } - ReactDefaultPerf._recordWrite( - args[0]._rootNodeID, - update.type, - totalTime, - writeArgs - ); - }); - } else { - // basic format - var id = args[0]; - if (moduleName === 'EventPluginHub') { - id = id._rootNodeID; - } else if (fnName === 'replaceNodeWithMarkup') { - // Old node is already unmounted; can't get its instance - id = ReactDOMComponentTree.getInstanceFromNode(args[1].node)._rootNodeID; - } else if (fnName === 'replaceDelimitedText') { - id = getID(ReactDOMComponentTree.getInstanceFromNode(args[0])); - } else if (typeof id === 'object') { - id = getID(ReactDOMComponentTree.getInstanceFromNode(args[0])); - } - ReactDefaultPerf._recordWrite( - id, - fnName, - totalTime, - Array.prototype.slice.call(args, 1) - ); - } - return rv; - } else if (moduleName === 'ReactCompositeComponent' && ( - fnName === 'mountComponent' || - fnName === 'updateComponent' || // TODO: receiveComponent()? - fnName === '_renderValidatedComponent')) { - - if (this._currentElement.type === ReactMount.TopLevelWrapper) { - return func.apply(this, args); - } - - var rootNodeID = getIDOfComposite(this); - var isRender = fnName === '_renderValidatedComponent'; - var isMount = fnName === 'mountComponent'; - - var mountStack = ReactDefaultPerf._mountStack; - - if (isRender) { - addValue(entry.counts, rootNodeID, 1); - } else if (isMount) { - entry.created[rootNodeID] = true; - mountStack.push(0); - } - - ReactDefaultPerf._compositeStack.push(rootNodeID); - - start = performanceNow(); - rv = func.apply(this, args); - totalTime = performanceNow() - start; - - ReactDefaultPerf._compositeStack.pop(); - - if (isRender) { - addValue(entry.render, rootNodeID, totalTime); - } else if (isMount) { - var subMountTime = mountStack.pop(); - mountStack[mountStack.length - 1] += totalTime; - addValue(entry.exclusive, rootNodeID, totalTime - subMountTime); - addValue(entry.inclusive, rootNodeID, totalTime); - } else { - addValue(entry.inclusive, rootNodeID, totalTime); - } - - entry.displayNames[rootNodeID] = { - current: this.getName(), - owner: this._currentElement._owner ? - this._currentElement._owner.getName() : - '', - }; - - return rv; - } else if ( - (moduleName === 'ReactDOMComponent' || - moduleName === 'ReactDOMTextComponent') && - (fnName === 'mountComponent' || - fnName === 'receiveComponent')) { - - rv = func.apply(this, args); - entry.hierarchy[getID(this)] = - ReactDefaultPerf._compositeStack.slice(); - return rv; - } else { - return func.apply(this, args); - } - }; - }, -}; - -module.exports = ReactDefaultPerf; diff --git a/src/test/ReactDefaultPerfAnalysis.js b/src/test/ReactDefaultPerfAnalysis.js deleted file mode 100644 index bb5a35e2d0e..00000000000 --- a/src/test/ReactDefaultPerfAnalysis.js +++ /dev/null @@ -1,214 +0,0 @@ -/** - * 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 ReactDefaultPerfAnalysis - */ - -'use strict'; - - -// Don't try to save users less than 1.2ms (a number I made up) -var DONT_CARE_THRESHOLD = 1.2; -var DOM_OPERATION_TYPES = { - '_mountImageIntoNode': 'set innerHTML', - INSERT_MARKUP: 'set innerHTML', - MOVE_EXISTING: 'move', - REMOVE_NODE: 'remove', - SET_MARKUP: 'set innerHTML', - TEXT_CONTENT: 'set textContent', - 'setValueForProperty': 'update attribute', - 'setValueForAttribute': 'update attribute', - 'deleteValueForProperty': 'remove attribute', - 'setValueForStyles': 'update styles', - 'replaceNodeWithMarkup': 'replace', - 'replaceDelimitedText': 'replace', -}; - -function getTotalTime(measurements) { - // TODO: return number of DOM ops? could be misleading. - // TODO: measure dropped frames after reconcile? - // TODO: log total time of each reconcile and the top-level component - // class that triggered it. - var totalTime = 0; - for (var i = 0; i < measurements.length; i++) { - var measurement = measurements[i]; - totalTime += measurement.totalTime; - } - return totalTime; -} - -function getDOMSummary(measurements) { - var items = []; - measurements.forEach(function(measurement) { - Object.keys(measurement.writes).forEach(function(id) { - measurement.writes[id].forEach(function(write) { - items.push({ - id: id, - type: DOM_OPERATION_TYPES[write.type] || write.type, - args: write.args, - }); - }); - }); - }); - return items; -} - -function getExclusiveSummary(measurements) { - var candidates = {}; - var displayName; - - for (var i = 0; i < measurements.length; i++) { - var measurement = measurements[i]; - var allIDs = Object.assign( - {}, - measurement.exclusive, - measurement.inclusive - ); - - for (var id in allIDs) { - displayName = measurement.displayNames[id].current; - - candidates[displayName] = candidates[displayName] || { - componentName: displayName, - inclusive: 0, - exclusive: 0, - render: 0, - count: 0, - }; - if (measurement.render[id]) { - candidates[displayName].render += measurement.render[id]; - } - if (measurement.exclusive[id]) { - candidates[displayName].exclusive += measurement.exclusive[id]; - } - if (measurement.inclusive[id]) { - candidates[displayName].inclusive += measurement.inclusive[id]; - } - if (measurement.counts[id]) { - candidates[displayName].count += measurement.counts[id]; - } - } - } - - // Now make a sorted array with the results. - var arr = []; - for (displayName in candidates) { - if (candidates[displayName].exclusive >= DONT_CARE_THRESHOLD) { - arr.push(candidates[displayName]); - } - } - - arr.sort(function(a, b) { - return b.exclusive - a.exclusive; - }); - - return arr; -} - -function getInclusiveSummary(measurements, onlyClean) { - var candidates = {}; - var inclusiveKey; - - for (var i = 0; i < measurements.length; i++) { - var measurement = measurements[i]; - var allIDs = Object.assign( - {}, - measurement.exclusive, - measurement.inclusive - ); - var cleanComponents; - - if (onlyClean) { - cleanComponents = getUnchangedComponents(measurement); - } - - for (var id in allIDs) { - if (onlyClean && !cleanComponents[id]) { - continue; - } - - var displayName = measurement.displayNames[id]; - - // Inclusive time is not useful for many components without knowing where - // they are instantiated. So we aggregate inclusive time with both the - // owner and current displayName as the key. - inclusiveKey = displayName.owner + ' > ' + displayName.current; - - candidates[inclusiveKey] = candidates[inclusiveKey] || { - componentName: inclusiveKey, - time: 0, - count: 0, - }; - - if (measurement.inclusive[id]) { - candidates[inclusiveKey].time += measurement.inclusive[id]; - } - if (measurement.counts[id]) { - candidates[inclusiveKey].count += measurement.counts[id]; - } - } - } - - // Now make a sorted array with the results. - var arr = []; - for (inclusiveKey in candidates) { - if (candidates[inclusiveKey].time >= DONT_CARE_THRESHOLD) { - arr.push(candidates[inclusiveKey]); - } - } - - arr.sort(function(a, b) { - return b.time - a.time; - }); - - return arr; -} - -function getUnchangedComponents(measurement) { - // For a given reconcile, look at which components did not actually - // render anything to the DOM and return a mapping of their ID to - // the amount of time it took to render the entire subtree. - var cleanComponents = {}; - var writes = measurement.writes; - var hierarchy = measurement.hierarchy; - var dirtyComposites = {}; - Object.keys(writes).forEach(function(id) { - writes[id].forEach(function(write) { - // Root mounting (innerHTML set) is recorded with an ID of '' - if (id !== '' && hierarchy.hasOwnProperty(id)) { - hierarchy[id].forEach((c) => dirtyComposites[c] = true); - } - }); - }); - var allIDs = Object.assign({}, measurement.exclusive, measurement.inclusive); - - for (var id in allIDs) { - var isDirty = false; - // See if any of the DOM operations applied to this component's subtree. - if (dirtyComposites[id]) { - isDirty = true; - } - // check if component newly created - if (measurement.created[id]) { - isDirty = true; - } - if (!isDirty && measurement.counts[id] > 0) { - cleanComponents[id] = true; - } - } - return cleanComponents; -} - -var ReactDefaultPerfAnalysis = { - getExclusiveSummary: getExclusiveSummary, - getInclusiveSummary: getInclusiveSummary, - getDOMSummary: getDOMSummary, - getTotalTime: getTotalTime, -}; - -module.exports = ReactDefaultPerfAnalysis; diff --git a/src/test/ReactPerf.js b/src/test/ReactPerf.js deleted file mode 100644 index a8e6679311b..00000000000 --- a/src/test/ReactPerf.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * 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 ReactPerf - */ - -'use strict'; - -/** - * ReactPerf is a general AOP system designed to measure performance. This - * module only has the hooks: see ReactDefaultPerf for the analysis tool. - * See also https://facebook.github.io/react/docs/perf.html - */ -var ReactPerf = { - /** - * Boolean to enable/disable measurement. Set to false by default to prevent - * accidental logging and perf loss. - */ - enableMeasure: false, - - /** - * Holds onto the measure function in use. By default, don't measure - * anything, but we'll override this if we inject a measure function. - */ - storedMeasure: _noMeasure, - - /** - * @param {object} object - * @param {string} objectName - * @param {object} methodNames - */ - measureMethods: function(object, objectName, methodNames) { - if (__DEV__) { - for (var key in methodNames) { - if (!methodNames.hasOwnProperty(key)) { - continue; - } - object[key] = ReactPerf.measure( - objectName, - methodNames[key], - object[key] - ); - } - } - }, - - /** - * Use this to wrap methods you want to measure. Zero overhead in production. - * - * @param {string} objName - * @param {string} fnName - * @param {function} func - * @return {function} - */ - measure: function(objName, fnName, func) { - if (__DEV__) { - var measuredFunc = null; - var wrapper = function() { - if (ReactPerf.enableMeasure) { - if (!measuredFunc) { - measuredFunc = ReactPerf.storedMeasure(objName, fnName, func); - } - return measuredFunc.apply(this, arguments); - } - return func.apply(this, arguments); - }; - wrapper.displayName = objName + '_' + fnName; - return wrapper; - } - return func; - }, - - injection: { - /** - * @param {function} measure - */ - injectMeasure: function(measure) { - ReactPerf.storedMeasure = measure; - }, - }, -}; - -/** - * Simply passes through the measured function, without measuring it. - * - * @param {string} objName - * @param {string} fnName - * @param {function} func - * @return {function} - */ -function _noMeasure(objName, fnName, func) { - return func; -} - -module.exports = ReactPerf; diff --git a/src/test/__tests__/ReactDefaultPerf-test.js b/src/test/__tests__/ReactDefaultPerf-test.js deleted file mode 100644 index 8019f0fe8e5..00000000000 --- a/src/test/__tests__/ReactDefaultPerf-test.js +++ /dev/null @@ -1,299 +0,0 @@ -/** - * 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. - * - * @emails react-core - */ - -'use strict'; - -describe('ReactDefaultPerf', function() { - var React; - var ReactDOM; - var ReactDOMFeatureFlags; - var ReactDefaultPerf; - var ReactTestUtils; - var ReactDefaultPerfAnalysis; - - var App; - var Box; - var Div; - - beforeEach(function() { - var now = 0; - jest.setMock('fbjs/lib/performanceNow', function() { - return now++; - }); - - if (typeof console.table !== 'function') { - console.table = () => {}; - console.table.isFake = true; - } - - React = require('React'); - ReactDOM = require('ReactDOM'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - ReactDefaultPerf = require('ReactDefaultPerf'); - ReactTestUtils = require('ReactTestUtils'); - ReactDefaultPerfAnalysis = require('ReactDefaultPerfAnalysis'); - - App = React.createClass({ - render: function() { - return
; - }, - }); - - Box = React.createClass({ - render: function() { - return
; - }, - }); - - // ReactPerf only measures composites, so we put everything in one. - Div = React.createClass({ - render: function() { - return
; - }, - }); - }); - - afterEach(function() { - if (console.table.isFake) { - delete console.table; - } - }); - - function measure(fn) { - ReactDefaultPerf.start(); - fn(); - ReactDefaultPerf.stop(); - return ReactDefaultPerf.getLastMeasurements().__unstable_this_format_will_change; - } - - it('should count no-op update as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(, container); - var measurements = measure(() => { - ReactDOM.render(, container); - }); - - var summary = ReactDefaultPerf.getWasted(measurements); - expect(summary.length).toBe(2); - - /*eslint-disable dot-notation */ - - expect(summary[0]['Owner > component']).toBe(' > App'); - expect(summary[0]['Wasted time (ms)']).not.toBe(0); - expect(summary[0]['Instances']).toBe(1); - - expect(summary[1]['Owner > component']).toBe('App > Box'); - expect(summary[1]['Wasted time (ms)']).not.toBe(0); - expect(summary[1]['Instances']).toBe(2); - - /*eslint-enable dot-notation */ - }); - - it('should count no-op update in child as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(, container); - - // Here, we add a Box -- two of the updates are wasted time (but the - // addition of the third is not) - var measurements = measure(() => { - ReactDOM.render(, container); - }); - - var summary = ReactDefaultPerf.getWasted(measurements); - expect(summary.length).toBe(1); - - /*eslint-disable dot-notation */ - - expect(summary[0]['Owner > component']).toBe('App > Box'); - expect(summary[0]['Wasted time (ms)']).not.toBe(0); - expect(summary[0]['Instances']).toBe(1); - - /*eslint-enable dot-notation */ - }); - - function expectNoWaste(fn) { - var measurements = measure(fn); - var summary = ReactDefaultPerf.getWasted(measurements); - expect(summary).toEqual([]); - } - - it('should not count initial render as waste', function() { - expectNoWaste(() => { - ReactTestUtils.renderIntoDocument(); - }); - }); - - it('should not count unmount as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
hello
, container); - expectNoWaste(() => { - ReactDOM.unmountComponentAtNode(container); - }); - }); - - it('should not count content update as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
hello
, container); - expectNoWaste(() => { - ReactDOM.render(
hello world
, container); - }); - }); - - it('should not count child addition as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
, container); - expectNoWaste(() => { - ReactDOM.render(
, container); - }); - }); - - it('should not count child removal as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
, container); - expectNoWaste(() => { - ReactDOM.render(
, container); - }); - }); - - it('should not count property update as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - expectNoWaste(() => { - ReactDOM.render(
hey
, container); - }); - }); - - it('should not count style update as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - expectNoWaste(() => { - ReactDOM.render(
hey
, container); - }); - }); - - it('should not count property removal as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - expectNoWaste(() => { - ReactDOM.render(
hey
, container); - }); - }); - - it('should not count raw HTML update as waste', function() { - var container = document.createElement('div'); - ReactDOM.render( -
, - container - ); - expectNoWaste(() => { - ReactDOM.render( -
, - container - ); - }); - }); - - it('should not count child reordering as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
, container); - expectNoWaste(() => { - ReactDOM.render(
, container); - }); - }); - - it('should not count text update as waste', function() { - var container = document.createElement('div'); - ReactDOM.render(
{'hello'}{'world'}
, container); - expectNoWaste(() => { - ReactDOM.render(
{'hello'}{'friend'}
, container); - }); - }); - - it('putListener should not be instrumented', function() { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - var measurements = measure(() => { - ReactDOM.render(
hey
, container); - }); - - var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements); - expect(summary).toEqual([]); - }); - - it('deleteListener should not be instrumented', function() { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - var measurements = measure(() => { - ReactDOM.render(
hey
, container); - }); - - var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements); - expect(summary).toEqual([]); - }); - - it('should not fail on input change events', function() { - var container = document.createElement('div'); - var onChange = () => {}; - var input = ReactDOM.render( - , - container - ); - expectNoWaste(() => { - ReactTestUtils.Simulate.change(input); - }); - }); - - it('should print a table after calling printOperations', function() { - var container = document.createElement('div'); - var measurements = measure(() => { - ReactDOM.render(
hey
, container); - }); - spyOn(console, 'table'); - ReactDefaultPerf.printOperations(measurements); - expect(console.table.calls.length).toBe(1); - expect(console.table.argsForCall[0][0]).toEqual([{ - 'data-reactid': '', - type: 'set innerHTML', - args: ReactDOMFeatureFlags.useCreateElement ? - '{"node":"","children":[],"html":null,"text":null}' : - '"
hey
"', - }]); - }); - - it('warns once when using getMeasurementsSummaryMap', function() { - var measurements = measure(() => {}); - spyOn(console, 'error'); - ReactDefaultPerf.getMeasurementsSummaryMap(measurements); - expect(console.error.calls.length).toBe(1); - expect(console.error.argsForCall[0][0]).toContain( - '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + - '`ReactPerf.getWasted(...)` instead.' - ); - - ReactDefaultPerf.getMeasurementsSummaryMap(measurements); - expect(console.error.calls.length).toBe(1); - }); - - it('warns once when using printDOM', function() { - var measurements = measure(() => {}); - spyOn(console, 'error'); - ReactDefaultPerf.printDOM(measurements); - expect(console.error.calls.length).toBe(1); - expect(console.error.argsForCall[0][0]).toContain( - '`ReactPerf.printDOM(...)` is deprecated. Use ' + - '`ReactPerf.printOperations(...)` instead.' - ); - - ReactDefaultPerf.printDOM(measurements); - expect(console.error.calls.length).toBe(1); - }); - -}); From 411fc9ca7d9c2660f4755bf54ad5c9ec316b56d2 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 15:18:04 +0100 Subject: [PATCH 02/11] Rename the new ReactPerfAnalysis to ReactPerf --- grunt/tasks/npm-react-addons.js | 2 +- src/addons/ReactWithAddons.js | 2 +- src/isomorphic/{ReactPerfAnalysis.js => ReactPerf.js} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/isomorphic/{ReactPerfAnalysis.js => ReactPerf.js} (99%) diff --git a/grunt/tasks/npm-react-addons.js b/grunt/tasks/npm-react-addons.js index 1cb2cf1c304..0f3d088d9cc 100644 --- a/grunt/tasks/npm-react-addons.js +++ b/grunt/tasks/npm-react-addons.js @@ -16,7 +16,7 @@ var addons = { docs: 'two-way-binding-helpers', }, Perf: { - module: 'ReactPerfAnalysis', + module: 'ReactPerf', name: 'perf', docs: 'perf', }, diff --git a/src/addons/ReactWithAddons.js b/src/addons/ReactWithAddons.js index 89e4e844824..94102c5c2e8 100644 --- a/src/addons/ReactWithAddons.js +++ b/src/addons/ReactWithAddons.js @@ -34,7 +34,7 @@ React.addons = { }; if (__DEV__) { - React.addons.Perf = require('ReactPerfAnalysis'); + React.addons.Perf = require('ReactPerf'); React.addons.TestUtils = require('ReactTestUtils'); } diff --git a/src/isomorphic/ReactPerfAnalysis.js b/src/isomorphic/ReactPerf.js similarity index 99% rename from src/isomorphic/ReactPerfAnalysis.js rename to src/isomorphic/ReactPerf.js index f32d6a9b4d4..7c93a02490f 100644 --- a/src/isomorphic/ReactPerfAnalysis.js +++ b/src/isomorphic/ReactPerf.js @@ -6,7 +6,7 @@ * 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 ReactPerfAnalysis + * @providesModule ReactPerf */ 'use strict'; From f22e54a947a08dcd3cc45d969afa7fcfccf279f8 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 20:52:42 +0100 Subject: [PATCH 03/11] Add getLastMeasurements() as it is documented as public API --- src/isomorphic/ReactPerf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isomorphic/ReactPerf.js b/src/isomorphic/ReactPerf.js index 7c93a02490f..596ca6e900d 100644 --- a/src/isomorphic/ReactPerf.js +++ b/src/isomorphic/ReactPerf.js @@ -342,7 +342,7 @@ function stop() { } var ReactPerfAnalysis = { - getFlushHistory, + getLastMeasurements: getFlushHistory, getExclusive, getInclusive, getWasted, From 49a1542c9f82b2934bd96374b222bcd86ab9d044 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 20:53:17 +0100 Subject: [PATCH 04/11] Emit flush events on React Native for ReactPerf --- src/renderers/native/ReactNativeMount.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderers/native/ReactNativeMount.js b/src/renderers/native/ReactNativeMount.js index 2e578b8fde4..cb4880e0d51 100644 --- a/src/renderers/native/ReactNativeMount.js +++ b/src/renderers/native/ReactNativeMount.js @@ -142,6 +142,10 @@ var ReactNativeMount = { // Mute future events from the top level wrapper. // It is an implementation detail that devtools should not know about. instance._debugID = 0; + + if (__DEV__) { + ReactInstrumentation.debugTool.onBeginFlush(); + } } // The initial render is synchronous but any updates that happen during @@ -158,6 +162,7 @@ var ReactNativeMount = { ReactInstrumentation.debugTool.onMountRootComponent( instance._renderedComponent._debugID ); + ReactInstrumentation.debugTool.onEndFlush(); } var component = instance.getPublicInstance(); if (callback) { From 103ca4b406a3c008cca99a17b33bdf83344e7ffe Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 20:55:51 +0100 Subject: [PATCH 05/11] Use performanceNow() instead of performance.now() --- src/isomorphic/ReactDebugTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 5d166bca7f0..ce16ad40969 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -175,7 +175,7 @@ var ReactDebugTool = { currentFlushMeasurements.push({ timerType, instanceID: debugID, - duration: performance.now() - currentTimerStartTime, + duration: performanceNow() - currentTimerStartTime, }); currentTimerStartTime = null; currentTimerDebugID = null; From 8b9b79eb6b90e42a1b9e3cf2f05100dc4f6e4db5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 21:02:56 +0100 Subject: [PATCH 06/11] Fix incorrect onBegin/onEnd timer pair --- src/renderers/shared/reconciler/ReactCompositeComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index be951bd56f1..120e5900a45 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -394,7 +394,7 @@ var ReactCompositeComponentMixin = { instanceOrElement = Component(publicProps, publicContext, ReactUpdateQueue); if (__DEV__) { if (this._debugID !== 0) { - ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + ReactInstrumentation.debugTool.onEndLifeCycleTimer( this._debugID, 'render' ); From abe9a0ce94145d286c251ac66c7e17ea2ae879ad Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 21:14:21 +0100 Subject: [PATCH 07/11] Ignore lifecycle events outside batches Technically this shouldn't happen but it seems possible with ReactNativeMount.unmountComponentAtNode(). For now, let's just ignore these lifecycle events because ReactPerf makes a hard assumption that all lifecycle hooks happen inside batches. We can revisit later when we have a comprehensive test suite for ReactPerf itself. --- src/isomorphic/ReactDebugTool.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index ce16ad40969..a76668b12c3 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -144,7 +144,7 @@ var ReactDebugTool = { onBeginLifeCycleTimer(debugID, timerType) { emitEvent('onBeginLifeCycleTimer', debugID, timerType); if (__DEV__) { - if (isProfiling) { + if (isProfiling && currentFlushNesting > 0) { warning( !currentTimerType, 'There is an internal error in the React performance measurement code. ' + @@ -162,7 +162,7 @@ var ReactDebugTool = { }, onEndLifeCycleTimer(debugID, timerType) { if (__DEV__) { - if (isProfiling) { + if (isProfiling && currentFlushNesting > 0) { warning( currentTimerType === timerType, 'There is an internal error in the React performance measurement code. ' + From e187affcafc39dd1184e892db02d71e57d020a47 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 21:30:17 +0100 Subject: [PATCH 08/11] Make stats less noisy for top-level components --- src/isomorphic/ReactPerf.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/isomorphic/ReactPerf.js b/src/isomorphic/ReactPerf.js index 596ca6e900d..a9cf2b50707 100644 --- a/src/isomorphic/ReactPerf.js +++ b/src/isomorphic/ReactPerf.js @@ -84,7 +84,7 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { var {displayName, ownerID} = treeSnapshot[instanceID]; var owner = treeSnapshot[ownerID]; - var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`; + var key = `${owner ? owner.displayName + ' >' : ''} ${displayName}`; var stats = aggregatedStats[key]; if (!stats) { @@ -151,7 +151,7 @@ function getWasted(flushHistory = getFlushHistory()) { var {displayName, ownerID} = treeSnapshot[instanceID]; var owner = treeSnapshot[ownerID]; - var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`; + var key = (owner ? owner.displayName + ' > ' : '') + displayName; var stats = aggregatedStats[key]; if (!stats) { @@ -232,7 +232,7 @@ function getOperations(flushHistory = getFlushHistory()) { var {instanceID, type, payload} = operation; var {displayName, ownerID} = treeSnapshot[instanceID]; var owner = treeSnapshot[ownerID]; - var key = `${(owner ? owner.displayName : '(no owner)')} > ${displayName}`; + var key = (owner ? owner.displayName + ' > ' : '') + displayName; stats.push({ flushIndex, From bc241bfcfef0fb54f6b8436bcf12c402102f565e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 21:52:45 +0100 Subject: [PATCH 09/11] Add getUpdateCount() to ReactComponentTreeDevtool It is necessary to exclude just mounted components from wasted calculation. --- src/isomorphic/ReactDebugTool.js | 1 + .../devtools/ReactComponentTreeDevtool.js | 10 ++++++++ .../ReactComponentTreeDevtool-test.js | 25 +++++++++++++++++++ .../ReactComponentTreeDevtool-test.native.js | 23 +++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index a76668b12c3..8fbc0341f80 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -72,6 +72,7 @@ function resetMeasurements() { tree[id] = { displayName: ReactComponentTreeDevtool.getDisplayName(id), text: ReactComponentTreeDevtool.getText(id), + updateCount: ReactComponentTreeDevtool.getUpdateCount(id), childIDs: ReactComponentTreeDevtool.getChildIDs(id), // Text nodes don't have owners but this is close enough. ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID), diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index bdba5b378f3..57e85b39dfc 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -25,6 +25,7 @@ function updateTree(id, update) { childIDs: [], displayName: 'Unknown', isMounted: false, + updateCount: 0, }; } update(tree[id]); @@ -95,6 +96,10 @@ var ReactComponentTreeDevtool = { rootIDs.push(id); }, + onUpdateComponent(id) { + updateTree(id, item => item.updateCount++); + }, + onUnmountComponent(id) { updateTree(id, item => item.isMounted = false); rootIDs = rootIDs.filter(rootID => rootID !== id); @@ -136,6 +141,11 @@ var ReactComponentTreeDevtool = { return item ? item.text : null; }, + getUpdateCount(id) { + var item = tree[id]; + return item ? item.updateCount : 0; + }, + getRootIDs() { return rootIDs; }, diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 2ff1a58abb8..106307d0d25 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -1697,6 +1697,31 @@ describe('ReactComponentTreeDevtool', () => { }); }); + it('reports update counts', () => { + var node = document.createElement('div'); + + ReactDOM.render(
, node); + var divID = ReactComponentTreeDevtool.getRootIDs()[0]; + expect(ReactComponentTreeDevtool.getUpdateCount(divID)).toEqual(0); + + ReactDOM.render(, node); + var spanID = ReactComponentTreeDevtool.getRootIDs()[0]; + expect(ReactComponentTreeDevtool.getUpdateCount(divID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(spanID)).toEqual(0); + + ReactDOM.render(, node); + expect(ReactComponentTreeDevtool.getUpdateCount(divID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(spanID)).toEqual(1); + + ReactDOM.render(, node); + expect(ReactComponentTreeDevtool.getUpdateCount(divID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(spanID)).toEqual(2); + + ReactDOM.unmountComponentAtNode(node); + expect(ReactComponentTreeDevtool.getUpdateCount(divID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(spanID)).toEqual(2); + }); + it('does not report top-level wrapper as a root', () => { var node = document.createElement('div'); diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js index 4da93ab911a..3113d6fcad8 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js @@ -1685,6 +1685,29 @@ describe('ReactComponentTreeDevtool', () => { }); }); + it('reports update counts', () => { + ReactNative.render(, 1); + var viewID = ReactComponentTreeDevtool.getRootIDs()[0]; + expect(ReactComponentTreeDevtool.getUpdateCount(viewID)).toEqual(0); + + ReactNative.render(, 1); + var imageID = ReactComponentTreeDevtool.getRootIDs()[0]; + expect(ReactComponentTreeDevtool.getUpdateCount(viewID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(imageID)).toEqual(0); + + ReactNative.render(, 1); + expect(ReactComponentTreeDevtool.getUpdateCount(viewID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(imageID)).toEqual(1); + + ReactNative.render(, 1); + expect(ReactComponentTreeDevtool.getUpdateCount(viewID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(imageID)).toEqual(2); + + ReactNative.unmountComponentAtNode(1); + expect(ReactComponentTreeDevtool.getUpdateCount(viewID)).toEqual(0); + expect(ReactComponentTreeDevtool.getUpdateCount(imageID)).toEqual(2); + }); + it('does not report top-level wrapper as a root', () => { ReactNative.render(, 1); expect(getRootDisplayNames()).toEqual(['View']); From 7b241d1d7fa285218fcdb3522b24de1bce9c8b25 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 23:09:54 +0100 Subject: [PATCH 10/11] Add tests and fix wasted render calculation --- src/isomorphic/ReactPerf.js | 80 ++++--- src/isomorphic/__tests__/ReactPerf-test.js | 238 +++++++++++++++++++++ 2 files changed, 283 insertions(+), 35 deletions(-) create mode 100644 src/isomorphic/__tests__/ReactPerf-test.js diff --git a/src/isomorphic/ReactPerf.js b/src/isomorphic/ReactPerf.js index a9cf2b50707..14ea56be510 100644 --- a/src/isomorphic/ReactPerf.js +++ b/src/isomorphic/ReactPerf.js @@ -27,12 +27,10 @@ function getExclusive(flushHistory = getFlushHistory()) { var aggregatedStats = {}; var affectedIDs = {}; - function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { + function updateAggregatedStats(treeSnapshot, instanceID, timerType, applyUpdate) { var {displayName} = treeSnapshot[instanceID]; - var key = displayName; var stats = aggregatedStats[key]; - if (!stats) { affectedIDs[key] = {}; stats = aggregatedStats[key] = { @@ -43,7 +41,12 @@ function getExclusive(flushHistory = getFlushHistory()) { totalDuration: 0, }; } - + if (!stats.durations[timerType]) { + stats.durations[timerType] = 0; + } + if (!stats.counts[timerType]) { + stats.counts[timerType] = 0; + } affectedIDs[key][instanceID] = true; applyUpdate(stats); } @@ -52,17 +55,9 @@ function getExclusive(flushHistory = getFlushHistory()) { var {measurements, treeSnapshot} = flush; measurements.forEach(measurement => { var {duration, instanceID, timerType} = measurement; - updateAggregatedStats(treeSnapshot, instanceID, stats => { + updateAggregatedStats(treeSnapshot, instanceID, timerType, stats => { stats.totalDuration += duration; - - if (!stats.durations[timerType]) { - stats.durations[timerType] = 0; - } stats.durations[timerType] += duration; - - if (!stats.counts[timerType]) { - stats.counts[timerType] = 0; - } stats.counts[timerType]++; }); }); @@ -73,20 +68,20 @@ function getExclusive(flushHistory = getFlushHistory()) { ...aggregatedStats[key], instanceCount: Object.keys(affectedIDs[key]).length, })) - .sort((a, b) => b.totalDuration - a.totalDuration); + .sort((a, b) => + b.totalDuration - a.totalDuration + ); } -function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { +function getInclusive(flushHistory = getFlushHistory()) { var aggregatedStats = {}; var affectedIDs = {}; function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { var {displayName, ownerID} = treeSnapshot[instanceID]; - var owner = treeSnapshot[ownerID]; var key = `${owner ? owner.displayName + ' >' : ''} ${displayName}`; var stats = aggregatedStats[key]; - if (!stats) { affectedIDs[key] = {}; stats = aggregatedStats[key] = { @@ -96,12 +91,11 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { renderCount: 0, }; } - affectedIDs[key][instanceID] = true; applyUpdate(stats); } - var hasRenderedByID = {}; + var isCompositeByID = {}; flushHistory.forEach(flush => { var {measurements} = flush; measurements.forEach(measurement => { @@ -109,7 +103,7 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { if (timerType !== 'render') { return; } - hasRenderedByID[instanceID] = true; + isCompositeByID[instanceID] = true; }); }); @@ -125,7 +119,9 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { }); var nextParentID = instanceID; while (nextParentID) { - if (hasRenderedByID[nextParentID]) { + // As we traverse parents, only count inclusive time towards composites. + // We know something is a composite if its render() was called. + if (isCompositeByID[nextParentID]) { updateAggregatedStats(treeSnapshot, nextParentID, stats => { stats.inclusiveRenderDuration += duration; }); @@ -140,7 +136,9 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { ...aggregatedStats[key], instanceCount: Object.keys(affectedIDs[key]).length, })) - .sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration); + .sort((a, b) => + b.inclusiveRenderDuration - a.inclusiveRenderDuration + ); } function getWasted(flushHistory = getFlushHistory()) { @@ -149,11 +147,9 @@ function getWasted(flushHistory = getFlushHistory()) { function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { var {displayName, ownerID} = treeSnapshot[instanceID]; - var owner = treeSnapshot[ownerID]; var key = (owner ? owner.displayName + ' > ' : '') + displayName; var stats = aggregatedStats[key]; - if (!stats) { affectedIDs[key] = {}; stats = aggregatedStats[key] = { @@ -163,25 +159,27 @@ function getWasted(flushHistory = getFlushHistory()) { renderCount: 0, }; } - affectedIDs[key][instanceID] = true; applyUpdate(stats); } flushHistory.forEach(flush => { var {measurements, treeSnapshot, operations} = flush; - var dirtyInstanceIDs = {}; + var isDefinitelyNotWastedByID = {}; + // Find native components associated with an operation in this batch. + // Mark all components in their parent tree as definitely not wasted. operations.forEach(operation => { var {instanceID} = operation; - var nextParentID = instanceID; while (nextParentID) { - dirtyInstanceIDs[nextParentID] = true; + isDefinitelyNotWastedByID[nextParentID] = true; nextParentID = treeSnapshot[nextParentID].parentID; } }); + // Find composite components that rendered in this batch. + // These are potential candidates for being wasted renders. var renderedCompositeIDs = {}; measurements.forEach(measurement => { var {instanceID, timerType} = measurement; @@ -196,21 +194,31 @@ function getWasted(flushHistory = getFlushHistory()) { if (timerType !== 'render') { return; } + + // If there was a DOM update below this component, or it has just been + // mounted, its render() is not considered wasted. var { updateCount } = treeSnapshot[instanceID]; - if (dirtyInstanceIDs[instanceID] || updateCount === 0) { + if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) { return; } + + // We consider this render() wasted. updateAggregatedStats(treeSnapshot, instanceID, stats => { stats.renderCount++; }); + var nextParentID = instanceID; while (nextParentID) { - if (!renderedCompositeIDs[nextParentID]) { - break; + // Any parents rendered during this batch are considered wasted + // unless we previously marked them as dirty. + var isWasted = + renderedCompositeIDs[nextParentID] && + !isDefinitelyNotWastedByID[nextParentID]; + if (isWasted) { + updateAggregatedStats(treeSnapshot, nextParentID, stats => { + stats.inclusiveRenderDuration += duration; + }); } - updateAggregatedStats(treeSnapshot, nextParentID, stats => { - stats.inclusiveRenderDuration += duration; - }); nextParentID = treeSnapshot[nextParentID].parentID; } }); @@ -221,7 +229,9 @@ function getWasted(flushHistory = getFlushHistory()) { ...aggregatedStats[key], instanceCount: Object.keys(affectedIDs[key]).length, })) - .sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration); + .sort((a, b) => + b.inclusiveRenderDuration - a.inclusiveRenderDuration + ); } function getOperations(flushHistory = getFlushHistory()) { diff --git a/src/isomorphic/__tests__/ReactPerf-test.js b/src/isomorphic/__tests__/ReactPerf-test.js new file mode 100644 index 00000000000..98df00938b0 --- /dev/null +++ b/src/isomorphic/__tests__/ReactPerf-test.js @@ -0,0 +1,238 @@ +/** + * 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. + * + * @emails react-core + */ + +'use strict'; + +describe('ReactPerf', function() { + var React; + var ReactDOM; + var ReactPerf; + var ReactTestUtils; + + var App; + var Box; + var Div; + + beforeEach(function() { + var now = 0; + jest.setMock('fbjs/lib/performanceNow', function() { + return now++; + }); + + if (typeof console.table !== 'function') { + console.table = () => {}; + console.table.isFake = true; + } + + React = require('React'); + ReactDOM = require('ReactDOM'); + ReactPerf = require('ReactPerf'); + ReactTestUtils = require('ReactTestUtils'); + + App = React.createClass({ + render: function() { + return
; + }, + }); + + Box = React.createClass({ + render: function() { + return
; + }, + }); + + // ReactPerf only measures composites, so we put everything in one. + Div = React.createClass({ + render: function() { + return
; + }, + }); + }); + + afterEach(function() { + if (console.table.isFake) { + delete console.table; + } + }); + + function measure(fn) { + ReactPerf.start(); + fn(); + ReactPerf.stop(); + return ReactPerf.getLastMeasurements(); + } + + it('should count no-op update as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(, container); + var measurements = measure(() => { + ReactDOM.render(, container); + }); + + var summary = ReactPerf.getWasted(measurements); + expect(summary).toEqual([{ + key: 'App', + instanceCount: 1, + inclusiveRenderDuration: 3, + renderCount: 1, + }, { + key: 'App > Box', + instanceCount: 2, + inclusiveRenderDuration: 2, + renderCount: 2, + }]); + }); + + it('should count no-op update in child as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(, container); + + // Here, we add a Box -- two of the updates are wasted time (but the + // addition of the third is not) + var measurements = measure(() => { + ReactDOM.render(, container); + }); + + var summary = ReactPerf.getWasted(measurements); + expect(summary).toEqual([{ + key: 'App > Box', + instanceCount: 1, + inclusiveRenderDuration: 1, + renderCount: 1, + }]); + }); + + function expectNoWaste(fn) { + var measurements = measure(fn); + var summary = ReactPerf.getWasted(measurements); + expect(summary).toEqual([]); + } + + it('should not count initial render as waste', function() { + expectNoWaste(() => { + ReactTestUtils.renderIntoDocument(); + }); + }); + + it('should not count unmount as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
hello
, container); + expectNoWaste(() => { + ReactDOM.unmountComponentAtNode(container); + }); + }); + + it('should not count content update as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
hello
, container); + expectNoWaste(() => { + ReactDOM.render(
hello world
, container); + }); + }); + + it('should not count child addition as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
, container); + expectNoWaste(() => { + ReactDOM.render(
, container); + }); + }); + + it('should not count child removal as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
, container); + expectNoWaste(() => { + ReactDOM.render(
, container); + }); + }); + + it('should not count property update as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
hey
, container); + expectNoWaste(() => { + ReactDOM.render(
hey
, container); + }); + }); + + it('should not count style update as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
hey
, container); + expectNoWaste(() => { + ReactDOM.render(
hey
, container); + }); + }); + + it('should not count property removal as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
hey
, container); + expectNoWaste(() => { + ReactDOM.render(
hey
, container); + }); + }); + + it('should not count raw HTML update as waste', function() { + var container = document.createElement('div'); + ReactDOM.render( +
, + container + ); + expectNoWaste(() => { + ReactDOM.render( +
, + container + ); + }); + }); + + it('should not count child reordering as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
, container); + expectNoWaste(() => { + ReactDOM.render(
, container); + }); + }); + + it('should not count text update as waste', function() { + var container = document.createElement('div'); + ReactDOM.render(
{'hello'}{'world'}
, container); + expectNoWaste(() => { + ReactDOM.render(
{'hello'}{'friend'}
, container); + }); + }); + + it('warns once when using getMeasurementsSummaryMap', function() { + var measurements = measure(() => {}); + spyOn(console, 'error'); + ReactPerf.getMeasurementsSummaryMap(measurements); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + + '`ReactPerf.getWasted(...)` instead.' + ); + + ReactPerf.getMeasurementsSummaryMap(measurements); + expect(console.error.calls.length).toBe(1); + }); + + it('warns once when using printDOM', function() { + var measurements = measure(() => {}); + spyOn(console, 'error'); + ReactPerf.printDOM(measurements); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + '`ReactPerf.printDOM(...)` is deprecated. Use ' + + '`ReactPerf.printOperations(...)` instead.' + ); + + ReactPerf.printDOM(measurements); + expect(console.error.calls.length).toBe(1); + }); +}); From 82e363c46477cc1c3b37a5dd94a1e8d92d6598ce Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 23:12:46 +0100 Subject: [PATCH 11/11] Fix displayName in ReactPerf.getInclusive() output --- src/isomorphic/ReactPerf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isomorphic/ReactPerf.js b/src/isomorphic/ReactPerf.js index 14ea56be510..4c631a7f13e 100644 --- a/src/isomorphic/ReactPerf.js +++ b/src/isomorphic/ReactPerf.js @@ -80,7 +80,7 @@ function getInclusive(flushHistory = getFlushHistory()) { function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { var {displayName, ownerID} = treeSnapshot[instanceID]; var owner = treeSnapshot[ownerID]; - var key = `${owner ? owner.displayName + ' >' : ''} ${displayName}`; + var key = (owner ? owner.displayName + ' > ' : '') + displayName; var stats = aggregatedStats[key]; if (!stats) { affectedIDs[key] = {};