Skip to content

Instantly share code, notes, and snippets.

@meeech
Created March 13, 2012 21:37
Show Gist options
  • Save meeech/2031838 to your computer and use it in GitHub Desktop.
Save meeech/2031838 to your computer and use it in GitHub Desktop.
YUI Profiler code tweaked to run in titanium
/*
Profiler - Code base from YUI 3.4.1 (build 4118)
Brought required pieces into one file, to run in context of Titanium
Licensed under the BSD License.
http://yuilibrary.com/license/
*/
(function(){
//Begin Closure
var Y = {
Lang: {}
},
STRING_PROTO = String.prototype,
TOSTRING = Object.prototype.toString,
TYPES = {
'undefined' : 'undefined',
'number' : 'number',
'boolean' : 'boolean',
'string' : 'string',
'[object Function]': 'function',
'[object RegExp]' : 'regexp',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object Error]' : 'error'
},
SUBREGEX = /\{\s*([^|}]+?)\s*(?:\|([^}]*))?\s*\}/g,
TRIMREGEX = /^\s+|\s+$/g,
// If either MooTools or Prototype is on the page, then there's a chance that we
// can't trust "native" language features to actually be native. When this is
// the case, we take the safe route and fall back to our own non-native
// implementation.
unsafeNatives = false;
/**
* The YUI JavaScript profiler.
* @module profiler
*/
var hasOwn = Object.prototype.hasOwnProperty;
Y.mix = function(receiver, supplier, overwrite, whitelist, mode, merge) {
var alwaysOverwrite, exists, from, i, key, len, to;
// If no supplier is given, we return the receiver. If no receiver is given,
// we return Y. Returning Y doesn't make much sense to me, but it's
// grandfathered in for backcompat reasons.
if (!receiver || !supplier) {
return receiver || Y;
}
if (mode) {
// In mode 2 (prototype to prototype and object to object), we recurse
// once to do the proto to proto mix. The object to object mix will be
// handled later on.
if (mode === 2) {
Y.mix(receiver.prototype, supplier.prototype, overwrite,
whitelist, 0, merge);
}
// Depending on which mode is specified, we may be copying from or to
// the prototypes of the supplier and receiver.
from = mode === 1 || mode === 3 ? supplier.prototype : supplier;
to = mode === 1 || mode === 4 ? receiver.prototype : receiver;
// If either the supplier or receiver doesn't actually have a
// prototype property, then we could end up with an undefined `from`
// or `to`. If that happens, we abort and return the receiver.
if (!from || !to) {
return receiver;
}
} else {
from = supplier;
to = receiver;
}
// If `overwrite` is truthy and `merge` is falsy, then we can skip a
// property existence check on each iteration and save some time.
alwaysOverwrite = overwrite && !merge;
if (whitelist) {
for (i = 0, len = whitelist.length; i < len; ++i) {
key = whitelist[i];
// We call `Object.prototype.hasOwnProperty` instead of calling
// `hasOwnProperty` on the object itself, since the object's
// `hasOwnProperty` method may have been overridden or removed.
// Also, some native objects don't implement a `hasOwnProperty`
// method.
if (!hasOwn.call(from, key)) {
continue;
}
// The `key in to` check here is (sadly) intentional for backwards
// compatibility reasons. It prevents undesired shadowing of
// prototype members on `to`.
exists = alwaysOverwrite ? false : key in to;
if (merge && exists && isObject(to[key], true)
&& isObject(from[key], true)) {
// If we're in merge mode, and the key is present on both
// objects, and the value on both objects is either an object or
// an array (but not a function), then we recurse to merge the
// `from` value into the `to` value instead of overwriting it.
//
// Note: It's intentional that the whitelist isn't passed to the
// recursive call here. This is legacy behavior that lots of
// code still depends on.
Y.mix(to[key], from[key], overwrite, null, 0, merge);
} else if (overwrite || !exists) {
// We're not in merge mode, so we'll only copy the `from` value
// to the `to` value if we're in overwrite mode or if the
// current key doesn't exist on the `to` object.
to[key] = from[key];
}
}
} else {
for (key in from) {
// The code duplication here is for runtime performance reasons.
// Combining whitelist and non-whitelist operations into a single
// loop or breaking the shared logic out into a function both result
// in worse performance, and Y.mix is critical enough that the byte
// tradeoff is worth it.
if (!hasOwn.call(from, key)) {
continue;
}
// The `key in to` check here is (sadly) intentional for backwards
// compatibility reasons. It prevents undesired shadowing of
// prototype members on `to`.
exists = alwaysOverwrite ? false : key in to;
if (merge && exists && isObject(to[key], true)
&& isObject(from[key], true)) {
Y.mix(to[key], from[key], overwrite, null, 0, merge);
} else if (overwrite || !exists) {
to[key] = from[key];
}
}
}
return receiver;
};
var L = Y.Lang;
L.type = function(o) {
return TYPES[typeof o] || TYPES[TOSTRING.call(o)] || (o ? 'object' : 'null');
};
L.isString = function(o) {
return typeof o === 'string';
};
L.isFunction = function(o) {
return L.type(o) === 'function';
};
L.isObject = function(o, failfn) {
var t = typeof o;
return (o && (t === 'object' ||
(!failfn && (t === 'function' || L.isFunction(o))))) || false;
};
//-------------------------------------------------------------------------
// Private Variables and Functions
//-------------------------------------------------------------------------
var container = {}, //Container object on which to put the original unprofiled methods.
report = {}, //Profiling information for functions
stopwatches = {}, //Additional stopwatch information
WATCH_STARTED = 0,
WATCH_STOPPED = 1,
WATCH_PAUSED = 2,
//shortcuts
L = Y.Lang;
/* (intentionally not documented)
* Creates a report object with the given name.
* @param {String} name The name to store for the report object.
* @return {Void}
* @method createReport
* @private
*/
function createReport(name){
report[name] = {
calls: 0,
max: 0,
min: 0,
avg: 0,
points: []
};
return report[name];
}
/* (intentionally not documented)
* Called when a method ends execution. Marks the start and end time of the
* method so it can calculate how long the function took to execute. Also
* updates min/max/avg calculations for the function.
* @param {String} name The name of the function to mark as stopped.
* @param {int} duration The number of milliseconds it took the function to
* execute.
* @return {Void}
* @method saveDataPoint
* @private
* @static
*/
function saveDataPoint(name, duration){
//get the function data
var functionData /*:Object*/ = report[name];
//just in case clear() was called
if (!functionData){
functionData = createReport(name);
}
//increment the calls
functionData.calls++;
functionData.points.push(duration);
//if it's already been called at least once, do more complex calculations
if (functionData.calls > 1) {
functionData.avg = ((functionData.avg*(functionData.calls-1))+duration)/functionData.calls;
functionData.min = Math.min(functionData.min, duration);
functionData.max = Math.max(functionData.max, duration);
} else {
functionData.avg = duration;
functionData.min = duration;
functionData.max = duration;
}
}
//-------------------------------------------------------------------------
// Public Interface
//-------------------------------------------------------------------------
/**
* Profiles functions in JavaScript.
* @class Profiler
* @static
*/
Y.Profiler = {
//-------------------------------------------------------------------------
// Utility Methods
//-------------------------------------------------------------------------
/**
* Removes all report data from the profiler.
* @param {String} name (Optional) The name of the report to clear. If
* omitted, then all report data is cleared.
* @return {Void}
* @method clear
* @static
*/
clear: function(name){
if (L.isString(name)){
delete report[name];
delete stopwatches[name];
} else {
report = {};
stopwatches = {};
}
},
/**
* Returns the uninstrumented version of a function/object.
* @param {String} name The name of the function/object to retrieve.
* @return {Function|Object} The uninstrumented version of a function/object.
* @method getOriginal
* @static
*/
getOriginal: function(name){
return container[name];
},
/**
* Instruments a method to have profiling calls.
* @param {String} name The name of the report for the function.
* @param {Function} method The function to instrument.
* @return {Function} An instrumented version of the function.
* @method instrument
* @static
*/
instrument: function(name, method){
//create instrumented version of function
var newMethod = function () {
var start = new Date(),
retval = method.apply(this, arguments),
stop = new Date();
saveDataPoint(name, stop-start);
return retval;
};
//copy the function properties over
Y.mix(newMethod, method);
//assign prototype and flag as being profiled
newMethod.__yuiProfiled = true;
newMethod.prototype = method.prototype;
//store original method
container[name] = method;
container[name].__yuiFuncName = name;
//create the report
createReport(name);
//return the new method
return newMethod;
},
//-------------------------------------------------------------------------
// Stopwatch Methods
//-------------------------------------------------------------------------
/**
* Pauses profiling information for a given name.
* @param {String} name The name of the data point.
* @return {Void}
* @method pause
* @static
*/
pause: function(name){
var now = new Date(),
stopwatch = stopwatches[name];
if (stopwatch && stopwatch.state == WATCH_STARTED){
stopwatch.total += (now - stopwatch.start);
stopwatch.start = 0;
stopwatch.state = WATCH_PAUSED;
}
},
/**
* Start profiling information for a given name. The name cannot be the name
* of a registered function or object. This is used to start timing for a
* particular block of code rather than instrumenting the entire function.
* @param {String} name The name of the data point.
* @return {Void}
* @method start
* @static
*/
start: function(name){
if(container[name]){
throw new Error("Cannot use '" + name + "' for profiling through start(), name is already in use.");
} else {
//create report if necessary
if (!report[name]){
createReport(name);
}
//create stopwatch object if necessary
if (!stopwatches[name]){
stopwatches[name] = {
state: WATCH_STOPPED,
start: 0,
total: 0
};
}
if (stopwatches[name].state == WATCH_STOPPED){
stopwatches[name].state = WATCH_STARTED;
stopwatches[name].start = new Date();
}
}
},
/**
* Stops profiling information for a given name.
* @param {String} name The name of the data point.
* @return {Void}
* @method stop
* @static
*/
stop: function(name){
var now = new Date(),
stopwatch = stopwatches[name];
if (stopwatch){
if (stopwatch.state == WATCH_STARTED){
saveDataPoint(name, stopwatch.total + (now - stopwatch.start));
} else if (stopwatch.state == WATCH_PAUSED){
saveDataPoint(name, stopwatch.total);
}
//reset stopwatch information
stopwatch.start = 0;
stopwatch.total = 0;
stopwatch.state = WATCH_STOPPED;
}
},
//-------------------------------------------------------------------------
// Reporting Methods
//-------------------------------------------------------------------------
/**
* Returns the average amount of time (in milliseconds) that the function
* with the given name takes to execute.
* @param {String} name The name of the function whose data should be returned.
* If an object type method, it should be 'constructor.prototype.methodName';
* a normal object method would just be 'object.methodName'.
* @return {float} The average time it takes the function to execute.
* @method getAverage
* @static
*/
getAverage : function (name /*:String*/) /*:float*/ {
return report[name].avg;
},
/**
* Returns the number of times that the given function has been called.
* @param {String} name The name of the function whose data should be returned.
* @return {int} The number of times the function was called.
* @method getCallCount
* @static
*/
getCallCount : function (name /*:String*/) /*:int*/ {
return report[name].calls;
},
/**
* Returns the maximum amount of time (in milliseconds) that the function
* with the given name takes to execute.
* @param {String} name The name of the function whose data should be returned.
* If an object type method, it should be 'constructor.prototype.methodName';
* a normal object method would just be 'object.methodName'.
* @return {float} The maximum time it takes the function to execute.
* @method getMax
* @static
*/
getMax : function (name /*:String*/) /*:int*/ {
return report[name].max;
},
/**
* Returns the minimum amount of time (in milliseconds) that the function
* with the given name takes to execute.
* @param {String} name The name of the function whose data should be returned.
* If an object type method, it should be 'constructor.prototype.methodName';
* a normal object method would just be 'object.methodName'.
* @return {float} The minimum time it takes the function to execute.
* @method getMin
* @static
*/
getMin : function (name /*:String*/) /*:int*/ {
return report[name].min;
},
/**
* Returns an object containing profiling data for a single function.
* The object has an entry for min, max, avg, calls, and points).
* @return {Object} An object containing profile data for a given function.
* @method getFunctionReport
* @static
* @deprecated Use getReport() instead.
*/
getFunctionReport : function (name /*:String*/) /*:Object*/ {
return report[name];
},
/**
* Returns an object containing profiling data for a single function.
* The object has an entry for min, max, avg, calls, and points).
* @return {Object} An object containing profile data for a given function.
* @method getReport
* @static
*/
getReport : function (name /*:String*/) /*:Object*/ {
return report[name];
},
/**
* Returns an object containing profiling data for all of the functions
* that were profiled. The object has an entry for each function and
* returns all information (min, max, average, calls, etc.) for each
* function.
* @return {Object} An object containing all profile data.
* @method getFullReport
* @static
*/
getFullReport : function (filter /*:Function*/) /*:Object*/ {
filter = filter || function(){return true;};
if (L.isFunction(filter)) {
var fullReport = {};
for (var name in report){
if (filter(report[name])){
fullReport[name] = report[name];
}
}
return fullReport;
}
},
//-------------------------------------------------------------------------
// Profiling Methods
//-------------------------------------------------------------------------
/**
* Sets up a constructor for profiling, including all properties and methods on the prototype.
* @param {string} name The fully-qualified name of the function including namespace information.
* @param {Object} owner (Optional) The object that owns the function (namespace or containing object).
* @return {Void}
* @method registerConstructor
* @static
*/
registerConstructor : function (name /*:String*/, owner /*:Object*/) /*:Void*/ {
this.registerFunction(name, owner, true);
},
/**
* Sets up a function for profiling. It essentially overwrites the function with one
* that has instrumentation data. This method also creates an entry for the function
* in the profile report. The original function is stored on the container object.
* @param {String} name The full name of the function including namespacing. This
* is the name of the function that is stored in the report.
* @param {Object} owner (Optional) The object that owns the function. If the function
* isn't global then this argument is required. This could be the namespace that
* the function belongs to or the object on which it's
* a method.
* @param {Boolean} registerPrototype (Optional) Indicates that the prototype should
* also be instrumented. Setting to true has the same effect as calling
* registerConstructor().
* @return {Void}
* @method registerFunction
* @static
*/
registerFunction : function(name /*:String*/, owner /*:Object*/, registerPrototype /*:Boolean*/) /*:Void*/{
//figure out the function name without namespacing
var funcName = (name.indexOf(".") > -1 ?
name.substring(name.lastIndexOf(".")+1) : name),
method,
prototype;
//if owner isn't an object, try to find it from the name
if (!L.isObject(owner)){
owner = eval(name.substring(0, name.lastIndexOf(".")));
}
owner = owner || {};
//get the method and prototype
method = owner[funcName] || eval(funcName);
prototype = method.prototype;
//see if the method has already been registered
if (L.isFunction(method) && !method.__yuiProfiled){
//replace the function with the profiling one
owner[funcName] = this.instrument(name, method);
/*
* Store original function information. We store the actual
* function as well as the owner and the name used to identify
* the function so it can be restored later.
*/
container[name].__yuiOwner = owner;
container[name].__yuiFuncName = funcName; //overwrite with less-specific name
//register prototype if necessary
if (registerPrototype) {
this.registerObject(name + ".prototype", prototype);
}
}
},
/**
* Sets up an object for profiling. It takes the object and looks for functions.
* When a function is found, registerMethod() is called on it. If set to recrusive
* mode, it will also setup objects found inside of this object for profiling,
* using the same methodology.
* @param {String} name The name of the object to profile (shows up in report).
* @param {Object} owner (Optional) The object represented by the name.
* @param {Boolean} recurse (Optional) Determines if subobject methods are also profiled.
* @return {Void}
* @method registerObject
* @static
*/
registerObject : function (name /*:String*/, object /*:Object*/, recurse /*:Boolean*/) /*:Void*/{
//get the object
object = (L.isObject(object) ? object : eval(name));
//save the object
container[name] = object;
for (var prop in object) {
if (typeof object[prop] == "function"){
if (prop != "constructor" && prop != "superclass"){ //don't do constructor or superclass, it's recursive
this.registerFunction(name + "." + prop, object);
}
} else if (typeof object[prop] == "object" && recurse){
this.registerObject(name + "." + prop, object[prop], recurse);
}
}
},
/**
* Removes a constructor function from profiling. Reverses the registerConstructor() method.
* @param {String} name The full name of the function including namespacing. This
* is the name of the function that is stored in the report.
* @return {Void}
* @method unregisterFunction
* @static
*/
unregisterConstructor : function(name /*:String*/) /*:Void*/{
//see if the method has been registered
if (L.isFunction(container[name])){
this.unregisterFunction(name, true);
}
},
/**
* Removes function from profiling. Reverses the registerFunction() method.
* @param {String} name The full name of the function including namespacing. This
* is the name of the function that is stored in the report.
* @return {Void}
* @method unregisterFunction
* @static
*/
unregisterFunction : function(name /*:String*/, unregisterPrototype /*:Boolean*/) /*:Void*/{
//see if the method has been registered
if (L.isFunction(container[name])){
//check to see if you should unregister the prototype
if (unregisterPrototype){
this.unregisterObject(name + ".prototype", container[name].prototype);
}
//get original data
var owner /*:Object*/ = container[name].__yuiOwner,
funcName /*:String*/ = container[name].__yuiFuncName;
//delete extra information
delete container[name].__yuiOwner;
delete container[name].__yuiFuncName;
//replace instrumented function
owner[funcName] = container[name];
//delete supporting information
delete container[name];
}
},
/**
* Unregisters an object for profiling. It takes the object and looks for functions.
* When a function is found, unregisterMethod() is called on it. If set to recrusive
* mode, it will also unregister objects found inside of this object,
* using the same methodology.
* @param {String} name The name of the object to unregister.
* @param {Boolean} recurse (Optional) Determines if subobject methods should also be
* unregistered.
* @return {Void}
* @method unregisterObject
* @static
*/
unregisterObject : function (name /*:String*/, recurse /*:Boolean*/) /*:Void*/{
//get the object
if (L.isObject(container[name])){
var object = container[name];
for (var prop in object) {
if (typeof object[prop] == "function"){
this.unregisterFunction(name + "." + prop);
} else if (typeof object[prop] == "object" && recurse){
this.unregisterObject(name + "." + prop, recurse);
}
}
delete container[name];
}
}
};
//Give me a hook on my global object
NIRV.YProfiler = Y.Profiler;
//End Closure
})();
@meeech
Copy link
Author

meeech commented Mar 13, 2012

Only limitation right now is you still need to register functions/objects that aren't global, since there's no global window object in titanium like there is in browser.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment