diff --git a/Libraries/Core/InitializeCore.js b/Libraries/Core/InitializeCore.js index 9efeba237055b9..6e00fe10696a3c 100644 --- a/Libraries/Core/InitializeCore.js +++ b/Libraries/Core/InitializeCore.js @@ -99,6 +99,9 @@ if (!global.process.env.NODE_ENV) { // Set up profile const Systrace = require('Systrace'); Systrace.setEnabled(global.__RCTProfileIsProfiling || false); +if (__DEV__) { + global.performance = Systrace.getUserTimingPolyfill(); +} // Set up console const ExceptionsManager = require('ExceptionsManager'); diff --git a/Libraries/Performance/RCTRenderingPerf.js b/Libraries/Performance/RCTRenderingPerf.js index c15e742a0d2abc..5c54d3989e92d4 100644 --- a/Libraries/Performance/RCTRenderingPerf.js +++ b/Libraries/Performance/RCTRenderingPerf.js @@ -11,9 +11,6 @@ */ 'use strict'; -var ReactDebugTool = require('ReactDebugTool'); -var ReactPerf = require('ReactPerf'); - var invariant = require('fbjs/lib/invariant'); var performanceNow = require('fbjs/lib/performanceNow'); @@ -24,22 +21,6 @@ type perfModule = { var perfModules = []; var enabled = false; -var lastRenderStartTime = 0; -var totalRenderDuration = 0; - -var RCTRenderingPerfDevtool = { - onBeginLifeCycleTimer(debugID, timerType) { - if (timerType === 'render') { - lastRenderStartTime = performanceNow(); - } - }, - onEndLifeCycleTimer(debugID, timerType) { - if (timerType === 'render') { - var lastRenderDuration = performanceNow() - lastRenderStartTime; - totalRenderDuration += lastRenderDuration; - } - }, -}; var RCTRenderingPerf = { // Once perf is enabled, it stays enabled @@ -53,8 +34,6 @@ var RCTRenderingPerf = { return; } - ReactPerf.start(); - ReactDebugTool.addHook(RCTRenderingPerfDevtool); perfModules.forEach((module) => module.start()); }, @@ -63,15 +42,6 @@ var RCTRenderingPerf = { return; } - ReactPerf.stop(); - ReactPerf.printInclusive(); - ReactPerf.printWasted(); - ReactDebugTool.removeHook(RCTRenderingPerfDevtool); - - console.log(`Total time spent in render(): ${totalRenderDuration.toFixed(2)} ms`); - lastRenderStartTime = 0; - totalRenderDuration = 0; - perfModules.forEach((module) => module.stop()); }, diff --git a/Libraries/Performance/Systrace.js b/Libraries/Performance/Systrace.js index 15bade515b165d..8db4ad99ed6895 100644 --- a/Libraries/Performance/Systrace.js +++ b/Libraries/Performance/Systrace.js @@ -11,6 +11,8 @@ */ 'use strict'; +const invariant = require('fbjs/lib/invariant'); + type RelayProfiler = { attachProfileHandler( name: string, @@ -29,52 +31,89 @@ const TRACE_TAG_JSC_CALLS = 1 << 27; let _enabled = false; let _asyncCookie = 0; +const _markStack = []; +let _markStackIndex = -1; -const ReactSystraceDevtool = __DEV__ ? { - onBeforeMountComponent(debugID) { - const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook; - const displayName = ReactComponentTreeHook.getDisplayName(debugID); - Systrace.beginEvent(`ReactReconciler.mountComponent(${displayName})`); - }, - onMountComponent(debugID) { - Systrace.endEvent(); - }, - onBeforeUpdateComponent(debugID) { - const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook; - const displayName = ReactComponentTreeHook.getDisplayName(debugID); - Systrace.beginEvent(`ReactReconciler.updateComponent(${displayName})`); - }, - onUpdateComponent(debugID) { - Systrace.endEvent(); - }, - onBeforeUnmountComponent(debugID) { - const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook; - const displayName = ReactComponentTreeHook.getDisplayName(debugID); - Systrace.beginEvent(`ReactReconciler.unmountComponent(${displayName})`); +// Implements a subset of User Timing API necessary for React measurements. +// https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API +const REACT_MARKER = '\u269B'; +const userTimingPolyfill = { + mark(markName: string) { + if (__DEV__) { + if (_enabled) { + _markStackIndex++; + _markStack[_markStackIndex] = markName; + let systraceLabel = markName; + // Since perf measurements are a shared namespace in User Timing API, + // we prefix all React results with a React emoji. + if (markName[0] === REACT_MARKER) { + // This is coming from React. + // Removing component IDs keeps trace colors stable. + const indexOfId = markName.lastIndexOf(' (#'); + const cutoffIndex = indexOfId !== -1 ? indexOfId : markName.length; + // Also cut off the emoji because it breaks Systrace + systraceLabel = markName.slice(2, cutoffIndex); + } + Systrace.beginEvent(systraceLabel); + } + } }, - onUnmountComponent(debugID) { - Systrace.endEvent(); + measure(measureName: string, startMark: ?string, endMark: ?string) { + if (__DEV__) { + if (_enabled) { + invariant( + typeof measureName === 'string' && + typeof startMark === 'string' && + typeof endMark === 'undefined', + 'Only performance.measure(string, string) overload is supported.' + ); + const topMark = _markStack[_markStackIndex]; + invariant( + startMark === topMark, + 'There was a mismatching performance.measure() call. ' + + 'Expected "%s" but got "%s."', + topMark, + startMark, + ); + _markStackIndex--; + // We can't use more descriptive measureName because Systrace doesn't + // let us edit labels post factum. + Systrace.endEvent(); + } + } }, - onBeginLifeCycleTimer(debugID, timerType) { - const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook; - const displayName = ReactComponentTreeHook.getDisplayName(debugID); - Systrace.beginEvent(`${displayName}.${timerType}()`); + clearMarks(markName: string) { + if (__DEV__) { + if (_enabled) { + if (_markStackIndex === -1) { + return; + } + if (markName === _markStack[_markStackIndex]) { + // React uses this for "cancelling" started measurements. + // Systrace doesn't support deleting measurements, so we just stop them. + userTimingPolyfill.measure(markName, markName); + } + } + } }, - onEndLifeCycleTimer(debugID, timerType) { - Systrace.endEvent(); + clearMeasures() { + // React calls this to avoid memory leaks in browsers, but we don't keep + // measurements anyway. }, -} : null; +}; const Systrace = { + getUserTimingPolyfill() { + return userTimingPolyfill; + }, + setEnabled(enabled: boolean) { if (_enabled !== enabled) { if (__DEV__) { if (enabled) { global.nativeTraceBeginLegacy && global.nativeTraceBeginLegacy(TRACE_TAG_JSC_CALLS); - require('ReactDebugTool').addHook(ReactSystraceDevtool); } else { global.nativeTraceEndLegacy && global.nativeTraceEndLegacy(TRACE_TAG_JSC_CALLS); - require('ReactDebugTool').removeHook(ReactSystraceDevtool); } } _enabled = enabled;