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',