/* Copyright (c) 2011, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 2.9.0 */ 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.9.0", build: "2800"});