From 8ef3aa5d18ca4c973c8517edc4c9ddecf7223d18 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 9 Mar 2017 15:53:23 +0000 Subject: [PATCH] more forwarding modules --- scripts/rollup/forwarding/ReactDebugTool.js | 424 ++++++++++++++++++ scripts/rollup/forwarding/ReactPerf.js | 450 +++++++++++++++++++- 2 files changed, 869 insertions(+), 5 deletions(-) create mode 100644 scripts/rollup/forwarding/ReactDebugTool.js diff --git a/scripts/rollup/forwarding/ReactDebugTool.js b/scripts/rollup/forwarding/ReactDebugTool.js new file mode 100644 index 0000000000..fa2f68bdde --- /dev/null +++ b/scripts/rollup/forwarding/ReactDebugTool.js @@ -0,0 +1,424 @@ +/** + * Copyright 2016-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 ReactDebugTool + * @flow + */ + +'use strict'; + +var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook'); +var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); +var ReactComponentTreeHook = require('ReactComponentTreeHook'); +var ExecutionEnvironment = require('ExecutionEnvironment'); + +var performanceNow = require('performanceNow'); +var warning = require('warning'); + +import type { ReactElement } from 'ReactElementType'; +import type { DebugID } from 'ReactInstanceType'; +import type { Operation } from 'ReactHostOperationHistoryHook'; + +type Hook = any; + +type TimerType = + 'ctor' | + 'render' | + 'componentWillMount' | + 'componentWillUnmount' | + 'componentWillReceiveProps' | + 'shouldComponentUpdate' | + 'componentWillUpdate' | + 'componentDidUpdate' | + 'componentDidMount'; + +type Measurement = { + timerType: TimerType, + instanceID: DebugID, + duration: number, +}; + +type TreeSnapshot = { + [key: DebugID]: { + displayName: string, + text: string, + updateCount: number, + childIDs: Array, + ownerID: DebugID, + parentID: DebugID, + } +}; + +type HistoryItem = { + duration: number, + measurements: Array, + operations: Array, + treeSnapshot: TreeSnapshot, +}; + +export type FlushHistory = Array; + +// Trust the developer to only use this with a __DEV__ check +var ReactDebugTool = ((null: any): typeof ReactDebugTool); + +if (__DEV__) { + var hooks = []; + var didHookThrowForEvent = {}; + + const callHook = function(event, fn, context, arg1, arg2, arg3, arg4, arg5) { + try { + fn.call(context, arg1, arg2, arg3, arg4, arg5); + } catch (e) { + warning( + didHookThrowForEvent[event], + 'Exception thrown by hook while handling %s: %s', + event, + e + '\n' + e.stack + ); + didHookThrowForEvent[event] = true; + } + }; + + const emitEvent = function(event, arg1, arg2, arg3, arg4, arg5) { + for (var i = 0; i < hooks.length; i++) { + var hook = hooks[i]; + var fn = hook[event]; + if (fn) { + callHook(event, fn, hook, arg1, arg2, arg3, arg4, arg5); + } + } + }; + + var isProfiling = false; + var flushHistory = []; + var lifeCycleTimerStack = []; + var currentFlushNesting = 0; + var currentFlushMeasurements = []; + var currentFlushStartTime = 0; + var currentTimerDebugID = null; + var currentTimerStartTime = 0; + var currentTimerNestedFlushDuration = 0; + var currentTimerType = null; + + var lifeCycleTimerHasWarned = false; + + const clearHistory = function() { + ReactComponentTreeHook.purgeUnmountedComponents(); + ReactHostOperationHistoryHook.clearHistory(); + }; + + const getTreeSnapshot = function(registeredIDs) { + return registeredIDs.reduce((tree, id) => { + var ownerID = ReactComponentTreeHook.getOwnerID(id); + var parentID = ReactComponentTreeHook.getParentID(id); + tree[id] = { + displayName: ReactComponentTreeHook.getDisplayName(id), + text: ReactComponentTreeHook.getText(id), + updateCount: ReactComponentTreeHook.getUpdateCount(id), + childIDs: ReactComponentTreeHook.getChildIDs(id), + // Text nodes don't have owners but this is close enough. + ownerID: ownerID || + parentID && ReactComponentTreeHook.getOwnerID(parentID) || + 0, + parentID, + }; + return tree; + }, {}); + }; + + const resetMeasurements = function() { + var previousStartTime = currentFlushStartTime; + var previousMeasurements = currentFlushMeasurements; + var previousOperations = ReactHostOperationHistoryHook.getHistory(); + + if (currentFlushNesting === 0) { + currentFlushStartTime = 0; + currentFlushMeasurements = []; + clearHistory(); + return; + } + + if (previousMeasurements.length || previousOperations.length) { + var registeredIDs = ReactComponentTreeHook.getRegisteredIDs(); + flushHistory.push({ + duration: performanceNow() - previousStartTime, + measurements: previousMeasurements || [], + operations: previousOperations || [], + treeSnapshot: getTreeSnapshot(registeredIDs), + }); + } + + clearHistory(); + currentFlushStartTime = performanceNow(); + currentFlushMeasurements = []; + }; + + const checkDebugID = function(debugID, allowRoot = false) { + if (allowRoot && debugID === 0) { + return; + } + if (!debugID) { + warning(false, 'ReactDebugTool: debugID may not be empty.'); + } + }; + + const beginLifeCycleTimer = function(debugID, timerType) { + if (currentFlushNesting === 0) { + return; + } + if (currentTimerType && !lifeCycleTimerHasWarned) { + warning( + false, + 'There is an internal error in the React performance measurement code.' + + '\n\nDid not expect %s timer to start while %s timer is still in ' + + 'progress for %s instance.', + timerType, + currentTimerType || 'no', + (debugID === currentTimerDebugID) ? 'the same' : 'another' + ); + lifeCycleTimerHasWarned = true; + } + currentTimerStartTime = performanceNow(); + currentTimerNestedFlushDuration = 0; + currentTimerDebugID = debugID; + currentTimerType = timerType; + }; + + const endLifeCycleTimer = function(debugID, timerType) { + if (currentFlushNesting === 0) { + return; + } + if (currentTimerType !== timerType && !lifeCycleTimerHasWarned) { + warning( + false, + 'There is an internal error in the React performance measurement code. ' + + 'We did not expect %s timer to stop while %s timer is still in ' + + 'progress for %s instance. Please report this as a bug in React.', + timerType, + currentTimerType || 'no', + (debugID === currentTimerDebugID) ? 'the same' : 'another' + ); + lifeCycleTimerHasWarned = true; + } + if (isProfiling) { + currentFlushMeasurements.push({ + timerType, + instanceID: debugID, + duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration, + }); + } + currentTimerStartTime = 0; + currentTimerNestedFlushDuration = 0; + currentTimerDebugID = null; + currentTimerType = null; + }; + + const pauseCurrentLifeCycleTimer = function() { + var currentTimer = { + startTime: currentTimerStartTime, + nestedFlushStartTime: performanceNow(), + debugID: currentTimerDebugID, + timerType: currentTimerType, + }; + lifeCycleTimerStack.push(currentTimer); + currentTimerStartTime = 0; + currentTimerNestedFlushDuration = 0; + currentTimerDebugID = null; + currentTimerType = null; + }; + + const resumeCurrentLifeCycleTimer = function() { + var {startTime, nestedFlushStartTime, debugID, timerType} = lifeCycleTimerStack.pop(); + var nestedFlushDuration = performanceNow() - nestedFlushStartTime; + currentTimerStartTime = startTime; + currentTimerNestedFlushDuration += nestedFlushDuration; + currentTimerDebugID = debugID; + currentTimerType = timerType; + }; + + var lastMarkTimeStamp = 0; + var canUsePerformanceMeasure: boolean = + typeof performance !== 'undefined' && + typeof performance.mark === 'function' && + typeof performance.clearMarks === 'function' && + typeof performance.measure === 'function' && + typeof performance.clearMeasures === 'function'; + + const shouldMark = function(debugID) { + if (!isProfiling || !canUsePerformanceMeasure) { + return false; + } + var element = ReactComponentTreeHook.getElement(debugID); + if (element == null || typeof element !== 'object') { + return false; + } + var isHostElement = typeof element.type === 'string'; + if (isHostElement) { + return false; + } + return true; + }; + + const markBegin = function(debugID, markType) { + if (!shouldMark(debugID)) { + return; + } + + var markName = `${debugID}::${markType}`; + lastMarkTimeStamp = performanceNow(); + performance.mark(markName); + }; + + const markEnd = function(debugID, markType) { + if (!shouldMark(debugID)) { + return; + } + + var markName = `${debugID}::${markType}`; + var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown'; + + // Chrome has an issue of dropping markers recorded too fast: + // https://bugs.chromium.org/p/chromium/issues/detail?id=640652 + // To work around this, we will not report very small measurements. + // I determined the magic number by tweaking it back and forth. + // 0.05ms was enough to prevent the issue, but I set it to 0.1ms to be safe. + // When the bug is fixed, we can `measure()` unconditionally if we want to. + var timeStamp = performanceNow(); + if (timeStamp - lastMarkTimeStamp > 0.1) { + var measurementName = `${displayName} [${markType}]`; + performance.measure(measurementName, markName); + } + + performance.clearMarks(markName); + performance.clearMeasures(measurementName); + }; + + ReactDebugTool = { + addHook(hook: Hook): void { + hooks.push(hook); + }, + removeHook(hook: Hook): void { + for (var i = 0; i < hooks.length; i++) { + if (hooks[i] === hook) { + hooks.splice(i, 1); + i--; + } + } + }, + isProfiling(): boolean { + return isProfiling; + }, + beginProfiling(): void { + if (isProfiling) { + return; + } + + isProfiling = true; + flushHistory.length = 0; + resetMeasurements(); + ReactDebugTool.addHook(ReactHostOperationHistoryHook); + }, + endProfiling(): void { + if (!isProfiling) { + return; + } + + isProfiling = false; + resetMeasurements(); + ReactDebugTool.removeHook(ReactHostOperationHistoryHook); + }, + getFlushHistory(): FlushHistory { + return flushHistory; + }, + onBeginFlush(): void { + currentFlushNesting++; + resetMeasurements(); + pauseCurrentLifeCycleTimer(); + emitEvent('onBeginFlush'); + }, + onEndFlush(): void { + resetMeasurements(); + currentFlushNesting--; + resumeCurrentLifeCycleTimer(); + emitEvent('onEndFlush'); + }, + onBeginLifeCycleTimer(debugID: DebugID, timerType: TimerType): void { + checkDebugID(debugID); + emitEvent('onBeginLifeCycleTimer', debugID, timerType); + markBegin(debugID, timerType); + beginLifeCycleTimer(debugID, timerType); + }, + onEndLifeCycleTimer(debugID: DebugID, timerType: TimerType): void { + checkDebugID(debugID); + endLifeCycleTimer(debugID, timerType); + markEnd(debugID, timerType); + emitEvent('onEndLifeCycleTimer', debugID, timerType); + }, + onBeginProcessingChildContext(): void { + emitEvent('onBeginProcessingChildContext'); + }, + onEndProcessingChildContext(): void { + emitEvent('onEndProcessingChildContext'); + }, + onHostOperation(operation: Operation) { + checkDebugID(operation.instanceID); + emitEvent('onHostOperation', operation); + }, + onSetState(): void { + emitEvent('onSetState'); + }, + onSetChildren(debugID: DebugID, childDebugIDs: Array) { + checkDebugID(debugID); + childDebugIDs.forEach(checkDebugID); + emitEvent('onSetChildren', debugID, childDebugIDs); + }, + onBeforeMountComponent(debugID: DebugID, element: ReactElement, parentDebugID: DebugID): void { + checkDebugID(debugID); + checkDebugID(parentDebugID, true); + emitEvent('onBeforeMountComponent', debugID, element, parentDebugID); + markBegin(debugID, 'mount'); + }, + onMountComponent(debugID: DebugID): void { + checkDebugID(debugID); + markEnd(debugID, 'mount'); + emitEvent('onMountComponent', debugID); + }, + onBeforeUpdateComponent(debugID: DebugID, element: ReactElement): void { + checkDebugID(debugID); + emitEvent('onBeforeUpdateComponent', debugID, element); + markBegin(debugID, 'update'); + }, + onUpdateComponent(debugID: DebugID): void { + checkDebugID(debugID); + markEnd(debugID, 'update'); + emitEvent('onUpdateComponent', debugID); + }, + onBeforeUnmountComponent(debugID: DebugID): void { + checkDebugID(debugID); + emitEvent('onBeforeUnmountComponent', debugID); + markBegin(debugID, 'unmount'); + }, + onUnmountComponent(debugID: DebugID): void { + checkDebugID(debugID); + markEnd(debugID, 'unmount'); + emitEvent('onUnmountComponent', debugID); + }, + onTestEvent(): void { + emitEvent('onTestEvent'); + }, + }; + + ReactDebugTool.addHook(ReactInvalidSetStateWarningHook); + ReactDebugTool.addHook(ReactComponentTreeHook); + var url = (ExecutionEnvironment.canUseDOM && window.location.href) || ''; + if ((/[?&]react_perf\b/).test(url)) { + ReactDebugTool.beginProfiling(); + } +} + +module.exports = ReactDebugTool; diff --git a/scripts/rollup/forwarding/ReactPerf.js b/scripts/rollup/forwarding/ReactPerf.js index 02715f05bd..0f3886f69f 100644 --- a/scripts/rollup/forwarding/ReactPerf.js +++ b/scripts/rollup/forwarding/ReactPerf.js @@ -1,5 +1,5 @@ /** - * Copyright 2013-present, Facebook, Inc. + * Copyright 2016-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -12,8 +12,448 @@ 'use strict'; -const { - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, -} = require('ReactDOM'); +var ReactDebugTool = require('ReactDebugTool'); +var warning = require('warning'); +var alreadyWarned = false; -module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf; +import type { FlushHistory } from 'ReactDebugTool'; + +function roundFloat(val, base = 2) { + var n = Math.pow(10, base); + return Math.floor(val * n) / n; +} + +// Flow type definition of console.table is too strict right now, see +// https://github.com/facebook/flow/pull/2353 for updates +function consoleTable(table: Array<{[key: string]: any}>): void { + console.table((table: any)); +} + +function warnInProduction() { + if (alreadyWarned) { + return; + } + alreadyWarned = true; + if (typeof console !== 'undefined') { + console.error( + 'ReactPerf is not supported in the production builds of React. ' + + 'To collect measurements, please use the development build of React instead.' + ); + } +} + +function getLastMeasurements() { + if (!__DEV__) { + warnInProduction(); + return []; + } + + return ReactDebugTool.getFlushHistory(); +} + +function getExclusive(flushHistory = getLastMeasurements()) { + if (!__DEV__) { + warnInProduction(); + return []; + } + + var aggregatedStats = {}; + var affectedIDs = {}; + + function updateAggregatedStats(treeSnapshot, instanceID, timerType, applyUpdate) { + var {displayName} = treeSnapshot[instanceID]; + var key = displayName; + var stats = aggregatedStats[key]; + if (!stats) { + affectedIDs[key] = {}; + stats = aggregatedStats[key] = { + key, + instanceCount: 0, + counts: {}, + durations: {}, + totalDuration: 0, + }; + } + if (!stats.durations[timerType]) { + stats.durations[timerType] = 0; + } + if (!stats.counts[timerType]) { + stats.counts[timerType] = 0; + } + affectedIDs[key][instanceID] = true; + applyUpdate(stats); + } + + flushHistory.forEach(flush => { + var {measurements, treeSnapshot} = flush; + measurements.forEach(measurement => { + var {duration, instanceID, timerType} = measurement; + updateAggregatedStats(treeSnapshot, instanceID, timerType, stats => { + stats.totalDuration += duration; + stats.durations[timerType] += duration; + stats.counts[timerType]++; + }); + }); + }); + + return Object.keys(aggregatedStats) + .map(key => ({ + ...aggregatedStats[key], + instanceCount: Object.keys(affectedIDs[key]).length, + })) + .sort((a, b) => + b.totalDuration - a.totalDuration + ); +} + +function getInclusive(flushHistory = getLastMeasurements()) { + if (!__DEV__) { + warnInProduction(); + return []; + } + + 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] = { + key, + instanceCount: 0, + inclusiveRenderDuration: 0, + renderCount: 0, + }; + } + affectedIDs[key][instanceID] = true; + applyUpdate(stats); + } + + var isCompositeByID = {}; + flushHistory.forEach(flush => { + var {measurements} = flush; + measurements.forEach(measurement => { + var {instanceID, timerType} = measurement; + if (timerType !== 'render') { + return; + } + isCompositeByID[instanceID] = true; + }); + }); + + flushHistory.forEach(flush => { + var {measurements, treeSnapshot} = flush; + measurements.forEach(measurement => { + var {duration, instanceID, timerType} = measurement; + if (timerType !== 'render') { + return; + } + updateAggregatedStats(treeSnapshot, instanceID, stats => { + stats.renderCount++; + }); + var nextParentID = instanceID; + while (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; + }); + } + nextParentID = treeSnapshot[nextParentID].parentID; + } + }); + }); + + return Object.keys(aggregatedStats) + .map(key => ({ + ...aggregatedStats[key], + instanceCount: Object.keys(affectedIDs[key]).length, + })) + .sort((a, b) => + b.inclusiveRenderDuration - a.inclusiveRenderDuration + ); +} + +function getWasted(flushHistory = getLastMeasurements()) { + if (!__DEV__) { + warnInProduction(); + return []; + } + + 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] = { + key, + instanceCount: 0, + inclusiveRenderDuration: 0, + renderCount: 0, + }; + } + affectedIDs[key][instanceID] = true; + applyUpdate(stats); + } + + flushHistory.forEach(flush => { + var {measurements, treeSnapshot, operations} = flush; + var isDefinitelyNotWastedByID = {}; + + // Find host 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) { + 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; + if (timerType !== 'render') { + return; + } + renderedCompositeIDs[instanceID] = true; + }); + + measurements.forEach(measurement => { + var {duration, instanceID, timerType} = measurement; + 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 (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) { + return; + } + + // We consider this render() wasted. + updateAggregatedStats(treeSnapshot, instanceID, stats => { + stats.renderCount++; + }); + + var nextParentID = instanceID; + while (nextParentID) { + // 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; + }); + } + nextParentID = treeSnapshot[nextParentID].parentID; + } + }); + }); + + return Object.keys(aggregatedStats) + .map(key => ({ + ...aggregatedStats[key], + instanceCount: Object.keys(affectedIDs[key]).length, + })) + .sort((a, b) => + b.inclusiveRenderDuration - a.inclusiveRenderDuration + ); +} + +function getOperations(flushHistory = getLastMeasurements()) { + if (!__DEV__) { + warnInProduction(); + return []; + } + + var stats = []; + flushHistory.forEach((flush, flushIndex) => { + var {operations, treeSnapshot} = flush; + operations.forEach(operation => { + var {instanceID, type, payload} = operation; + var {displayName, ownerID} = treeSnapshot[instanceID]; + var owner = treeSnapshot[ownerID]; + var key = (owner ? owner.displayName + ' > ' : '') + displayName; + + stats.push({ + flushIndex, + instanceID, + key, + type, + ownerID, + payload, + }); + }); + }); + return stats; +} + +function printExclusive(flushHistory?: FlushHistory) { + if (!__DEV__) { + warnInProduction(); + return; + } + + var stats = getExclusive(flushHistory); + var table = stats.map(item => { + var {key, instanceCount, totalDuration} = item; + var renderCount = item.counts.render || 0; + var renderDuration = item.durations.render || 0; + return { + 'Component': key, + 'Total time (ms)': roundFloat(totalDuration), + 'Instance count': instanceCount, + 'Total render time (ms)': roundFloat(renderDuration), + 'Average render time (ms)': renderCount ? + roundFloat(renderDuration / renderCount) : + undefined, + 'Render count': renderCount, + 'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration), + }; + }); + consoleTable(table); +} + +function printInclusive(flushHistory?: FlushHistory) { + if (!__DEV__) { + warnInProduction(); + return; + } + + var stats = getInclusive(flushHistory); + var table = stats.map(item => { + var {key, instanceCount, inclusiveRenderDuration, renderCount} = item; + return { + 'Owner > Component': key, + 'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration), + 'Instance count': instanceCount, + 'Render count': renderCount, + }; + }); + consoleTable(table); +} + +function printWasted(flushHistory?: FlushHistory) { + if (!__DEV__) { + warnInProduction(); + return; + } + + var stats = getWasted(flushHistory); + var table = stats.map(item => { + var {key, instanceCount, inclusiveRenderDuration, renderCount} = item; + return { + 'Owner > Component': key, + 'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration), + 'Instance count': instanceCount, + 'Render count': renderCount, + }; + }); + consoleTable(table); +} + +function printOperations(flushHistory?: FlushHistory) { + if (!__DEV__) { + warnInProduction(); + return; + } + + var stats = getOperations(flushHistory); + var table = stats.map(stat => ({ + 'Owner > Node': stat.key, + 'Operation': stat.type, + 'Payload': typeof stat.payload === 'object' ? + JSON.stringify(stat.payload) : + stat.payload, + 'Flush index': stat.flushIndex, + 'Owner Component ID': stat.ownerID, + 'DOM Component ID': stat.instanceID, + })); + consoleTable(table); +} + +var warnedAboutPrintDOM = false; +function printDOM(measurements: FlushHistory) { + warning( + warnedAboutPrintDOM, + '`ReactPerf.printDOM(...)` is deprecated. Use ' + + '`ReactPerf.printOperations(...)` instead.' + ); + warnedAboutPrintDOM = true; + return printOperations(measurements); +} + +var warnedAboutGetMeasurementsSummaryMap = false; +function getMeasurementsSummaryMap(measurements: FlushHistory) { + warning( + warnedAboutGetMeasurementsSummaryMap, + '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + + '`ReactPerf.getWasted(...)` instead.' + ); + warnedAboutGetMeasurementsSummaryMap = true; + return getWasted(measurements); +} + +function start() { + if (!__DEV__) { + warnInProduction(); + return; + } + + ReactDebugTool.beginProfiling(); +} + +function stop() { + if (!__DEV__) { + warnInProduction(); + return; + } + + ReactDebugTool.endProfiling(); +} + +function isRunning() { + if (!__DEV__) { + warnInProduction(); + return false; + } + + return ReactDebugTool.isProfiling(); +} + +var ReactPerfAnalysis = { + getLastMeasurements, + getExclusive, + getInclusive, + getWasted, + getOperations, + printExclusive, + printInclusive, + printWasted, + printOperations, + start, + stop, + isRunning, + // Deprecated: + printDOM, + getMeasurementsSummaryMap, +}; + +module.exports = ReactPerfAnalysis;