270 lines
8.3 KiB
JavaScript
270 lines
8.3 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
|
|
var aggregateHandlersByName = {
|
|
'*': []
|
|
};
|
|
var profileHandlersByName = {
|
|
'*': []
|
|
};
|
|
var NOT_INVOKED = {};
|
|
var defaultProfiler = {
|
|
stop: require("fbjs/lib/emptyFunction")
|
|
};
|
|
|
|
var shouldInstrument = function shouldInstrument(name) {
|
|
if (process.env.NODE_ENV !== "production") {
|
|
return true;
|
|
}
|
|
|
|
return name.charAt(0) !== '@';
|
|
};
|
|
/**
|
|
* @public
|
|
*
|
|
* Instruments methods to allow profiling various parts of Relay. Profiling code
|
|
* in Relay consists of three steps:
|
|
*
|
|
* - Instrument the function to be profiled.
|
|
* - Attach handlers to the instrumented function.
|
|
* - Run the code which triggers the handlers.
|
|
*
|
|
* Handlers attached to instrumented methods are called with an instrumentation
|
|
* name and a callback that must be synchronously executed:
|
|
*
|
|
* instrumentedMethod.attachHandler(function(name, callback) {
|
|
* const start = performance.now();
|
|
* callback();
|
|
* console.log('Duration', performance.now() - start);
|
|
* });
|
|
*
|
|
* Handlers for profiles are callbacks that return a stop method:
|
|
*
|
|
* RelayProfiler.attachProfileHandler('profileName', (name, state) => {
|
|
* const start = performance.now();
|
|
* return function stop(name, state) {
|
|
* console.log(`Duration (${name})`, performance.now() - start);
|
|
* }
|
|
* });
|
|
*
|
|
* In order to reduce the impact on performance in production, instrumented
|
|
* methods and profilers with names that begin with `@` will only be measured
|
|
* if `__DEV__` is true. This should be used for very hot functions.
|
|
*/
|
|
|
|
|
|
var RelayProfiler = {
|
|
/**
|
|
* Instruments methods on a class or object. This re-assigns the method in
|
|
* order to preserve function names in stack traces (which are detected by
|
|
* modern debuggers via heuristics). Example usage:
|
|
*
|
|
* const RelayStore = { primeCache: function() {...} };
|
|
* RelayProfiler.instrumentMethods(RelayStore, {
|
|
* primeCache: 'RelayStore.primeCache'
|
|
* });
|
|
*
|
|
* RelayStore.primeCache.attachHandler(...);
|
|
*
|
|
* As a result, the methods will be replaced by wrappers that provide the
|
|
* `attachHandler` and `detachHandler` methods.
|
|
*/
|
|
instrumentMethods: function instrumentMethods(object, names) {
|
|
for (var _key in names) {
|
|
if (names.hasOwnProperty(_key)) {
|
|
if (typeof object[_key] === 'function') {
|
|
object[_key] = RelayProfiler.instrument(names[_key], object[_key]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Wraps the supplied function with one that provides the `attachHandler` and
|
|
* `detachHandler` methods. Example usage:
|
|
*
|
|
* const printRelayQuery =
|
|
* RelayProfiler.instrument('printRelayQuery', printRelayQuery);
|
|
*
|
|
* printRelayQuery.attachHandler(...);
|
|
*
|
|
* NOTE: The instrumentation assumes that no handlers are attached or detached
|
|
* in the course of executing another handler.
|
|
*/
|
|
instrument: function instrument(name, originalFunction) {
|
|
if (!shouldInstrument(name)) {
|
|
originalFunction.attachHandler = require("fbjs/lib/emptyFunction");
|
|
originalFunction.detachHandler = require("fbjs/lib/emptyFunction");
|
|
return originalFunction;
|
|
}
|
|
|
|
if (!aggregateHandlersByName.hasOwnProperty(name)) {
|
|
aggregateHandlersByName[name] = [];
|
|
}
|
|
|
|
var catchallHandlers = aggregateHandlersByName['*'];
|
|
var aggregateHandlers = aggregateHandlersByName[name];
|
|
var handlers = [];
|
|
var contexts = [];
|
|
|
|
var invokeHandlers = function invokeHandlers() {
|
|
var context = contexts[contexts.length - 1];
|
|
|
|
if (context[0]) {
|
|
context[0]--;
|
|
catchallHandlers[context[0]](name, invokeHandlers);
|
|
} else if (context[1]) {
|
|
context[1]--;
|
|
aggregateHandlers[context[1]](name, invokeHandlers);
|
|
} else if (context[2]) {
|
|
context[2]--;
|
|
handlers[context[2]](name, invokeHandlers);
|
|
} else {
|
|
context[5] = originalFunction.apply(context[3], context[4]);
|
|
}
|
|
};
|
|
|
|
var instrumentedCallback = function instrumentedCallback() {
|
|
var returnValue;
|
|
|
|
if (aggregateHandlers.length === 0 && handlers.length === 0 && catchallHandlers.length === 0) {
|
|
returnValue = originalFunction.apply(this, arguments);
|
|
} else {
|
|
contexts.push([catchallHandlers.length, aggregateHandlers.length, handlers.length, this, arguments, NOT_INVOKED]);
|
|
invokeHandlers();
|
|
var context = contexts.pop();
|
|
returnValue = context[5];
|
|
|
|
if (returnValue === NOT_INVOKED) {
|
|
throw new Error('RelayProfiler: Handler did not invoke original function.');
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
};
|
|
|
|
instrumentedCallback.attachHandler = function (handler) {
|
|
handlers.push(handler);
|
|
};
|
|
|
|
instrumentedCallback.detachHandler = function (handler) {
|
|
require("fbjs/lib/removeFromArray")(handlers, handler);
|
|
};
|
|
|
|
instrumentedCallback.displayName = '(instrumented ' + name + ')';
|
|
return instrumentedCallback;
|
|
},
|
|
|
|
/**
|
|
* Attaches a handler to all methods instrumented with the supplied name.
|
|
*
|
|
* function createRenderer() {
|
|
* return RelayProfiler.instrument('render', function() {...});
|
|
* }
|
|
* const renderA = createRenderer();
|
|
* const renderB = createRenderer();
|
|
*
|
|
* // Only profiles `renderA`.
|
|
* renderA.attachHandler(...);
|
|
*
|
|
* // Profiles both `renderA` and `renderB`.
|
|
* RelayProfiler.attachAggregateHandler('render', ...);
|
|
*
|
|
*/
|
|
attachAggregateHandler: function attachAggregateHandler(name, handler) {
|
|
if (shouldInstrument(name)) {
|
|
if (!aggregateHandlersByName.hasOwnProperty(name)) {
|
|
aggregateHandlersByName[name] = [];
|
|
}
|
|
|
|
aggregateHandlersByName[name].push(handler);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Detaches a handler attached via `attachAggregateHandler`.
|
|
*/
|
|
detachAggregateHandler: function detachAggregateHandler(name, handler) {
|
|
if (shouldInstrument(name)) {
|
|
if (aggregateHandlersByName.hasOwnProperty(name)) {
|
|
require("fbjs/lib/removeFromArray")(aggregateHandlersByName[name], handler);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Instruments profiling for arbitrarily asynchronous code by a name.
|
|
*
|
|
* const timerProfiler = RelayProfiler.profile('timeout');
|
|
* setTimeout(function() {
|
|
* timerProfiler.stop();
|
|
* }, 1000);
|
|
*
|
|
* RelayProfiler.attachProfileHandler('timeout', ...);
|
|
*
|
|
* Arbitrary state can also be passed into `profile` as a second argument. The
|
|
* attached profile handlers will receive this as the second argument.
|
|
*/
|
|
profile: function profile(name, state) {
|
|
var hasCatchAllHandlers = profileHandlersByName['*'].length > 0;
|
|
var hasNamedHandlers = profileHandlersByName.hasOwnProperty(name);
|
|
|
|
if (hasNamedHandlers || hasCatchAllHandlers) {
|
|
var profileHandlers = hasNamedHandlers && hasCatchAllHandlers ? profileHandlersByName[name].concat(profileHandlersByName['*']) : hasNamedHandlers ? profileHandlersByName[name] : profileHandlersByName['*'];
|
|
var stopHandlers;
|
|
|
|
for (var ii = profileHandlers.length - 1; ii >= 0; ii--) {
|
|
var profileHandler = profileHandlers[ii];
|
|
var stopHandler = profileHandler(name, state);
|
|
stopHandlers = stopHandlers || [];
|
|
stopHandlers.unshift(stopHandler);
|
|
}
|
|
|
|
return {
|
|
stop: function stop(error) {
|
|
if (stopHandlers) {
|
|
stopHandlers.forEach(function (stopHandler) {
|
|
return stopHandler(error);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
return defaultProfiler;
|
|
},
|
|
|
|
/**
|
|
* Attaches a handler to profiles with the supplied name. You can also
|
|
* attach to the special name '*' which is a catch all.
|
|
*/
|
|
attachProfileHandler: function attachProfileHandler(name, handler) {
|
|
if (shouldInstrument(name)) {
|
|
if (!profileHandlersByName.hasOwnProperty(name)) {
|
|
profileHandlersByName[name] = [];
|
|
}
|
|
|
|
profileHandlersByName[name].push(handler);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Detaches a handler attached via `attachProfileHandler`.
|
|
*/
|
|
detachProfileHandler: function detachProfileHandler(name, handler) {
|
|
if (shouldInstrument(name)) {
|
|
if (profileHandlersByName.hasOwnProperty(name)) {
|
|
require("fbjs/lib/removeFromArray")(profileHandlersByName[name], handler);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
module.exports = RelayProfiler; |