@samitouri / QOS-React / commits / 67b1dbf75f

Add new ReactPerf

Dan Abramov committed Apr 28, 2016 at 19:56 UTC 67b1dbf75f3960c5aabf905876eddd5b1260c541
12 files changed +792 -39
grunt/tasks/npm-react-addons.js
+1 -1
@@ -16,7 +16,7 @@ var addons = {
16 docs: 'two-way-binding-helpers',
17 },
18 Perf: {
19 - module: 'ReactDefaultPerf',
19 + module: 'ReactPerfAnalysis',
20 name: 'perf',
21 docs: 'perf',
22 },
src/addons/ReactWithAddons.js
+1 -1
@@ -34,7 +34,7 @@ React.addons = {
34 };
35
36 if (__DEV__) {
37 - React.addons.Perf = require('ReactDefaultPerf');
37 + React.addons.Perf = require('ReactPerfAnalysis');
38 React.addons.TestUtils = require('ReactTestUtils');
39 }
40
src/isomorphic/ReactDebugTool.js
+154 -2
@@ -11,7 +11,9 @@
11
12 'use strict';
13
14 -var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
14 +var ExecutionEnvironment = require('ExecutionEnvironment');
15 +
16 +var performanceNow = require('performanceNow');
17 var warning = require('warning');
18
19 var eventHandlers = [];
@@ -37,6 +39,56 @@ function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) {
39 }
40 }
41
42 +var isProfiling = false;
43 +var flushHistory = [];
44 +var currentFlushNesting = 0;
45 +var currentFlushMeasurements = null;
46 +var currentFlushStartTime = null;
47 +var currentTimerDebugID = null;
48 +var currentTimerStartTime = null;
49 +var currentTimerType = null;
50 +
51 +function resetMeasurements() {
52 + if (__DEV__) {
53 + if (!isProfiling || currentFlushNesting === 0) {
54 + currentFlushStartTime = null;
55 + currentFlushMeasurements = null;
56 + return;
57 + }
58 +
59 + var previousStartTime = currentFlushStartTime;
60 + var previousMeasurements = currentFlushMeasurements || [];
61 + var previousOperations = ReactNativeOperationHistoryDevtool.getHistory();
62 +
63 + if (previousMeasurements.length || previousOperations.length) {
64 + var registeredIDs = ReactComponentTreeDevtool.getRegisteredIDs();
65 + flushHistory.push({
66 + duration: performanceNow() - previousStartTime,
67 + measurements: previousMeasurements || [],
68 + operations: previousOperations || [],
69 + treeSnapshot: registeredIDs.reduce((tree, id) => {
70 + var ownerID = ReactComponentTreeDevtool.getOwnerID(id);
71 + var parentID = ReactComponentTreeDevtool.getParentID(id);
72 + tree[id] = {
73 + displayName: ReactComponentTreeDevtool.getDisplayName(id),
74 + text: ReactComponentTreeDevtool.getText(id),
75 + childIDs: ReactComponentTreeDevtool.getChildIDs(id),
76 + // Text nodes don't have owners but this is close enough.
77 + ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID),
78 + parentID,
79 + };
80 + return tree;
81 + }, {}),
82 + });
83 + }
84 +
85 + currentFlushStartTime = performanceNow();
86 + currentFlushMeasurements = [];
87 + ReactComponentTreeDevtool.purgeUnmountedComponents();
88 + ReactNativeOperationHistoryDevtool.clearHistory();
89 + }
90 +}
91 +
92 var ReactDebugTool = {
93 addDevtool(devtool) {
94 eventHandlers.push(devtool);
@@ -49,6 +101,95 @@ var ReactDebugTool = {
101 }
102 }
103 },
104 + beginProfiling() {
105 + if (__DEV__) {
106 + if (isProfiling) {
107 + return;
108 + }
109 +
110 + isProfiling = true;
111 + flushHistory.length = 0;
112 + resetMeasurements();
113 + }
114 + },
115 + endProfiling() {
116 + if (__DEV__) {
117 + if (!isProfiling) {
118 + return;
119 + }
120 +
121 + isProfiling = false;
122 + resetMeasurements();
123 + }
124 + },
125 + getFlushHistory() {
126 + if (__DEV__) {
127 + return flushHistory;
128 + }
129 + },
130 + onBeginFlush() {
131 + if (__DEV__) {
132 + currentFlushNesting++;
133 + resetMeasurements();
134 + }
135 + emitEvent('onBeginFlush');
136 + },
137 + onEndFlush() {
138 + if (__DEV__) {
139 + resetMeasurements();
140 + currentFlushNesting--;
141 + }
142 + emitEvent('onEndFlush');
143 + },
144 + onBeginLifeCycleTimer(debugID, timerType) {
145 + emitEvent('onBeginLifeCycleTimer', debugID, timerType);
146 + if (__DEV__) {
147 + if (isProfiling) {
148 + warning(
149 + !currentTimerType,
150 + 'There is an internal error in the React performance measurement code. ' +
151 + 'Did not expect %s timer to start while %s timer is still in ' +
152 + 'progress for %s instance.',
153 + timerType,
154 + currentTimerType || 'no',
155 + (debugID === currentTimerDebugID) ? 'the same' : 'another'
156 + );
157 + currentTimerStartTime = performanceNow();
158 + currentTimerDebugID = debugID;
159 + currentTimerType = timerType;
160 + }
161 + }
162 + },
163 + onEndLifeCycleTimer(debugID, timerType) {
164 + if (__DEV__) {
165 + if (isProfiling) {
166 + warning(
167 + currentTimerType === timerType,
168 + 'There is an internal error in the React performance measurement code. ' +
169 + 'We did not expect %s timer to stop while %s timer is still in ' +
170 + 'progress for %s instance. Please report this as a bug in React.',
171 + timerType,
172 + currentTimerType || 'no',
173 + (debugID === currentTimerDebugID) ? 'the same' : 'another'
174 + );
175 + currentFlushMeasurements.push({
176 + timerType,
177 + instanceID: debugID,
178 + duration: performance.now() - currentTimerStartTime,
179 + });
180 + currentTimerStartTime = null;
181 + currentTimerDebugID = null;
182 + currentTimerType = null;
183 + }
184 + }
185 + emitEvent('onEndLifeCycleTimer', debugID, timerType);
186 + },
187 + onBeginReconcilerTimer(debugID, timerType) {
188 + emitEvent('onBeginReconcilerTimer', debugID, timerType);
189 + },
190 + onEndReconcilerTimer(debugID, timerType) {
191 + emitEvent('onEndReconcilerTimer', debugID, timerType);
192 + },
193 onBeginProcessingChildContext() {
194 emitEvent('onBeginProcessingChildContext');
195 },
@@ -90,6 +231,17 @@ var ReactDebugTool = {
231 },
232 };
233
93 -ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
234 +if (__DEV__) {
235 + var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
236 + var ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool');
237 + var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
238 + ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
239 + ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
240 + ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool);
241 + var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
242 + if ((/[?&]react_perf\b/).test(url)) {
243 + ReactDebugTool.beginProfiling();
244 + }
245 +}
246
247 module.exports = ReactDebugTool;
src/isomorphic/ReactPerfAnalysis.js new
+361
@@ -0,0 +1,361 @@
1 +/**
2 + * Copyright 2016-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactPerfAnalysis
10 + */
11 +
12 +'use strict';
13 +
14 +var ReactDebugTool = require('ReactDebugTool');
15 +var warning = require('warning');
16 +
17 +function roundFloat(val, base = 2) {
18 + var n = Math.pow(10, base);
19 + return Math.floor(val * n) / n;
20 +}
21 +
22 +function getFlushHistory() {
23 + return ReactDebugTool.getFlushHistory();
24 +}
25 +
26 +function getExclusive(flushHistory = getFlushHistory()) {
27 + var aggregatedStats = {};
28 + var affectedIDs = {};
29 +
30 + function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
31 + var {displayName} = treeSnapshot[instanceID];
32 +
33 + var key = displayName;
34 + var stats = aggregatedStats[key];
35 +
36 + if (!stats) {
37 + affectedIDs[key] = {};
38 + stats = aggregatedStats[key] = {
39 + key,
40 + instanceCount: 0,
41 + counts: {},
42 + durations: {},
43 + totalDuration: 0,
44 + };
45 + }
46 +
47 + affectedIDs[key][instanceID] = true;
48 + applyUpdate(stats);
49 + }
50 +
51 + flushHistory.forEach(flush => {
52 + var {measurements, treeSnapshot} = flush;
53 + measurements.forEach(measurement => {
54 + var {duration, instanceID, timerType} = measurement;
55 + updateAggregatedStats(treeSnapshot, instanceID, stats => {
56 + stats.totalDuration += duration;
57 +
58 + if (!stats.durations[timerType]) {
59 + stats.durations[timerType] = 0;
60 + }
61 + stats.durations[timerType] += duration;
62 +
63 + if (!stats.counts[timerType]) {
64 + stats.counts[timerType] = 0;
65 + }
66 + stats.counts[timerType]++;
67 + });
68 + });
69 + });
70 +
71 + return Object.keys(aggregatedStats)
72 + .map(key => ({
73 + ...aggregatedStats[key],
74 + instanceCount: Object.keys(affectedIDs[key]).length,
75 + }))
76 + .sort((a, b) => b.totalDuration - a.totalDuration);
77 +}
78 +
79 +function getInclusive(flushHistory = getFlushHistory(), wastedOnly) {
80 + var aggregatedStats = {};
81 + var affectedIDs = {};
82 +
83 + function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
84 + var {displayName, ownerID} = treeSnapshot[instanceID];
85 +
86 + var owner = treeSnapshot[ownerID];
87 + var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`;
88 + var stats = aggregatedStats[key];
89 +
90 + if (!stats) {
91 + affectedIDs[key] = {};
92 + stats = aggregatedStats[key] = {
93 + key,
94 + instanceCount: 0,
95 + inclusiveRenderDuration: 0,
96 + renderCount: 0,
97 + };
98 + }
99 +
100 + affectedIDs[key][instanceID] = true;
101 + applyUpdate(stats);
102 + }
103 +
104 + var hasRenderedByID = {};
105 + flushHistory.forEach(flush => {
106 + var {measurements} = flush;
107 + measurements.forEach(measurement => {
108 + var {instanceID, timerType} = measurement;
109 + if (timerType !== 'render') {
110 + return;
111 + }
112 + hasRenderedByID[instanceID] = true;
113 + });
114 + });
115 +
116 + flushHistory.forEach(flush => {
117 + var {measurements, treeSnapshot} = flush;
118 + measurements.forEach(measurement => {
119 + var {duration, instanceID, timerType} = measurement;
120 + if (timerType !== 'render') {
121 + return;
122 + }
123 + updateAggregatedStats(treeSnapshot, instanceID, stats => {
124 + stats.renderCount++;
125 + });
126 + var nextParentID = instanceID;
127 + while (nextParentID) {
128 + if (hasRenderedByID[nextParentID]) {
129 + updateAggregatedStats(treeSnapshot, nextParentID, stats => {
130 + stats.inclusiveRenderDuration += duration;
131 + });
132 + }
133 + nextParentID = treeSnapshot[nextParentID].parentID;
134 + }
135 + });
136 + });
137 +
138 + return Object.keys(aggregatedStats)
139 + .map(key => ({
140 + ...aggregatedStats[key],
141 + instanceCount: Object.keys(affectedIDs[key]).length,
142 + }))
143 + .sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration);
144 +}
145 +
146 +function getWasted(flushHistory = getFlushHistory()) {
147 + var aggregatedStats = {};
148 + var affectedIDs = {};
149 +
150 + function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
151 + var {displayName, ownerID} = treeSnapshot[instanceID];
152 +
153 + var owner = treeSnapshot[ownerID];
154 + var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`;
155 + var stats = aggregatedStats[key];
156 +
157 + if (!stats) {
158 + affectedIDs[key] = {};
159 + stats = aggregatedStats[key] = {
160 + key,
161 + instanceCount: 0,
162 + inclusiveRenderDuration: 0,
163 + renderCount: 0,
164 + };
165 + }
166 +
167 + affectedIDs[key][instanceID] = true;
168 + applyUpdate(stats);
169 + }
170 +
171 + flushHistory.forEach(flush => {
172 + var {measurements, treeSnapshot, operations} = flush;
173 + var dirtyInstanceIDs = {};
174 +
175 + operations.forEach(operation => {
176 + var {instanceID} = operation;
177 +
178 + var nextParentID = instanceID;
179 + while (nextParentID) {
180 + dirtyInstanceIDs[nextParentID] = true;
181 + nextParentID = treeSnapshot[nextParentID].parentID;
182 + }
183 + });
184 +
185 + var renderedCompositeIDs = {};
186 + measurements.forEach(measurement => {
187 + var {instanceID, timerType} = measurement;
188 + if (timerType !== 'render') {
189 + return;
190 + }
191 + renderedCompositeIDs[instanceID] = true;
192 + });
193 +
194 + measurements.forEach(measurement => {
195 + var {duration, instanceID, timerType} = measurement;
196 + if (timerType !== 'render') {
197 + return;
198 + }
199 + var { updateCount } = treeSnapshot[instanceID];
200 + if (dirtyInstanceIDs[instanceID] || updateCount === 0) {
201 + return;
202 + }
203 + updateAggregatedStats(treeSnapshot, instanceID, stats => {
204 + stats.renderCount++;
205 + });
206 + var nextParentID = instanceID;
207 + while (nextParentID) {
208 + if (!renderedCompositeIDs[nextParentID]) {
209 + break;
210 + }
211 + updateAggregatedStats(treeSnapshot, nextParentID, stats => {
212 + stats.inclusiveRenderDuration += duration;
213 + });
214 + nextParentID = treeSnapshot[nextParentID].parentID;
215 + }
216 + });
217 + });
218 +
219 + return Object.keys(aggregatedStats)
220 + .map(key => ({
221 + ...aggregatedStats[key],
222 + instanceCount: Object.keys(affectedIDs[key]).length,
223 + }))
224 + .sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration);
225 +}
226 +
227 +function getOperations(flushHistory = getFlushHistory()) {
228 + var stats = [];
229 + flushHistory.forEach((flush, flushIndex) => {
230 + var {operations, treeSnapshot} = flush;
231 + operations.forEach(operation => {
232 + var {instanceID, type, payload} = operation;
233 + var {displayName, ownerID} = treeSnapshot[instanceID];
234 + var owner = treeSnapshot[ownerID];
235 + var key = `${(owner ? owner.displayName : '(no owner)')} > ${displayName}`;
236 +
237 + stats.push({
238 + flushIndex,
239 + instanceID,
240 + key,
241 + type,
242 + ownerID,
243 + payload,
244 + });
245 + });
246 + });
247 + return stats;
248 +}
249 +
250 +function printExclusive(flushHistory) {
251 + var stats = getExclusive(flushHistory);
252 + var table = stats.map(item => {
253 + var {key, instanceCount, totalDuration} = item;
254 + var renderCount = item.counts.render || 0;
255 + var renderDuration = item.durations.render || 0;
256 + return {
257 + 'Component': key,
258 + 'Total time (ms)': roundFloat(totalDuration),
259 + 'Instance count': instanceCount,
260 + 'Total render time (ms)': roundFloat(renderDuration),
261 + 'Average render time (ms)': renderCount ?
262 + roundFloat(renderDuration / renderCount) :
263 + undefined,
264 + 'Render count': renderCount,
265 + 'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration),
266 + };
267 + });
268 + console.table(table);
269 +}
270 +
271 +function printInclusive(flushHistory) {
272 + var stats = getInclusive(flushHistory);
273 + var table = stats.map(item => {
274 + var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
275 + return {
276 + 'Owner > Component': key,
277 + 'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration),
278 + 'Instance count': instanceCount,
279 + 'Render count': renderCount,
280 + };
281 + });
282 + console.table(table);
283 +}
284 +
285 +function printWasted(flushHistory) {
286 + var stats = getWasted(flushHistory);
287 + var table = stats.map(item => {
288 + var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
289 + return {
290 + 'Owner > Component': key,
291 + 'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration),
292 + 'Instance count': instanceCount,
293 + 'Render count': renderCount,
294 + };
295 + });
296 + console.table(table);
297 +}
298 +
299 +function printOperations(flushHistory) {
300 + var stats = getOperations(flushHistory);
301 + var table = stats.map(stat => ({
302 + 'Owner > Node': stat.key,
303 + 'Operation': stat.type,
304 + 'Payload': typeof stat.payload === 'object' ?
305 + JSON.stringify(stat.payload) :
306 + stat.payload,
307 + 'Flush index': stat.flushIndex,
308 + 'Owner Component ID': stat.ownerID,
309 + 'DOM Component ID': stat.instanceID,
310 + }));
311 + console.table(table);
312 +}
313 +
314 +var warnedAboutPrintDOM = false;
315 +function printDOM(measurements) {
316 + warning(
317 + warnedAboutPrintDOM,
318 + '`ReactPerf.printDOM(...)` is deprecated. Use ' +
319 + '`ReactPerf.printOperations(...)` instead.'
320 + );
321 + warnedAboutPrintDOM = true;
322 + return printOperations(measurements);
323 +}
324 +
325 +var warnedAboutGetMeasurementsSummaryMap = false;
326 +function getMeasurementsSummaryMap(measurements) {
327 + warning(
328 + warnedAboutGetMeasurementsSummaryMap,
329 + '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' +
330 + '`ReactPerf.getWasted(...)` instead.'
331 + );
332 + warnedAboutGetMeasurementsSummaryMap = true;
333 + return getWasted(measurements);
334 +}
335 +
336 +function start() {
337 + ReactDebugTool.beginProfiling();
338 +}
339 +
340 +function stop() {
341 + ReactDebugTool.endProfiling();
342 +}
343 +
344 +var ReactPerfAnalysis = {
345 + getFlushHistory,
346 + getExclusive,
347 + getInclusive,
348 + getWasted,
349 + getOperations,
350 + printExclusive,
351 + printInclusive,
352 + printWasted,
353 + printOperations,
354 + start,
355 + stop,
356 + // Deprecated:
357 + printDOM,
358 + getMeasurementsSummaryMap,
359 +};
360 +
361 +module.exports = ReactPerfAnalysis;
src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js
-8
@@ -13,7 +13,6 @@
13
14 describe('ReactComponentTreeDevtool', () => {
15 var React;
16 - var ReactDebugTool;
16 var ReactDOM;
17 var ReactDOMServer;
18 var ReactInstanceMap;
@@ -23,17 +22,10 @@ describe('ReactComponentTreeDevtool', () => {
22 jest.resetModuleRegistry();
23
24 React = require('React');
26 - ReactDebugTool = require('ReactDebugTool');
25 ReactDOM = require('ReactDOM');
26 ReactDOMServer = require('ReactDOMServer');
27 ReactInstanceMap = require('ReactInstanceMap');
28 ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
31 -
32 - ReactDebugTool.addDevtool(ReactComponentTreeDevtool);
33 - });
34 -
35 - afterEach(() => {
36 - ReactDebugTool.removeDevtool(ReactComponentTreeDevtool);
29 });
30
31 function getRootDisplayNames() {
src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js
-8
@@ -13,7 +13,6 @@
13
14 describe('ReactNativeOperationHistoryDevtool', () => {
15 var React;
16 - var ReactDebugTool;
16 var ReactDOM;
17 var ReactDOMComponentTree;
18 var ReactDOMFeatureFlags;
@@ -23,17 +22,10 @@ describe('ReactNativeOperationHistoryDevtool', () => {
22 jest.resetModuleRegistry();
23
24 React = require('React');
26 - ReactDebugTool = require('ReactDebugTool');
25 ReactDOM = require('ReactDOM');
26 ReactDOMComponentTree = require('ReactDOMComponentTree');
27 ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
28 ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool');
31 -
32 - ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool);
33 - });
34 -
35 - afterEach(() => {
36 - ReactDebugTool.removeDevtool(ReactNativeOperationHistoryDevtool);
29 });
30
31 function assertHistoryMatches(expectedHistory) {
src/renderers/dom/client/ReactMount.js
+5
@@ -308,6 +308,10 @@ var ReactMount = {
308 shouldReuseMarkup,
309 context
310 ) {
311 + if (__DEV__) {
312 + ReactInstrumentation.debugTool.onBeginFlush();
313 + }
314 +
315 // Various parts of our code (such as ReactCompositeComponent's
316 // _renderValidatedComponent) assume that calls to render aren't nested;
317 // verify that that's the case.
@@ -359,6 +363,7 @@ var ReactMount = {
363 ReactInstrumentation.debugTool.onMountRootComponent(
364 componentInstance._renderedComponent._debugID
365 );
366 + ReactInstrumentation.debugTool.onEndFlush();
367 }
368
369 return componentInstance;
src/renderers/dom/shared/ReactDOMComponent.js
+5 -3
@@ -1115,9 +1115,11 @@ ReactDOMComponent.Mixin = {
1115 this._domID = null;
1116 this._wrapperState = null;
1117
1118 - if (this._contentDebugID) {
1119 - ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID);
1120 - this._contentDebugID = null;
1118 + if (__DEV__) {
1119 + if (this._contentDebugID) {
1120 + ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID);
1121 + this._contentDebugID = null;
1122 + }
1123 }
1124 },
1125
src/renderers/dom/shared/ReactDOMTextComponent.js
+4 -1
@@ -145,7 +145,10 @@ Object.assign(ReactDOMTextComponent.prototype, {
145 );
146
147 if (__DEV__) {
148 - ReactInstrumentation.debugTool.onSetText(this._debugID, nextStringText);
148 + ReactInstrumentation.debugTool.onSetText(
149 + this._debugID,
150 + nextStringText
151 + );
152 }
153 }
154 }
src/renderers/shared/reconciler/ReactCompositeComponent.js
+191 -11
@@ -60,6 +60,40 @@ function warnIfInvalidElement(Component, element) {
60 }
61 }
62
63 +function invokeComponentDidMountWithTimer() {
64 + var publicInstance = this._instance;
65 + if (this._debugID !== 0) {
66 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
67 + this._debugID,
68 + 'componentDidMount'
69 + );
70 + }
71 + publicInstance.componentDidMount();
72 + if (this._debugID !== 0) {
73 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
74 + this._debugID,
75 + 'componentDidMount'
76 + );
77 + }
78 +}
79 +
80 +function invokeComponentDidUpdateWithTimer(prevProps, prevState, prevContext) {
81 + var publicInstance = this._instance;
82 + if (this._debugID !== 0) {
83 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
84 + this._debugID,
85 + 'componentDidUpdate'
86 + );
87 + }
88 + publicInstance.componentDidUpdate(prevProps, prevState, prevContext);
89 + if (this._debugID !== 0) {
90 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
91 + this._debugID,
92 + 'componentDidUpdate'
93 + );
94 + }
95 +}
96 +
97 function shouldConstruct(Component) {
98 return Component.prototype && Component.prototype.isReactComponent;
99 }
@@ -302,7 +336,11 @@ var ReactCompositeComponentMixin = {
336 }
337
338 if (inst.componentDidMount) {
305 - transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
339 + if (__DEV__) {
340 + transaction.getReactMountReady().enqueue(invokeComponentDidMountWithTimer, this);
341 + } else {
342 + transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
343 + }
344 }
345
346 return markup;
@@ -323,11 +361,47 @@ var ReactCompositeComponentMixin = {
361
362 _constructComponentWithoutOwner: function(publicProps, publicContext) {
363 var Component = this._currentElement.type;
364 + var instanceOrElement;
365 if (shouldConstruct(Component)) {
327 - return new Component(publicProps, publicContext, ReactUpdateQueue);
366 + if (__DEV__) {
367 + if (this._debugID !== 0) {
368 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
369 + this._debugID,
370 + 'ctor'
371 + );
372 + }
373 + }
374 + instanceOrElement = new Component(publicProps, publicContext, ReactUpdateQueue);
375 + if (__DEV__) {
376 + if (this._debugID !== 0) {
377 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
378 + this._debugID,
379 + 'ctor'
380 + );
381 + }
382 + }
383 } else {
329 - return Component(publicProps, publicContext, ReactUpdateQueue);
384 + // This can still be an instance in case of factory components
385 + // but we'll count this as time spent rendering as the more common case.
386 + if (__DEV__) {
387 + if (this._debugID !== 0) {
388 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
389 + this._debugID,
390 + 'render'
391 + );
392 + }
393 + }
394 + instanceOrElement = Component(publicProps, publicContext, ReactUpdateQueue);
395 + if (__DEV__) {
396 + if (this._debugID !== 0) {
397 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
398 + this._debugID,
399 + 'render'
400 + );
401 + }
402 + }
403 }
404 + return instanceOrElement;
405 },
406
407 performInitialMountWithErrorHandling: function(
@@ -363,7 +437,23 @@ var ReactCompositeComponentMixin = {
437 performInitialMount: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
438 var inst = this._instance;
439 if (inst.componentWillMount) {
440 + if (__DEV__) {
441 + if (this._debugID !== 0) {
442 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
443 + this._debugID,
444 + 'componentWillMount'
445 + );
446 + }
447 + }
448 inst.componentWillMount();
449 + if (__DEV__) {
450 + if (this._debugID !== 0) {
451 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
452 + this._debugID,
453 + 'componentWillMount'
454 + );
455 + }
456 + }
457 // When mounting, calls to `setState` by `componentWillMount` will set
458 // `this._pendingStateQueue` without triggering a re-render.
459 if (this._pendingStateQueue) {
@@ -421,12 +511,28 @@ var ReactCompositeComponentMixin = {
511
512 if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
513 inst._calledComponentWillUnmount = true;
514 + if (__DEV__) {
515 + if (this._debugID !== 0) {
516 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
517 + this._debugID,
518 + 'componentWillUnmount'
519 + );
520 + }
521 + }
522 if (safely) {
523 var name = this.getName() + '.componentWillUnmount()';
524 ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
525 } else {
526 inst.componentWillUnmount();
527 }
528 + if (__DEV__) {
529 + if (this._debugID !== 0) {
530 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
531 + this._debugID,
532 + 'componentWillUnmount'
533 + );
534 + }
535 + }
536 }
537
538 if (this._renderedComponent) {
@@ -721,15 +827,47 @@ var ReactCompositeComponentMixin = {
827 // _pendingStateQueue which will ensure that any state updates gets
828 // immediately reconciled instead of waiting for the next batch.
829 if (willReceive && inst.componentWillReceiveProps) {
830 + if (__DEV__) {
831 + if (this._debugID !== 0) {
832 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
833 + this._debugID,
834 + 'componentWillReceiveProps'
835 + );
836 + }
837 + }
838 inst.componentWillReceiveProps(nextProps, nextContext);
839 + if (__DEV__) {
840 + if (this._debugID !== 0) {
841 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
842 + this._debugID,
843 + 'componentWillReceiveProps'
844 + );
845 + }
846 + }
847 }
848
849 var nextState = this._processPendingState(nextProps, nextContext);
850 + var shouldUpdate = true;
851
729 - var shouldUpdate =
730 - this._pendingForceUpdate ||
731 - !inst.shouldComponentUpdate ||
732 - inst.shouldComponentUpdate(nextProps, nextState, nextContext);
852 + if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
853 + if (__DEV__) {
854 + if (this._debugID !== 0) {
855 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
856 + this._debugID,
857 + 'shouldComponentUpdate'
858 + );
859 + }
860 + }
861 + shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
862 + if (__DEV__) {
863 + if (this._debugID !== 0) {
864 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
865 + this._debugID,
866 + 'shouldComponentUpdate'
867 + );
868 + }
869 + }
870 + }
871
872 if (__DEV__) {
873 warning(
@@ -824,7 +962,23 @@ var ReactCompositeComponentMixin = {
962 }
963
964 if (inst.componentWillUpdate) {
965 + if (__DEV__) {
966 + if (this._debugID !== 0) {
967 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
968 + this._debugID,
969 + 'componentWillUpdate'
970 + );
971 + }
972 + }
973 inst.componentWillUpdate(nextProps, nextState, nextContext);
974 + if (__DEV__) {
975 + if (this._debugID !== 0) {
976 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
977 + this._debugID,
978 + 'componentWillUpdate'
979 + );
980 + }
981 + }
982 }
983
984 this._currentElement = nextElement;
@@ -836,10 +990,17 @@ var ReactCompositeComponentMixin = {
990 this._updateRenderedComponent(transaction, unmaskedContext);
991
992 if (hasComponentDidUpdate) {
839 - transaction.getReactMountReady().enqueue(
840 - inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
841 - inst
842 - );
993 + if (__DEV__) {
994 + transaction.getReactMountReady().enqueue(
995 + invokeComponentDidUpdateWithTimer.bind(this, prevProps, prevState, prevContext),
996 + this
997 + );
998 + } else {
999 + transaction.getReactMountReady().enqueue(
1000 + inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
1001 + inst
1002 + );
1003 + }
1004 }
1005 },
1006
@@ -914,7 +1075,25 @@ var ReactCompositeComponentMixin = {
1075 */
1076 _renderValidatedComponentWithoutOwnerOrContext: function() {
1077 var inst = this._instance;
1078 +
1079 + if (__DEV__) {
1080 + if (this._debugID !== 0) {
1081 + ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
1082 + this._debugID,
1083 + 'render'
1084 + );
1085 + }
1086 + }
1087 var renderedComponent = inst.render();
1088 + if (__DEV__) {
1089 + if (this._debugID !== 0) {
1090 + ReactInstrumentation.debugTool.onEndLifeCycleTimer(
1091 + this._debugID,
1092 + 'render'
1093 + );
1094 + }
1095 + }
1096 +
1097 if (__DEV__) {
1098 // We allow auto-mocks to proceed as if they're returning null.
1099 if (renderedComponent === undefined &&
@@ -948,6 +1127,7 @@ var ReactCompositeComponentMixin = {
1127 'returned undefined, an array or some other invalid object.',
1128 this.getName() || 'ReactCompositeComponent'
1129 );
1130 +
1131 return renderedComponent;
1132 },
1133
src/renderers/shared/reconciler/ReactReconciler.js
+61 -4
@@ -42,6 +42,14 @@ var ReactReconciler = {
42 nativeContainerInfo,
43 context
44 ) {
45 + if (__DEV__) {
46 + if (internalInstance._debugID !== 0) {
47 + ReactInstrumentation.debugTool.onBeginReconcilerTimer(
48 + internalInstance._debugID,
49 + 'mountComponent'
50 + );
51 + }
52 + }
53 var markup = internalInstance.mountComponent(
54 transaction,
55 nativeParent,
@@ -54,7 +62,13 @@ var ReactReconciler = {
62 }
63 if (__DEV__) {
64 if (internalInstance._debugID !== 0) {
57 - ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID);
65 + ReactInstrumentation.debugTool.onEndReconcilerTimer(
66 + internalInstance._debugID,
67 + 'mountComponent'
68 + );
69 + ReactInstrumentation.debugTool.onMountComponent(
70 + internalInstance._debugID
71 + );
72 }
73 }
74 return markup;
@@ -75,11 +89,25 @@ var ReactReconciler = {
89 * @internal
90 */
91 unmountComponent: function(internalInstance, safely) {
92 + if (__DEV__) {
93 + if (internalInstance._debugID !== 0) {
94 + ReactInstrumentation.debugTool.onBeginReconcilerTimer(
95 + internalInstance._debugID,
96 + 'unmountComponent'
97 + );
98 + }
99 + }
100 ReactRef.detachRefs(internalInstance, internalInstance._currentElement);
101 internalInstance.unmountComponent(safely);
102 if (__DEV__) {
103 if (internalInstance._debugID !== 0) {
82 - ReactInstrumentation.debugTool.onUnmountComponent(internalInstance._debugID);
104 + ReactInstrumentation.debugTool.onEndReconcilerTimer(
105 + internalInstance._debugID,
106 + 'unmountComponent'
107 + );
108 + ReactInstrumentation.debugTool.onUnmountComponent(
109 + internalInstance._debugID
110 + );
111 }
112 }
113 },
@@ -114,6 +142,15 @@ var ReactReconciler = {
142 return;
143 }
144
145 + if (__DEV__) {
146 + if (internalInstance._debugID !== 0) {
147 + ReactInstrumentation.debugTool.onBeginReconcilerTimer(
148 + internalInstance._debugID,
149 + 'receiveComponent'
150 + );
151 + }
152 + }
153 +
154 var refsChanged = ReactRef.shouldUpdateRefs(
155 prevElement,
156 nextElement
@@ -133,7 +170,13 @@ var ReactReconciler = {
170
171 if (__DEV__) {
172 if (internalInstance._debugID !== 0) {
136 - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
173 + ReactInstrumentation.debugTool.onEndReconcilerTimer(
174 + internalInstance._debugID,
175 + 'receiveComponent'
176 + );
177 + ReactInstrumentation.debugTool.onUpdateComponent(
178 + internalInstance._debugID
179 + );
180 }
181 }
182 },
@@ -149,10 +192,24 @@ var ReactReconciler = {
192 internalInstance,
193 transaction
194 ) {
195 + if (__DEV__) {
196 + if (internalInstance._debugID !== 0) {
197 + ReactInstrumentation.debugTool.onBeginReconcilerTimer(
198 + internalInstance._debugID,
199 + 'performUpdateIfNecessary'
200 + );
201 + }
202 + }
203 internalInstance.performUpdateIfNecessary(transaction);
204 if (__DEV__) {
205 if (internalInstance._debugID !== 0) {
155 - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
206 + ReactInstrumentation.debugTool.onEndReconcilerTimer(
207 + internalInstance._debugID,
208 + 'performUpdateIfNecessary'
209 + );
210 + ReactInstrumentation.debugTool.onUpdateComponent(
211 + internalInstance._debugID
212 + );
213 }
214 }
215 },
src/renderers/shared/reconciler/ReactUpdates.js
+9
@@ -14,6 +14,7 @@
14 var CallbackQueue = require('CallbackQueue');
15 var PooledClass = require('PooledClass');
16 var ReactFeatureFlags = require('ReactFeatureFlags');
17 +var ReactInstrumentation = require('ReactInstrumentation');
18 var ReactPerf = require('ReactPerf');
19 var ReactReconciler = require('ReactReconciler');
20 var Transaction = require('Transaction');
@@ -184,6 +185,10 @@ function runBatchedUpdates(transaction) {
185 }
186
187 var flushBatchedUpdates = function() {
188 + if (__DEV__) {
189 + ReactInstrumentation.debugTool.onBeginFlush();
190 + }
191 +
192 // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
193 // array and perform any updates enqueued by mount-ready handlers (i.e.,
194 // componentDidUpdate) but we need to check here too in order to catch
@@ -203,6 +208,10 @@ var flushBatchedUpdates = function() {
208 CallbackQueue.release(queue);
209 }
210 }
211 +
212 + if (__DEV__) {
213 + ReactInstrumentation.debugTool.onEndFlush();
214 + }
215 };
216 flushBatchedUpdates = ReactPerf.measure(
217 'ReactUpdates',