UNPKG

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