JavaScript Profiler

Every web developer who developed web applications or sites for mobile devices aware of the lack of tool to profile JavaScript execution times. Although there are some developer tools available for mobile debugging, such as iWebInspector and weinre (WEb INspector REmote), non of them give the ability to profile JavaScript execution times on remote devices as it can be done in Developer Tools in Chrome and Safari or in Firebug in Firefox.

As an example I want to show you some piece of code and its profile results in Chrome Developer Tools and Firebug.

function MyClass() {
	this.foo();
}

MyClass.prototype.foo = function foo() {
	var i;
	for (i = 0; i < 100; i++) {
		document.createElement("div");
	}
	this.bar();
};

MyClass.prototype.bar = function bar() {
	var i;
	for (i = 0; i < 100; i++) {
		document.createElement("div");
	}
};

var obj = {
	funcA: function funcA() {
		var i;
		for (i = 0; i < 10; i++) {
			this.funcB();
		}
	},
	funcB: function funcB() {
		var i, c;
		for (i = 0; i < 10; i++) {
			c = new MyClass();
			c.bar();
		}
	}
};

function doSomething() {
	var i;
	for (i = 0; i < 10; i++) {
		obj.funcA();
	}
	return false;
}

I have invoked doSomething() function once while recording profile in Chrome Developer Tools and Firebug and got following results:

Profiling JavaScript execution time in Chrome

Profiling JavaScript execution time in Chrome gives an in-depth overview of function call hierarchy with an average execution time of code within each function (the self “column”) and an average execution time of all functions called from within that function (the total “column”).

One thing to note regarding Chrome profiling tool is that it doesn’t show total count of each executed function, as it is shown in Firebug (see below). In addition, times shown in this tool isn’t accurate: self time of “MyClass” constructor is too high relative to time of functions “foo” and “bar” invoked from within that constructor, while there is no code executed in this constructor except for calling “foo” function.

Profiling JavaScript execution time in Firefox

Profiling JavaScript execution time in Firebug gives an overview of executed functions. For each function the overview shows number of calls, own and total times, average time and other data.

Unlike the Chrome profiling tool, Firebug doesn’t show function call hierarchy, but does show other useful data.

The Problem

Anyway, none of the above tools can help us when we want to profile JavaScript on mobile devices. We have neither Chrome Developer Tools nor Firebug on mobile devices. Weinre, a tool I’ve mentioned before, at first glance, seems to allow profiling JavaScript on mobile devices remotely just like Webkit’s Web Inspector. But it doesn’t have the “Profiles” tab in its panel, so it is no go. And iWebInspector allows profiling JavaScript executed from iOS simulator only. So it seems that everything is lost. But, its not over yet. Put your thumbs up, coz we will fix that!

The Idea!

Well, my idea was to create a JavaScript library that will allow simple profiling of functions and class methods by collecting their execution time. Then, parsing collected data and displaying it in remote console or even in nicely designed dialog. One downside of this tool is that all functions and classes intended for profiling must be registered with the profiler before starting to profile them.

Step 1: registering functions and classes to be profiled

As I have already noted, all functions and classes intended for profiling must be registered with the profiler before starting the profiler. Before registering any functions you need to instantiate profiler object: var profiler = new JsProfiler();. Then you can start registering functions, classes and objects on that profiler instance.

Registering single functions:

To register single functions, invoke next method of the profiler instance:

registerFunction(contextObject, functionName, contextObjectLabel);

The contextObject parameter is the object on which the registered function is defined. The functionName parameter is a string indicating name of the registered function. In other words, the reference of the registered function can be obtained by using contextObject[functionName]. This string also will be used to denote the function in profiler results. If your function defined in global scope, then contextObject is the window object. This is obvious because reference to all global functions can be obtained by using window[functionName]. Third, contetObjectLabel, parameter is a string which will be used to prefix function name in profiler results.

Registering objects:

To register all functions defined on an object, including any defined getters and setters, invoke next method of the profiler instance:

registerObject(object, objectLabel)

The object parameter is the object of whose functions you want to register. And the objectLabel parameter is the a string which will be used to prefix functions of this object in profiler results.

Registering classes:

To register classes, including their constructor methods, prototype (instance) and class (static) methods, and any defined getters and setters (again, both on prototype and class), invoke next method of the profiler instance.

registerClass(contextObject, className)

Just like registering functions, the first parameter is the object on which the registered class is defined. And the second parameter is a string indicating name of the class. This string will be also used to prefix class static functions and class instance methods in profiler results. One thing to note is that only functions from the first prototype in the prototype chain of the class will be registered.

Recalling my first code snippet, to register the global scoped function doSomething(), the functions declared on object obj and the class MyClass() including its constructor and instance methods I would do the follwoing:

var profiler = new JsProfiler();
profiler.registerFunction(window, "doSomething", "window");
profiler.registerClass(window, "MyClass");
profiler.registerObject(obj, "obj");

step 2: profiling javascript execution time

Now, after we have registered all functions that we want to profile, we need to start the profiler to begin collecting their profile data. Without starting the profiler, invoking registered functions won’t collect any profile data. Starting profiler instance is simple as invoking its start method:

profiler.start();

Now we can start invoking registered functions to collect their execution times. Every time one of the registered functions will be invoked, the profiler will record its execution time.

In my case I will execute the doSomething function which will call all other registered functions:

doSomething();

After we have done with collecting profile data we need to stop the profiler. You guessed right! Stopping profiler instance is even simpler than starting it:

profiler.stop();

step 3: printing results

After we have collected all the profile data we can display it in different ways. But first, we need to get the raw collected data by running:

var results = profiler.getResults();

The return value of getResults method is an object representing all the collected profile data. This object has hierarchical structure matching hierarchical invokation of registered functions. Root properties of this object hold reference to objects representing functions invoked first in each call-stack. Each such object has several properties holding collected data related to execution times of each function and additional children property which is another object with properties holding reference to objects representing functions invoked from previous function. And so on, creating hierarchical data structure. So assuming we have three registered functions defined in global scope: rootFunction, innerFunction1 and innerFunction2, where rootFunction calls to two other function we can get following result:

{
	"window.rootFunction()": {
		"count": 1,
		"total": 250,
		"totalAverage": 250,
		"totalChildrenTime": 200,
		"self": 50,
		"selfAverage": 50,
		"children": {
			"window.innerFunction1()": {
				"count": 1,
				"total": 100,
				"totalAverage": 100,
				"totalChildrenTime": 0,
				"self": 100,
				"selfAverage": 100,
				"children": {}
			},
			"window.innerFunction2()": {
				"count": 1,
				"total": 100,
				"totalAverage": 100,
				"totalChildrenTime": 0,
				"self": 100,
				"selfAverage": 100,
				"children": {}
			}
		}
	}
}

To display this data in more clear way I have created two different output methods. But of course, you can create other output formats and destinations. So, after we’ve received results object, we can pass it to one of the output methods:

JsProfiler.HtmlTableGenerator.showTable(results);

This method will show a dialog with an HTML table allowing to browse through call-stack hierarchy similar to Chrome Profiler, but unlike Chrome, this table has more execution time related data similar to FireBug:

JavaScript Profiler HTML Result Table

Here is a demo of the whole code and the result table from JsFiddle:

Second method outputs the data to browser’s console using the console.log method:

JsProfiler.ConsoleTableGenerator.printResults(results)

JavaScript Profiler Console Table

Of course you can just use the raw data to send it to elsewhere for later examination.

The JsProfiler is available at GitHub, just include the jsProfiler.js file in your code and start profiling your code to make a better web!


comments powered by Disqus