UNPKG

14.8 kBJavaScriptView Raw
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 *
10 */
11
12'use strict';
13
14var _assign = require('object-assign');
15
16var _extends = _assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
17
18var ReactDebugTool = require('./ReactDebugTool');
19var lowPriorityWarning = require('./lowPriorityWarning');
20var alreadyWarned = false;
21
22function roundFloat(val) {
23 var base = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
24
25 var n = Math.pow(10, base);
26 return Math.floor(val * n) / n;
27}
28
29// Flow type definition of console.table is too strict right now, see
30// https://github.com/facebook/flow/pull/2353 for updates
31function consoleTable(table) {
32 console.table(table);
33}
34
35function warnInProduction() {
36 if (alreadyWarned) {
37 return;
38 }
39 alreadyWarned = true;
40 if (typeof console !== 'undefined') {
41 console.error('ReactPerf is not supported in the production builds of React. ' + 'To collect measurements, please use the development build of React instead.');
42 }
43}
44
45function getLastMeasurements() {
46 if (!(process.env.NODE_ENV !== 'production')) {
47 warnInProduction();
48 return [];
49 }
50
51 return ReactDebugTool.getFlushHistory();
52}
53
54function getExclusive() {
55 var flushHistory = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getLastMeasurements();
56
57 if (!(process.env.NODE_ENV !== 'production')) {
58 warnInProduction();
59 return [];
60 }
61
62 var aggregatedStats = {};
63 var affectedIDs = {};
64
65 function updateAggregatedStats(treeSnapshot, instanceID, timerType, applyUpdate) {
66 var displayName = treeSnapshot[instanceID].displayName;
67
68 var key = displayName;
69 var stats = aggregatedStats[key];
70 if (!stats) {
71 affectedIDs[key] = {};
72 stats = aggregatedStats[key] = {
73 key: key,
74 instanceCount: 0,
75 counts: {},
76 durations: {},
77 totalDuration: 0
78 };
79 }
80 if (!stats.durations[timerType]) {
81 stats.durations[timerType] = 0;
82 }
83 if (!stats.counts[timerType]) {
84 stats.counts[timerType] = 0;
85 }
86 affectedIDs[key][instanceID] = true;
87 applyUpdate(stats);
88 }
89
90 flushHistory.forEach(function (flush) {
91 var measurements = flush.measurements,
92 treeSnapshot = flush.treeSnapshot;
93
94 measurements.forEach(function (measurement) {
95 var duration = measurement.duration,
96 instanceID = measurement.instanceID,
97 timerType = measurement.timerType;
98
99 updateAggregatedStats(treeSnapshot, instanceID, timerType, function (stats) {
100 stats.totalDuration += duration;
101 stats.durations[timerType] += duration;
102 stats.counts[timerType]++;
103 });
104 });
105 });
106
107 return Object.keys(aggregatedStats).map(function (key) {
108 return _extends({}, aggregatedStats[key], {
109 instanceCount: Object.keys(affectedIDs[key]).length
110 });
111 }).sort(function (a, b) {
112 return b.totalDuration - a.totalDuration;
113 });
114}
115
116function getInclusive() {
117 var flushHistory = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getLastMeasurements();
118
119 if (!(process.env.NODE_ENV !== 'production')) {
120 warnInProduction();
121 return [];
122 }
123
124 var aggregatedStats = {};
125 var affectedIDs = {};
126
127 function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
128 var _treeSnapshot$instanc = treeSnapshot[instanceID],
129 displayName = _treeSnapshot$instanc.displayName,
130 ownerID = _treeSnapshot$instanc.ownerID;
131
132 var owner = treeSnapshot[ownerID];
133 var key = (owner ? owner.displayName + ' > ' : '') + displayName;
134 var stats = aggregatedStats[key];
135 if (!stats) {
136 affectedIDs[key] = {};
137 stats = aggregatedStats[key] = {
138 key: key,
139 instanceCount: 0,
140 inclusiveRenderDuration: 0,
141 renderCount: 0
142 };
143 }
144 affectedIDs[key][instanceID] = true;
145 applyUpdate(stats);
146 }
147
148 var isCompositeByID = {};
149 flushHistory.forEach(function (flush) {
150 var measurements = flush.measurements;
151
152 measurements.forEach(function (measurement) {
153 var instanceID = measurement.instanceID,
154 timerType = measurement.timerType;
155
156 if (timerType !== 'render') {
157 return;
158 }
159 isCompositeByID[instanceID] = true;
160 });
161 });
162
163 flushHistory.forEach(function (flush) {
164 var measurements = flush.measurements,
165 treeSnapshot = flush.treeSnapshot;
166
167 measurements.forEach(function (measurement) {
168 var duration = measurement.duration,
169 instanceID = measurement.instanceID,
170 timerType = measurement.timerType;
171
172 if (timerType !== 'render') {
173 return;
174 }
175 updateAggregatedStats(treeSnapshot, instanceID, function (stats) {
176 stats.renderCount++;
177 });
178 var nextParentID = instanceID;
179 while (nextParentID) {
180 // As we traverse parents, only count inclusive time towards composites.
181 // We know something is a composite if its render() was called.
182 if (isCompositeByID[nextParentID]) {
183 updateAggregatedStats(treeSnapshot, nextParentID, function (stats) {
184 stats.inclusiveRenderDuration += duration;
185 });
186 }
187 nextParentID = treeSnapshot[nextParentID].parentID;
188 }
189 });
190 });
191
192 return Object.keys(aggregatedStats).map(function (key) {
193 return _extends({}, aggregatedStats[key], {
194 instanceCount: Object.keys(affectedIDs[key]).length
195 });
196 }).sort(function (a, b) {
197 return b.inclusiveRenderDuration - a.inclusiveRenderDuration;
198 });
199}
200
201function getWasted() {
202 var flushHistory = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getLastMeasurements();
203
204 if (!(process.env.NODE_ENV !== 'production')) {
205 warnInProduction();
206 return [];
207 }
208
209 var aggregatedStats = {};
210 var affectedIDs = {};
211
212 function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
213 var _treeSnapshot$instanc2 = treeSnapshot[instanceID],
214 displayName = _treeSnapshot$instanc2.displayName,
215 ownerID = _treeSnapshot$instanc2.ownerID;
216
217 var owner = treeSnapshot[ownerID];
218 var key = (owner ? owner.displayName + ' > ' : '') + displayName;
219 var stats = aggregatedStats[key];
220 if (!stats) {
221 affectedIDs[key] = {};
222 stats = aggregatedStats[key] = {
223 key: key,
224 instanceCount: 0,
225 inclusiveRenderDuration: 0,
226 renderCount: 0
227 };
228 }
229 affectedIDs[key][instanceID] = true;
230 applyUpdate(stats);
231 }
232
233 flushHistory.forEach(function (flush) {
234 var measurements = flush.measurements,
235 treeSnapshot = flush.treeSnapshot,
236 operations = flush.operations;
237
238 var isDefinitelyNotWastedByID = {};
239
240 // Find host components associated with an operation in this batch.
241 // Mark all components in their parent tree as definitely not wasted.
242 operations.forEach(function (operation) {
243 var instanceID = operation.instanceID;
244
245 var nextParentID = instanceID;
246 while (nextParentID) {
247 isDefinitelyNotWastedByID[nextParentID] = true;
248 nextParentID = treeSnapshot[nextParentID].parentID;
249 }
250 });
251
252 // Find composite components that rendered in this batch.
253 // These are potential candidates for being wasted renders.
254 var renderedCompositeIDs = {};
255 measurements.forEach(function (measurement) {
256 var instanceID = measurement.instanceID,
257 timerType = measurement.timerType;
258
259 if (timerType !== 'render') {
260 return;
261 }
262 renderedCompositeIDs[instanceID] = true;
263 });
264
265 measurements.forEach(function (measurement) {
266 var duration = measurement.duration,
267 instanceID = measurement.instanceID,
268 timerType = measurement.timerType;
269
270 if (timerType !== 'render') {
271 return;
272 }
273
274 // If there was a DOM update below this component, or it has just been
275 // mounted, its render() is not considered wasted.
276 var updateCount = treeSnapshot[instanceID].updateCount;
277
278 if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) {
279 return;
280 }
281
282 // We consider this render() wasted.
283 updateAggregatedStats(treeSnapshot, instanceID, function (stats) {
284 stats.renderCount++;
285 });
286
287 var nextParentID = instanceID;
288 while (nextParentID) {
289 // Any parents rendered during this batch are considered wasted
290 // unless we previously marked them as dirty.
291 var isWasted = renderedCompositeIDs[nextParentID] && !isDefinitelyNotWastedByID[nextParentID];
292 if (isWasted) {
293 updateAggregatedStats(treeSnapshot, nextParentID, function (stats) {
294 stats.inclusiveRenderDuration += duration;
295 });
296 }
297 nextParentID = treeSnapshot[nextParentID].parentID;
298 }
299 });
300 });
301
302 return Object.keys(aggregatedStats).map(function (key) {
303 return _extends({}, aggregatedStats[key], {
304 instanceCount: Object.keys(affectedIDs[key]).length
305 });
306 }).sort(function (a, b) {
307 return b.inclusiveRenderDuration - a.inclusiveRenderDuration;
308 });
309}
310
311function getOperations() {
312 var flushHistory = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getLastMeasurements();
313
314 if (!(process.env.NODE_ENV !== 'production')) {
315 warnInProduction();
316 return [];
317 }
318
319 var stats = [];
320 flushHistory.forEach(function (flush, flushIndex) {
321 var operations = flush.operations,
322 treeSnapshot = flush.treeSnapshot;
323
324 operations.forEach(function (operation) {
325 var instanceID = operation.instanceID,
326 type = operation.type,
327 payload = operation.payload;
328 var _treeSnapshot$instanc3 = treeSnapshot[instanceID],
329 displayName = _treeSnapshot$instanc3.displayName,
330 ownerID = _treeSnapshot$instanc3.ownerID;
331
332 var owner = treeSnapshot[ownerID];
333 var key = (owner ? owner.displayName + ' > ' : '') + displayName;
334
335 stats.push({
336 flushIndex: flushIndex,
337 instanceID: instanceID,
338 key: key,
339 type: type,
340 ownerID: ownerID,
341 payload: payload
342 });
343 });
344 });
345 return stats;
346}
347
348function printExclusive(flushHistory) {
349 if (!(process.env.NODE_ENV !== 'production')) {
350 warnInProduction();
351 return;
352 }
353
354 var stats = getExclusive(flushHistory);
355 var table = stats.map(function (item) {
356 var key = item.key,
357 instanceCount = item.instanceCount,
358 totalDuration = item.totalDuration;
359
360 var renderCount = item.counts.render || 0;
361 var renderDuration = item.durations.render || 0;
362 return {
363 Component: key,
364 'Total time (ms)': roundFloat(totalDuration),
365 'Instance count': instanceCount,
366 'Total render time (ms)': roundFloat(renderDuration),
367 'Average render time (ms)': renderCount ? roundFloat(renderDuration / renderCount) : undefined,
368 'Render count': renderCount,
369 'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration)
370 };
371 });
372 consoleTable(table);
373}
374
375function printInclusive(flushHistory) {
376 if (!(process.env.NODE_ENV !== 'production')) {
377 warnInProduction();
378 return;
379 }
380
381 var stats = getInclusive(flushHistory);
382 var table = stats.map(function (item) {
383 var key = item.key,
384 instanceCount = item.instanceCount,
385 inclusiveRenderDuration = item.inclusiveRenderDuration,
386 renderCount = item.renderCount;
387
388 return {
389 'Owner > Component': key,
390 'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration),
391 'Instance count': instanceCount,
392 'Render count': renderCount
393 };
394 });
395 consoleTable(table);
396}
397
398function printWasted(flushHistory) {
399 if (!(process.env.NODE_ENV !== 'production')) {
400 warnInProduction();
401 return;
402 }
403
404 var stats = getWasted(flushHistory);
405 var table = stats.map(function (item) {
406 var key = item.key,
407 instanceCount = item.instanceCount,
408 inclusiveRenderDuration = item.inclusiveRenderDuration,
409 renderCount = item.renderCount;
410
411 return {
412 'Owner > Component': key,
413 'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration),
414 'Instance count': instanceCount,
415 'Render count': renderCount
416 };
417 });
418 consoleTable(table);
419}
420
421function printOperations(flushHistory) {
422 if (!(process.env.NODE_ENV !== 'production')) {
423 warnInProduction();
424 return;
425 }
426
427 var stats = getOperations(flushHistory);
428 var table = stats.map(function (stat) {
429 return {
430 'Owner > Node': stat.key,
431 Operation: stat.type,
432 Payload: typeof stat.payload === 'object' ? JSON.stringify(stat.payload) : stat.payload,
433 'Flush index': stat.flushIndex,
434 'Owner Component ID': stat.ownerID,
435 'DOM Component ID': stat.instanceID
436 };
437 });
438 consoleTable(table);
439}
440
441var warnedAboutPrintDOM = false;
442function printDOM(measurements) {
443 lowPriorityWarning(warnedAboutPrintDOM, '`ReactPerf.printDOM(...)` is deprecated. Use ' + '`ReactPerf.printOperations(...)` instead.');
444 warnedAboutPrintDOM = true;
445 return printOperations(measurements);
446}
447
448var warnedAboutGetMeasurementsSummaryMap = false;
449function getMeasurementsSummaryMap(measurements) {
450 lowPriorityWarning(warnedAboutGetMeasurementsSummaryMap, '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + '`ReactPerf.getWasted(...)` instead.');
451 warnedAboutGetMeasurementsSummaryMap = true;
452 return getWasted(measurements);
453}
454
455function start() {
456 if (!(process.env.NODE_ENV !== 'production')) {
457 warnInProduction();
458 return;
459 }
460
461 ReactDebugTool.beginProfiling();
462}
463
464function stop() {
465 if (!(process.env.NODE_ENV !== 'production')) {
466 warnInProduction();
467 return;
468 }
469
470 ReactDebugTool.endProfiling();
471}
472
473function isRunning() {
474 if (!(process.env.NODE_ENV !== 'production')) {
475 warnInProduction();
476 return false;
477 }
478
479 return ReactDebugTool.isProfiling();
480}
481
482var ReactPerfAnalysis = {
483 getLastMeasurements: getLastMeasurements,
484 getExclusive: getExclusive,
485 getInclusive: getInclusive,
486 getWasted: getWasted,
487 getOperations: getOperations,
488 printExclusive: printExclusive,
489 printInclusive: printInclusive,
490 printWasted: printWasted,
491 printOperations: printOperations,
492 start: start,
493 stop: stop,
494 isRunning: isRunning,
495 // Deprecated:
496 printDOM: printDOM,
497 getMeasurementsSummaryMap: getMeasurementsSummaryMap
498};
499
500module.exports = ReactPerfAnalysis;
\No newline at end of file