/** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactDefaultPerf * @typechecks static-only */ 'use strict'; var DOMProperty = require("./DOMProperty"); var ReactDefaultPerfAnalysis = require("./ReactDefaultPerfAnalysis"); var ReactMount = require("./ReactMount"); var ReactPerf = require("./ReactPerf"); var performanceNow = require("./performanceNow"); function roundFloat(val) { return Math.floor(val * 100) / 100; } function addValue(obj, key, val) { obj[key] = (obj[key] || 0) + val; } var ReactDefaultPerf = { _allMeasurements: [], // last item in the list is the current one _mountStack: [0], _injected: false, start: function() { if (!ReactDefaultPerf._injected) { ReactPerf.injection.injectMeasure(ReactDefaultPerf.measure); } ReactDefaultPerf._allMeasurements.length = 0; ReactPerf.enableMeasure = true; }, stop: function() { ReactPerf.enableMeasure = false; }, getLastMeasurements: function() { return ReactDefaultPerf._allMeasurements; }, printExclusive: function(measurements) { measurements = measurements || ReactDefaultPerf._allMeasurements; var summary = ReactDefaultPerfAnalysis.getExclusiveSummary(measurements); console.table(summary.map(function(item) { return { 'Component class name': item.componentName, 'Total inclusive time (ms)': roundFloat(item.inclusive), 'Exclusive mount time (ms)': roundFloat(item.exclusive), 'Exclusive render time (ms)': roundFloat(item.render), 'Mount time per instance (ms)': roundFloat(item.exclusive / item.count), 'Render time per instance (ms)': roundFloat(item.render / item.count), 'Instances': item.count }; })); // TODO: ReactDefaultPerfAnalysis.getTotalTime() does not return the correct // number. }, printInclusive: function(measurements) { measurements = measurements || ReactDefaultPerf._allMeasurements; var summary = ReactDefaultPerfAnalysis.getInclusiveSummary(measurements); console.table(summary.map(function(item) { return { 'Owner > component': item.componentName, 'Inclusive time (ms)': roundFloat(item.time), 'Instances': item.count }; })); console.log( 'Total time:', ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms' ); }, getMeasurementsSummaryMap: function(measurements) { var summary = ReactDefaultPerfAnalysis.getInclusiveSummary( measurements, true ); return summary.map(function(item) { return { 'Owner > component': item.componentName, 'Wasted time (ms)': item.time, 'Instances': item.count }; }); }, printWasted: function(measurements) { measurements = measurements || ReactDefaultPerf._allMeasurements; console.table(ReactDefaultPerf.getMeasurementsSummaryMap(measurements)); console.log( 'Total time:', ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms' ); }, printDOM: function(measurements) { measurements = measurements || ReactDefaultPerf._allMeasurements; var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements); console.table(summary.map(function(item) { var result = {}; result[DOMProperty.ID_ATTRIBUTE_NAME] = item.id; result['type'] = item.type; result['args'] = JSON.stringify(item.args); return result; })); console.log( 'Total time:', ReactDefaultPerfAnalysis.getTotalTime(measurements).toFixed(2) + ' ms' ); }, _recordWrite: function(id, fnName, totalTime, args) { // TODO: totalTime isn't that useful since it doesn't count paints/reflows var writes = ReactDefaultPerf ._allMeasurements[ReactDefaultPerf._allMeasurements.length - 1] .writes; writes[id] = writes[id] || []; writes[id].push({ type: fnName, time: totalTime, args: args }); }, measure: function(moduleName, fnName, func) { return function() {for (var args=[],$__0=0,$__1=arguments.length;$__0<$__1;$__0++) args.push(arguments[$__0]); var totalTime; var rv; var start; if (fnName === '_renderNewRootComponent' || fnName === 'flushBatchedUpdates') { // A "measurement" is a set of metrics recorded for each flush. We want // to group the metrics for a given flush together so we can look at the // components that rendered and the DOM operations that actually // happened to determine the amount of "wasted work" performed. ReactDefaultPerf._allMeasurements.push({ exclusive: {}, inclusive: {}, render: {}, counts: {}, writes: {}, displayNames: {}, totalTime: 0 }); start = performanceNow(); rv = func.apply(this, args); ReactDefaultPerf._allMeasurements[ ReactDefaultPerf._allMeasurements.length - 1 ].totalTime = performanceNow() - start; return rv; } else if (fnName === '_mountImageIntoNode' || moduleName === 'ReactDOMIDOperations') { start = performanceNow(); rv = func.apply(this, args); totalTime = performanceNow() - start; if (fnName === '_mountImageIntoNode') { var mountID = ReactMount.getID(args[1]); ReactDefaultPerf._recordWrite(mountID, fnName, totalTime, args[0]); } else if (fnName === 'dangerouslyProcessChildrenUpdates') { // special format args[0].forEach(function(update) { var writeArgs = {}; if (update.fromIndex !== null) { writeArgs.fromIndex = update.fromIndex; } if (update.toIndex !== null) { writeArgs.toIndex = update.toIndex; } if (update.textContent !== null) { writeArgs.textContent = update.textContent; } if (update.markupIndex !== null) { writeArgs.markup = args[1][update.markupIndex]; } ReactDefaultPerf._recordWrite( update.parentID, update.type, totalTime, writeArgs ); }); } else { // basic format ReactDefaultPerf._recordWrite( args[0], fnName, totalTime, Array.prototype.slice.call(args, 1) ); } return rv; } else if (moduleName === 'ReactCompositeComponent' && ( (// TODO: receiveComponent()? (fnName === 'mountComponent' || fnName === 'updateComponent' || fnName === '_renderValidatedComponent')))) { if (typeof this._currentElement.type === 'string') { return func.apply(this, args); } var rootNodeID = fnName === 'mountComponent' ? args[0] : this._rootNodeID; var isRender = fnName === '_renderValidatedComponent'; var isMount = fnName === 'mountComponent'; var mountStack = ReactDefaultPerf._mountStack; var entry = ReactDefaultPerf._allMeasurements[ ReactDefaultPerf._allMeasurements.length - 1 ]; if (isRender) { addValue(entry.counts, rootNodeID, 1); } else if (isMount) { mountStack.push(0); } start = performanceNow(); rv = func.apply(this, args); totalTime = performanceNow() - start; if (isRender) { addValue(entry.render, rootNodeID, totalTime); } else if (isMount) { var subMountTime = mountStack.pop(); mountStack[mountStack.length - 1] += totalTime; addValue(entry.exclusive, rootNodeID, totalTime - subMountTime); addValue(entry.inclusive, rootNodeID, totalTime); } else { addValue(entry.inclusive, rootNodeID, totalTime); } entry.displayNames[rootNodeID] = { current: this.getName(), owner: this._currentElement._owner ? this._currentElement._owner.getName() : '' }; return rv; } else { return func.apply(this, args); } }; } }; module.exports = ReactDefaultPerf;