@samitouri / QOS-React-2 / commits / fce57abeca

Benchmarking tool for React application performance

ReactAppPerf wraps core methods and logs info from them; there's no real UI at this point

Danny Ben-David committed Aug 21, 2013 at 20:30 UTC fce57abeca1d8388baaaac8511540aed486177dc
6 files changed +595 -61
src/core/React.js
+6 -1
@@ -23,6 +23,7 @@ var ReactCompositeComponent = require('ReactCompositeComponent');
23 var ReactComponent = require('ReactComponent');
24 var ReactDOM = require('ReactDOM');
25 var ReactMount = require('ReactMount');
26 +var ReactPerf = require('ReactPerf');
27 var ReactPropTypes = require('ReactPropTypes');
28 var ReactServerRendering = require('ReactServerRendering');
29
@@ -42,7 +43,11 @@ var React = {
43 constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
44 forEachChildren: ReactChildren.forEach,
45 mapChildren: ReactChildren.map,
45 - renderComponent: ReactMount.renderComponent,
46 + renderComponent: ReactPerf.measure(
47 + 'React',
48 + 'renderComponent',
49 + ReactMount.renderComponent
50 + ),
51 renderComponentToString: ReactServerRendering.renderComponentToString,
52 unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
53 isValidComponent: ReactComponent.isValidComponent
src/core/ReactCompositeComponent.js
+55 -46
@@ -21,6 +21,7 @@
21 var ReactComponent = require('ReactComponent');
22 var ReactCurrentOwner = require('ReactCurrentOwner');
23 var ReactOwner = require('ReactOwner');
24 +var ReactPerf = require('ReactPerf');
25 var ReactPropTransferer = require('ReactPropTransferer');
26 var ReactUpdates = require('ReactUpdates');
27
@@ -479,41 +480,45 @@ var ReactCompositeComponentMixin = {
480 * @final
481 * @internal
482 */
482 - mountComponent: function(rootID, transaction) {
483 - ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
484 - this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
483 + mountComponent: ReactPerf.measure(
484 + 'ReactCompositeComponent',
485 + 'mountComponent',
486 + function(rootID, transaction) {
487 + ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
488 + this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
489
486 - this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
487 - this._processProps(this.props);
490 + this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
491 + this._processProps(this.props);
492
489 - if (this.__reactAutoBindMap) {
490 - this._bindAutoBindMethods();
491 - }
493 + if (this.__reactAutoBindMap) {
494 + this._bindAutoBindMethods();
495 + }
496
493 - this.state = this.getInitialState ? this.getInitialState() : null;
494 - this._pendingState = null;
495 - this._pendingForceUpdate = false;
496 -
497 - if (this.componentWillMount) {
498 - this.componentWillMount();
499 - // When mounting, calls to `setState` by `componentWillMount` will set
500 - // `this._pendingState` without triggering a re-render.
501 - if (this._pendingState) {
502 - this.state = this._pendingState;
503 - this._pendingState = null;
497 + this.state = this.getInitialState ? this.getInitialState() : null;
498 + this._pendingState = null;
499 + this._pendingForceUpdate = false;
500 +
501 + if (this.componentWillMount) {
502 + this.componentWillMount();
503 + // When mounting, calls to `setState` by `componentWillMount` will set
504 + // `this._pendingState` without triggering a re-render.
505 + if (this._pendingState) {
506 + this.state = this._pendingState;
507 + this._pendingState = null;
508 + }
509 }
505 - }
510
507 - this._renderedComponent = this._renderValidatedComponent();
511 + this._renderedComponent = this._renderValidatedComponent();
512
509 - // Done with mounting, `setState` will now trigger UI changes.
510 - this._compositeLifeCycleState = null;
511 - var markup = this._renderedComponent.mountComponent(rootID, transaction);
512 - if (this.componentDidMount) {
513 - transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
513 + // Done with mounting, `setState` will now trigger UI changes.
514 + this._compositeLifeCycleState = null;
515 + var markup = this._renderedComponent.mountComponent(rootID, transaction);
516 + if (this.componentDidMount) {
517 + transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
518 + }
519 + return markup;
520 }
515 - return markup;
516 - },
521 + ),
522
523 /**
524 * Releases any resources allocated by `mountComponent`.
@@ -714,25 +719,29 @@ var ReactCompositeComponentMixin = {
719 * @internal
720 * @overridable
721 */
717 - updateComponent: function(transaction, prevProps, prevState) {
718 - ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
719 - var currentComponent = this._renderedComponent;
720 - var nextComponent = this._renderValidatedComponent();
721 - if (currentComponent.constructor === nextComponent.constructor) {
722 - currentComponent.receiveProps(nextComponent.props, transaction);
723 - } else {
724 - // These two IDs are actually the same! But nothing should rely on that.
725 - var thisID = this._rootNodeID;
726 - var currentComponentID = currentComponent._rootNodeID;
727 - currentComponent.unmountComponent();
728 - var nextMarkup = nextComponent.mountComponent(thisID, transaction);
729 - ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
730 - currentComponentID,
731 - nextMarkup
732 - );
733 - this._renderedComponent = nextComponent;
722 + updateComponent: ReactPerf.measure(
723 + 'ReactCompositeComponent',
724 + 'updateComponent',
725 + function(transaction, prevProps, prevState) {
726 + ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
727 + var currentComponent = this._renderedComponent;
728 + var nextComponent = this._renderValidatedComponent();
729 + if (currentComponent.constructor === nextComponent.constructor) {
730 + currentComponent.receiveProps(nextComponent.props, transaction);
731 + } else {
732 + // These two IDs are actually the same! But nothing should rely on that.
733 + var thisID = this._rootNodeID;
734 + var currentComponentID = currentComponent._rootNodeID;
735 + currentComponent.unmountComponent();
736 + var nextMarkup = nextComponent.mountComponent(thisID, transaction);
737 + ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
738 + currentComponentID,
739 + nextMarkup
740 + );
741 + this._renderedComponent = nextComponent;
742 + }
743 }
735 - },
744 + ),
745
746 /**
747 * Forces an update. This should only be invoked when it is known with
src/core/ReactDefaultInjection.js
+5
@@ -26,6 +26,7 @@ var ReactDOMSelect = require('ReactDOMSelect');
26 var ReactDOMTextarea = require('ReactDOMTextarea');
27 var ReactEventEmitter = require('ReactEventEmitter');
28 var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
29 +var ReactPerf = require('ReactPerf');
30
31 var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
32 var DOMProperty = require('DOMProperty');
@@ -66,6 +67,10 @@ function inject() {
67 });
68
69 DOMProperty.injection.injectDOMPropertyConfig(DefaultDOMPropertyConfig);
70 +
71 + if (__DEV__) {
72 + ReactPerf.injection.injectMeasure(require('ReactDefaultPerf').measure);
73 + }
74 }
75
76 module.exports = {
src/core/ReactNativeComponent.js
+23 -14
@@ -26,6 +26,7 @@ var ReactComponent = require('ReactComponent');
26 var ReactEventEmitter = require('ReactEventEmitter');
27 var ReactMultiChild = require('ReactMultiChild');
28 var ReactMount = require('ReactMount');
29 +var ReactPerf = require('ReactPerf');
30
31 var escapeTextForBrowser = require('escapeTextForBrowser');
32 var flattenChildren = require('flattenChildren');
@@ -85,15 +86,19 @@ ReactNativeComponent.Mixin = {
86 * @param {ReactReconcileTransaction} transaction
87 * @return {string} The computed markup.
88 */
88 - mountComponent: function(rootID, transaction) {
89 - ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
90 - assertValidProps(this.props);
91 - return (
92 - this._createOpenTagMarkup() +
93 - this._createContentMarkup(transaction) +
94 - this._tagClose
95 - );
96 - },
89 + mountComponent: ReactPerf.measure(
90 + 'ReactNativeComponent',
91 + 'mountComponent',
92 + function(rootID, transaction) {
93 + ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
94 + assertValidProps(this.props);
95 + return (
96 + this._createOpenTagMarkup() +
97 + this._createContentMarkup(transaction) +
98 + this._tagClose
99 + );
100 + }
101 + ),
102
103 /**
104 * Creates markup for the open tag and all attributes.
@@ -184,11 +189,15 @@ ReactNativeComponent.Mixin = {
189 * @internal
190 * @overridable
191 */
187 - updateComponent: function(transaction, prevProps) {
188 - ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
189 - this._updateDOMProperties(prevProps);
190 - this._updateDOMChildren(prevProps, transaction);
191 - },
192 + updateComponent: ReactPerf.measure(
193 + 'ReactNativeComponent',
194 + 'updateComponent',
195 + function(transaction, prevProps) {
196 + ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
197 + this._updateDOMProperties(prevProps);
198 + this._updateDOMChildren(prevProps, transaction);
199 + }
200 + ),
201
202 /**
203 * Reconciles the properties by detecting differences in property values and
src/test/ReactDefaultPerf.js new
+416
@@ -0,0 +1,416 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDefaultPerf
17 + * @typechecks static-only
18 + */
19 +
20 +"use strict";
21 +
22 +var ReactDefaultPerf = {};
23 +
24 +if (__DEV__) {
25 + ReactDefaultPerf = {
26 + /**
27 + * Gets the stored information for a given object's function.
28 + *
29 + * @param {string} objName
30 + * @param {string} fnName
31 + * @return {?object}
32 + */
33 + getInfo: function(objName, fnName) {
34 + if (!this.info[objName] || !this.info[objName][fnName]) {
35 + return null;
36 + }
37 + return this.info[objName][fnName];
38 + },
39 +
40 + /**
41 + * Gets the logs pertaining to a given object's function.
42 + *
43 + * @param {string} objName
44 + * @param {string} fnName
45 + * @return {?array<object>}
46 + */
47 + getLogs: function(objName, fnName) {
48 + if (!this.getInfo(objName, fnName)) {
49 + return null;
50 + }
51 + return this.logs.filter(function(log) {
52 + return log.objName === objName && log.fnName === fnName;
53 + });
54 + },
55 +
56 + /**
57 + * Runs through the logs and builds an array of arrays, where each array
58 + * walks through the mounting/updating of each component underneath.
59 + *
60 + * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
61 + * @return {array<array>}
62 + */
63 + getRawRenderHistory: function(rootID) {
64 + var history = [];
65 + /**
66 + * Since logs are added after the method returns, the logs are in a sense
67 + * upside-down: the inner-most elements from mounting/updating are logged
68 + * first, and the last addition to the log is the top renderComponent.
69 + * Therefore, we flip the logs upside down for ease of processing, and
70 + * reverse the history array at the end so the earliest event has index 0.
71 + */
72 + var logs = this.logs.filter(function(log) {
73 + return log.reactID.indexOf(rootID) === 0;
74 + }).reverse();
75 +
76 + var subHistory = [];
77 + logs.forEach(function(log, i) {
78 + if (i && log.reactID === rootID && logs[i - 1].reactID !== rootID) {
79 + subHistory.length && history.push(subHistory);
80 + subHistory = [];
81 + }
82 + subHistory.push(log);
83 + });
84 + if (subHistory.length) {
85 + history.push(subHistory);
86 + }
87 + return history.reverse();
88 + },
89 +
90 + /**
91 + * Runs through the logs and builds an array of strings, where each string
92 + * is a multiline formatted way of walking through the mounting/updating
93 + * underneath.
94 + *
95 + * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
96 + * @return {array<string>}
97 + */
98 + getRenderHistory: function(rootID) {
99 + var history = this.getRawRenderHistory(rootID);
100 +
101 + return history.map(function(subHistory) {
102 + var headerString = (
103 + 'log# Component (execution time) [bloat from logging]\n' +
104 + '================================================================\n'
105 + );
106 + return headerString + subHistory.map(function(log) {
107 + // Add two spaces for every layer in the reactID.
108 + var indents = '\t' + Array(log.reactID.split('.[').length).join(' ');
109 + var delta = _microTime(log.timing.delta);
110 + var bloat = _microTime(log.timing.timeToLog);
111 +
112 + return log.index + indents + log.name + ' (' + delta + 'ms)' +
113 + ' [' + bloat + 'ms]';
114 + }).join('\n');
115 + });
116 + },
117 +
118 + /**
119 + * Print the render history from `getRenderHistory` using console.log.
120 + * This is currently the best way to display perf data from
121 + * any React component; working on that.
122 + *
123 + * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
124 + * @param {number} index
125 + */
126 + printRenderHistory: function(rootID, index) {
127 + var history = this.getRenderHistory(rootID);
128 + if (!history[index]) {
129 + console.warn(
130 + 'Index', index, 'isn\'t available! ' +
131 + 'The render history is', history.length, 'long.'
132 + );
133 + return;
134 + }
135 + console.log(
136 + 'Loading render history #' + (index + 1) +
137 + ' of ' + history.length + ':\n' + history[index]
138 + );
139 + },
140 +
141 + /**
142 + * Prints the heatmap legend to console, showing how the colors correspond
143 + * with render times. This relies on console.log styles.
144 + */
145 + printHeatmapLegend: function() {
146 + if (!this.options.heatmap.enabled) {
147 + return;
148 + }
149 + var max = this.info.React
150 + && this.info.React.renderComponent
151 + && this.info.React.renderComponent.max;
152 + if (max) {
153 + var logStr = 'Heatmap: ';
154 + for (var ii = 0; ii <= 10 * max; ii += max) {
155 + logStr += '%c ' + (Math.round(ii) / 10) + 'ms ';
156 + }
157 + console.log(
158 + logStr,
159 + 'background-color: hsla(100, 100%, 50%, 0.6);',
160 + 'background-color: hsla( 90, 100%, 50%, 0.6);',
161 + 'background-color: hsla( 80, 100%, 50%, 0.6);',
162 + 'background-color: hsla( 70, 100%, 50%, 0.6);',
163 + 'background-color: hsla( 60, 100%, 50%, 0.6);',
164 + 'background-color: hsla( 50, 100%, 50%, 0.6);',
165 + 'background-color: hsla( 40, 100%, 50%, 0.6);',
166 + 'background-color: hsla( 30, 100%, 50%, 0.6);',
167 + 'background-color: hsla( 20, 100%, 50%, 0.6);',
168 + 'background-color: hsla( 10, 100%, 50%, 0.6);',
169 + 'background-color: hsla( 0, 100%, 50%, 0.6);'
170 + );
171 + }
172 + },
173 +
174 + /**
175 + * Measure a given function with logging information, and calls a callback
176 + * if there is one.
177 + *
178 + * @param {string} objName
179 + * @param {string} fnName
180 + * @param {function} func
181 + * @return {function}
182 + */
183 + measure: function(objName, fnName, func) {
184 + var info = _getNewInfo(objName, fnName);
185 +
186 + var fnArgs = _getFnArguments(func);
187 +
188 + return function() {
189 + var timeBeforeFn = now();
190 + var fnReturn = func.apply(this, arguments);
191 + var timeAfterFn = now();
192 +
193 + /**
194 + * Hold onto arguments in a readable way: args[1] -> args.component.
195 + * args is also passed to the callback, so if you want to save an
196 + * argument in the log, do so in the callback.
197 + */
198 + var args = {};
199 + for (var i = 0; i < arguments.length; i++) {
200 + args[fnArgs[i]] = arguments[i];
201 + }
202 +
203 + var log = {
204 + index: ReactDefaultPerf.logs.length,
205 + fnName: fnName,
206 + objName: objName,
207 + timing: {
208 + before: timeBeforeFn,
209 + after: timeAfterFn,
210 + delta: timeAfterFn - timeBeforeFn
211 + }
212 + };
213 +
214 + ReactDefaultPerf.logs.push(log);
215 +
216 + /**
217 + * The callback gets:
218 + * - this (the component)
219 + * - the original method's arguments
220 + * - what the method returned
221 + * - the log object, and
222 + * - the wrapped method's info object.
223 + */
224 + var callback = _getCallback(objName, fnName);
225 + callback && callback(this, args, fnReturn, log, info);
226 +
227 + log.timing.timeToLog = now() - timeAfterFn;
228 +
229 + return fnReturn;
230 + };
231 + },
232 +
233 + /**
234 + * Holds information on wrapped objects/methods.
235 + * For instance, ReactDefaultPerf.info.React.renderComponent
236 + */
237 + info: {},
238 +
239 + /**
240 + * Holds all of the logs. Filter this to pull desired information.
241 + */
242 + logs: [],
243 +
244 + /**
245 + * Toggle settings for ReactDefaultPerf
246 + */
247 + options: {
248 + /**
249 + * The heatmap sets the background color of the React containers
250 + * according to how much total time has been spent rendering them.
251 + * The most temporally expensive component is set as pure red,
252 + * and the others are colored from green to red as a fraction
253 + * of that max component time.
254 + */
255 + heatmap: {
256 + enabled: true
257 + }
258 + }
259 + };
260 +
261 + /**
262 + * Gets a info area for a given object's function, adding a new one if
263 + * necessary.
264 + *
265 + * @param {string} objName
266 + * @param {string} fnName
267 + * @return {object}
268 + */
269 + var _getNewInfo = function(objName, fnName) {
270 + var info = ReactDefaultPerf.getInfo(objName, fnName);
271 + if (info) {
272 + return info;
273 + }
274 + ReactDefaultPerf.info[objName] = ReactDefaultPerf.info[objName] || {};
275 +
276 + return ReactDefaultPerf.info[objName][fnName] = {
277 + getLogs: function() {
278 + return ReactDefaultPerf.getLogs(objName, fnName);
279 + }
280 + };
281 + };
282 +
283 + /**
284 + * Gets a list of the argument names from a function's definition.
285 + * This is useful for storing arguments by their names within wrapFn().
286 + *
287 + * @param {function} fn
288 + * @return {array<string>}
289 + */
290 + var _getFnArguments = function(fn) {
291 + var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
292 + var fnStr = fn.toString().replace(STRIP_COMMENTS, '');
293 + fnStr = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'));
294 + return fnStr.match(/([^\s,]+)/g);
295 + };
296 +
297 + /**
298 + * Store common callbacks within ReactDefaultPerf.
299 + *
300 + * @param {string} objName
301 + * @param {string} fnName
302 + * @return {?function}
303 + */
304 + var _getCallback = function(objName, fnName) {
305 + switch (objName + '.' + fnName) {
306 + case 'React.renderComponent':
307 + return _renderComponentCallback;
308 + case 'ReactNativeComponent.mountComponent':
309 + case 'ReactNativeComponent.updateComponent':
310 + return _nativeComponentCallback;
311 + case 'ReactCompositeComponent.mountComponent':
312 + case 'ReactCompositeComponent.updateComponent':
313 + return _compositeComponentCallback;
314 + default:
315 + return null;
316 + }
317 + };
318 +
319 + /**
320 + * Callback function for React.renderComponent
321 + *
322 + * @param {object} component
323 + * @param {object} args
324 + * @param {?object} fnReturn
325 + * @param {object} log
326 + * @param {object} info
327 + */
328 + var _renderComponentCallback =
329 + function(component, args, fnReturn, log, info) {
330 + log.name = args.nextComponent.constructor.displayName || '[unknown]';
331 + log.reactID = fnReturn._rootNodeID || null;
332 +
333 + if (ReactDefaultPerf.options.heatmap.enabled) {
334 + var container = args.container;
335 + if (!container.loggedByReactDefaultPerf) {
336 + container.loggedByReactDefaultPerf = true;
337 + info.components = info.components || [];
338 + info.components.push(container);
339 + }
340 +
341 + container.count = container.count || 0;
342 + container.count += log.timing.delta;
343 + info.max = info.max || 0;
344 + if (container.count > info.max) {
345 + info.max = container.count;
346 + info.components.forEach(function(component) {
347 + _setHue(component, 100 - 100 * component.count / info.max);
348 + });
349 + } else {
350 + _setHue(container, 100 - 100 * container.count / info.max);
351 + }
352 + }
353 + };
354 +
355 + /**
356 + * Callback function for ReactNativeComponent
357 + *
358 + * @param {object} component
359 + * @param {object} args
360 + * @param {?object} fnReturn
361 + * @param {object} log
362 + * @param {object} info
363 + */
364 + var _nativeComponentCallback =
365 + function(component, args, fnReturn, log, info) {
366 + log.name = component.tagName || '[unknown]';
367 + log.reactID = component._rootNodeID;
368 + };
369 +
370 + /**
371 + * Callback function for ReactCompositeComponent
372 + *
373 + * @param {object} component
374 + * @param {object} args
375 + * @param {?object} fnReturn
376 + * @param {object} log
377 + * @param {object} info
378 + */
379 + var _compositeComponentCallback =
380 + function(component, args, fnReturn, log, info) {
381 + log.name = component.constructor.displayName || '[unknown]';
382 + log.reactID = component._rootNodeID;
383 + };
384 +
385 + /**
386 + * Using the hsl() background-color attribute, colors an element.
387 + *
388 + * @param {DOMElement} el
389 + * @param {number} hue [0 for red, 120 for green, 240 for blue]
390 + */
391 + var _setHue = function(el, hue) {
392 + el.style.backgroundColor = 'hsla(' + hue + ', 100%, 50%, 0.6)';
393 + };
394 +
395 + /**
396 + * Round to the thousandth place.
397 + * @param {number} time
398 + * @return {number}
399 + */
400 + var _microTime = function(time) {
401 + return Math.round(time * 1000) / 1000;
402 + };
403 +
404 + /**
405 + * Shim window.performance.now
406 + * We can't assign window.performance.now and then call it, so need to bind.
407 + * TODO: Support Firefox < 15 for now
408 + */
409 + var performance = window && (window.performance || window.webkitPeformance);
410 + if (!performance || !performance.now) {
411 + performance = Date;
412 + }
413 + var now = performance.now.bind(performance);
414 +}
415 +
416 +module.exports = ReactDefaultPerf;
src/test/ReactPerf.js new
+90
@@ -0,0 +1,90 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactPerf
17 + * @typechecks static-only
18 + */
19 +
20 +"use strict";
21 +
22 +var ReactPerf = {
23 + /**
24 + * Boolean to enable/disable measurement. Set to false by default to prevent
25 + * accidental logging and perf loss.
26 + */
27 + enableMeasure: false,
28 +
29 + /**
30 + * Holds onto the measure function in use. By default, don't measure
31 + * anything, but we'll override this if we inject a measure function.
32 + */
33 + storedMeasure: _noMeasure,
34 +
35 + /**
36 + * Use this to wrap methods you want to measure.
37 + *
38 + * @param {string} objName
39 + * @param {string} fnName
40 + * @param {function} func
41 + * @return {function}
42 + */
43 + measure: function(objName, fnName, func) {
44 + if (__DEV__) {
45 + if (this.enableMeasure) {
46 + var measuredFunc = null;
47 + return function() {
48 + if (!measuredFunc) {
49 + measuredFunc = ReactPerf.storedMeasure(objName, fnName, func);
50 + }
51 + return measuredFunc.apply(this, arguments);
52 + };
53 + }
54 + }
55 + return func;
56 + },
57 +
58 + injection: {
59 + /**
60 + * @param {function} measure
61 + */
62 + injectMeasure: function(measure) {
63 + ReactPerf.storedMeasure = measure;
64 + }
65 + }
66 +};
67 +
68 +if (__DEV__) {
69 + var ExecutionEnvironment = require('ExecutionEnvironment');
70 + var URL = ExecutionEnvironment.global &&
71 + ExecutionEnvironment.global.location &&
72 + ExecutionEnvironment.global.location.href ||
73 + '';
74 + ReactPerf.enableMeasure = ReactPerf.enableMeasure ||
75 + !!URL.match(/[?&]react_perf\b/);
76 +}
77 +
78 +/**
79 + * Simply passes through the measured function, without measuring it.
80 + *
81 + * @param {string} objName
82 + * @param {string} fnName
83 + * @param {function} func
84 + * @return {function}
85 + */
86 +function _noMeasure(objName, fnName, func) {
87 + return func;
88 +}
89 +
90 +module.exports = ReactPerf;