Created
March 13, 2012 21:37
-
-
Save meeech/2031838 to your computer and use it in GitHub Desktop.
YUI Profiler code tweaked to run in titanium
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.