1

I have over 500 JavaScript functions that run client side. I want to measure the execution time and relative frequency of each function on the clients and send statistics to a server in order to find out what functions need to be optimized first.

All functions are a part of global objects (if it helps).

How can I make automatic measurements? I need one global function that will watch over all other functions and measure them. Is this possible?

Prosto Trader
  • 3,471
  • 3
  • 31
  • 52
  • 2
    [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance.now) – Oriol Nov 28 '14 at 19:10
  • 1
    Iterate over your namespace objects and wrap every function with a function that collects calls statistics. Send data back to server on `window.unload`. – Yury Tarabanko Nov 28 '14 at 19:15
  • Combine those two suggestions, should work :) – Jonathan Gray Nov 28 '14 at 19:17
  • @YuryTarabanko, that's what I was thinking of, but I don't know how to "wrap every function" - could you explain pls? – Prosto Trader Nov 28 '14 at 19:26
  • @ProstoTrader It's a little complicated even if you do know how to do it. I'll see what I can come up with. – Jonathan Gray Nov 28 '14 at 19:31
  • 1
    [this](http://stackoverflow.com/a/5034657/588079) might help with that. Note that I'd setup a buffer for your logs and submit/flush it every x number of calls, ending with the window.unload to submit/flush the remainder at the end of the session. Also note that you probably can not get access to private (closed over) functions. – GitaarLAB Nov 28 '14 at 19:32
  • @ProstoTrader let me know what you think of my solution please – Jonathan Gray Nov 28 '14 at 20:20

3 Answers3

2

Something like this should do the trick (haven't tested it)

var profiler = (function(win){
   var collector = {},
       wrap = function(fn, name) {
          var report = {calls: 0, times: []}; //create new report obj
          collector[name] = report; //save it to collector
          return function() {
             var start, end, out;
             report.calls++; //number of calls
             start = performance.now();
             out = fn.apply(this, arguments);
             end = performance.now();
             report.times.push(end - start); //time statistics
             return out;
          };
       };

   win.addEventListener('unload', function(){/*send collector here*/});

   return function() {
       [].forEach.call(arguments, function(holder, i) { //iterate over all namespaces
           Object.keys(holder).forEach(function(key){ //iterate over every member
              var fn = holder[key];
              if(typeof fn === 'function') {
                  holder[key] = wrap(fn, i + '_' + key); //replace member
              }
           });
       });
   };

}(window));

Usage

profiler(namespace1, namespace2 ...);
Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98
0

This should work (click here for JSFiddle):

function testFunc(test) {
    for(var i=0;i<100000000;i++) { i = i+test; }
    };

function testFunc2(test) {
    for(var i=0;i<100000000;i++) { i = i+test; }
    };

var getPerfResults = (function(){
    var timeRecords = {}, xTr = 0, name;
    var setPerfRecords = function(oFn, fnIdentity) {
        timeRecords[fnIdentity] = [];
        return function(){
            var xTime = performance.now();
            var xResult = oFn.apply(this, arguments);
            xTime = performance.now()-xTime;
            timeRecords[fnIdentity].push(xTime);
            return xResult;
            };
        };
    for (name in window) {
        try { window[name]; // Security exception may occur here
        if (typeof window[name] === 'function')
            window[name] = setPerfRecords(window[name], name);
            }
        catch(err) { }
        }
    return function() {
        var resultObj = {}, n, i;
        for(i in timeRecords) {
            if(timeRecords.hasOwnProperty(i)
             && timeRecords[i].length > 0) {
                resultObj[i] = 0; 
                for(n=0;n<timeRecords[i].length;n++) {
                    resultObj[i] = resultObj[i]+timeRecords[i][n];
                    }
                resultObj[i] = resultObj[i]/timeRecords[i].length;
                }
            }
        return resultObj;
        };
    }());

testFunc(1);
testFunc(10);
testFunc(100);
testFunc(1000);
testFunc(10000);
testFunc2(0);

document.body.innerHTML = JSON.stringify(getPerfResults());
Jonathan Gray
  • 2,509
  • 15
  • 20
  • You just want to make sure that `getPerfResults()` is initialized after all of the functions that you want the performance of recorded, but before they are actually called. This shouldn't be too hard of a task to accomplish. The logic for sending the data and the how the data is sent is left up to you. – Jonathan Gray Nov 28 '14 at 21:04
  • BTW `getPerfResults()` retrieves the *average* execution time of each global function in relation to `performance.now()`. – Jonathan Gray Nov 28 '14 at 21:08
0

Based on @Yury Tarabanko (that didn't work for me, but gave me an inspiration), here is what I got:

//Global Object where perfomance reuslts will be stored

perf = {};

Then, you need a function that wraps all other functions and tracks perfomance.

//Function that actually tracks the perfomance, wrapping all other functions
function trackPerfomance() {
    var name, fn;
    for (name in jNTRender) { //jNTRender - is the namespace that I was analysing. Use yours or window
        fn = jNTRender[name];
        if (typeof fn === 'function') {
            jNTRender[name] = (function(name, fn) {
                var args = arguments;
                return function() {
                    if (!perf[name]) perf[name] = {
                        timesCalled: 0, 
                        timeTaken: 0,
                        averageTime: 0
                    }

                    var start = performance.now(),
                    out = fn.apply(this, arguments),
                    end = performance.now();
                    perf[name].timesCalled ++; //how many times function was called
                    perf[name].timeTaken += (end - start);  //time taken for execution
                    perf[name].averageTime = perf[name].timeTaken/perf[name].timesCalled; //average execution time
                    return out;


                }
            })(name, fn);
        }
    }
}

And you need to analyse results...

//Function that analyzes results - jQuery used for simplicity
function analyzePerfomance(){

    functionsAverageTime = [];

    $.each(jNTPerfomance, function(functionName, functionPerfomance){
        functionsAverageTime.push([functionName, functionPerfomance.averageTime]);
    });

    functionsAverageTime.sort(function(a, b) { return b[1]-a[1] });

    console.log('Slowest in average functions, msec');
    $.each(functionsAverageTime, function(index, value){
        console.log(index+1, value[0], value[1]);
    });

    functionsTimesCalled = [];
    $.each(jNTPerfomance, function(functionName, functionPerfomance){
        functionsTimesCalled.push([functionName, functionPerfomance.timesCalled]);
    });

    console.log('Most used functions, times');
    $.each(functionsTimesCalled, function(index, value){
        console.log(index+1, value[0], value[1]);
    });

    functionsTotalTimeSpent = [];
    totalTime = 0;
    $.each(jNTPerfomance, function(functionName, functionPerfomance){
        totalTime += functionPerfomance.timeTaken;
    });


    $.each(jNTPerfomance, function(functionName, functionPerfomance){
        functionsTotalTimeSpent.push([functionName, functionPerfomance.timeTaken, 100*functionPerfomance.timeTaken/totalTime]);
    });

    functionsTotalTimeSpent.sort(function(a, b) { return b[1]-a[1] });

    console.log('Time taken by functions, msec, % of total time taken');
    $.each(functionsTotalTimeSpent, function(index, value){
        console.log(index+1, value[0], Math.round(value[1]), value[2].toFixed(2) + '%');
    });

}

Run the tracker from console.

trackPerfomance();

Wait for some time - minutes, hours... And analize perfomance:

analyzePerfomance();

Here is what I get in console. Really usefull, easy to read. And of cource it's possible to send perf object via ajax to server.

enter image description here

Prosto Trader
  • 3,471
  • 3
  • 31
  • 52