# Yahoo! UI Library

### profiler 3.0.0b1

Yahoo! UI Library > profiler > Profiler.js (source view)
Search:
Filters
Show Private Show Protected Show Deprecated

        /**
         * The YUI JavaScript profiler.
         * @module profiler
         * @requires yui
         */

        //-------------------------------------------------------------------------
        // 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: []
            };
        }

        /* (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.
             * @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(".")));
                }

                //get the method and prototype
                method = owner[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];
                }

            }

        };

Copyright © 2009 Yahoo! Inc. All rights reserved.