557 lines
21 KiB
JavaScript
557 lines
21 KiB
JavaScript
/*
|
|
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
|
|
Code licensed under the BSD License:
|
|
http://developer.yahoo.net/yui/license.txt
|
|
version: 2.8.0r4
|
|
*/
|
|
YAHOO.namespace("tool");
|
|
|
|
/**
|
|
* The YUI JavaScript profiler.
|
|
* @module profiler
|
|
* @namespace YAHOO.tool
|
|
* @requires yahoo
|
|
*/
|
|
|
|
/**
|
|
* Profiles functions in JavaScript.
|
|
* @namespace YAHOO.tool
|
|
* @class Profiler
|
|
* @static
|
|
*/
|
|
YAHOO.tool.Profiler = function(){
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 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,
|
|
|
|
lang = YAHOO.lang;
|
|
|
|
/**
|
|
* 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: []
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Singleton Object
|
|
//-------------------------------------------------------------------------
|
|
|
|
return {
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 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 (lang.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
|
|
lang.augmentObject(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.
|
|
* @static
|
|
*/
|
|
getFullReport : function (filter /*:Function*/) /*:Object*/ {
|
|
filter = filter || function(){return true;};
|
|
|
|
if (lang.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, such as YAHOO.util.Dom, 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 (!lang.isObject(owner)){
|
|
owner = eval(name.substring(0, name.lastIndexOf(".")));
|
|
}
|
|
|
|
//get the method and prototype
|
|
method = owner[funcName];
|
|
prototype = method.prototype;
|
|
|
|
//see if the method has already been registered
|
|
if (lang.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 = (lang.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 (lang.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 (lang.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 (lang.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];
|
|
}
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
YAHOO.register("profiler", YAHOO.tool.Profiler, {version: "2.8.0r4", build: "2449"});
|