This evening I was playing around with the idea of profiling jQuery applications – trying to find a convenient way to completely analyze all the code that is being executed in your application.
I’ve come up with a plugin that you can inject into a jQuery site that you own and see how the performance breaks down method-by-method.
Here’s how you can try this plugin on your own site:
Step 1: Copy site HTML, add base href, add plugin.
For example, Github.com uses jQuery for a few basic effects and pieces of interaction (they use considerably more on pages beyond the homepage).
I took a copy of their page, added a <base href> to the top and injected the profiling plugin giving a resulting test page.
Before:
<head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> ... <script src="/javascripts/bundle.js"></script> ... </head>
After:
<head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <base href="http://github.com/"/> ... <script src="/javascripts/bundle.js"></script> <script src="http://dev.jquery.com/~john/plugins/profile/jquery-profile.js"></script> ... </head>
Step 2: Use the site normally.
Use the site as you normally would. Load it up, click around – perform normal interactions. In the case of the Github.com page I let it load, scrolled down, and clicked on one of the demo images – which caused an overlay to appear. I then closed the X on the overlay and let it hide.
Step 3: View data.
In your console type jQuery.displayProfile();
and scroll down to the bottom of the page. You should see something like the following:
and here’s a sample data dump:
% | (ms) | Method | in | out |
---|---|---|---|---|
0.0% | 0 | jQuery(#document) | 1 | |
0.0% | 0 | .bind(“ready”, function()) | 1 | 1 |
3.6% | 6 | jQuery(“a[hotkey]”) | ||
0.0% | 0 | .each(function()) | ||
0.0% | 0 | jQuery(#document) | 1 | |
0.0% | 0 | .bind(“keydown.hotkey”, function()) | 1 | 1 |
0.0% | 0 | jQuery(“#triangle”) | ||
0.0% | 0 | jQuery(“body”) | 1 | |
1.2% | 2 | .append(“<div id=”triangle” style=”position: absolute; display: none;”> </div>”) | 1 | 1 |
0.6% | 1 | jQuery(“#repo_menu .active”) | ||
3.6% | 6 | jQuery(“.jshide”) | ||
0.0% | 0 | .hide() | ||
1.2% | 2 | jQuery(“.toggle_link”) | ||
0.0% | 0 | .click(function()) | ||
0.6% | 1 | jQuery(“#beta :text”) | ||
0.0% | 0 | .focus(function()) | ||
0.6% | 1 | jQuery(“#beta form”) | ||
0.0% | 0 | .ajaxForm(function()) | ||
1.2% | 2 | jQuery(“.hide_alert”) | ||
0.0% | 0 | .click(function()) | ||
0.0% | 0 | jQuery(“#login_field”) | ||
0.0% | 0 | .focus() | ||
0.0% | 0 | jQuery(“#versions_select”) | ||
0.0% | 0 | .change(function()) | ||
1.2% | 2 | jQuery(“a[rel*=facebox]”) | 3 | |
17.6% | 29 | .facebox() | 3 | 3 |
Event: load (1ms)
% | (ms) | Method | in | out |
---|
Event: click (29ms)
% | (ms) | Method | in | out |
---|---|---|---|---|
6.9% | 2 | jQuery(“#facebox .loading”) | ||
3.4% | 1 | jQuery(“facebox_overlay”) | ||
3.4% | 1 | jQuery(“body”) | 1 | |
6.9% | 2 | .append(“<div id=”facebox_overlay” class=”facebox_hide”></div>”) | 1 | 1 |
0.0% | 0 | jQuery(“#facebox_overlay”) | 1 | |
6.9% | 2 | .hide() | 1 | 1 |
3.4% | 1 | .addClass(“facebox_overlayBG”) | 1 | 1 |
0.0% | 0 | .css(“opacity”, 0) | 1 | 1 |
3.4% | 1 | .click(function()) | 1 | 1 |
6.9% | 2 | .fadeIn(200) | 1 | 1 |
3.4% | 1 | jQuery(“#facebox .content”) | 1 | |
3.4% | 1 | .empty() | 1 | 1 |
3.4% | 1 | jQuery(“#facebox .body”) | 1 | |
0.0% | 0 | .children() | 1 | 2 |
10.3% | 3 | .hide() | 2 | 2 |
0.0% | 0 | .end() | 2 | 1 |
6.9% | 2 | .append(“<div class=”loading”><img src=”/facebox/loading.gif”/></div>”) | 1 | 1 |
0.0% | 0 | jQuery(“#facebox”) | 1 | |
0.0% | 0 | jQuery({…}) | 1 | |
3.4% | 1 | .width() | 1 | |
0.0% | 0 | .css({…}) | 1 | 1 |
6.9% | 2 | .show() | 1 | 1 |
0.0% | 0 | jQuery(#document) | 1 | |
0.0% | 0 | .bind(“keydown.facebox”, function()) | 1 | 1 |
0.0% | 0 | jQuery(#document) | 1 | |
3.4% | 1 | .trigger(“loading.facebox”) | 1 | 1 |
Event: beforeReveal.facebox (1ms)
% | (ms) | Method | in | out |
---|
Event: click (6ms)
% | (ms) | Method | in | out |
---|---|---|---|---|
16.7% | 1 | jQuery(#document) | 1 | |
66.7% | 4 | .trigger(“close.facebox”) | 1 | 1 |
Event: close.facebox (3ms)
% | (ms) | Method | in | out |
---|
This quick table of data should be able to provide you with some interesting information about what’s happening in your code. The result is still incredibly basic (really only providing the most basic level of jQuery method introspection) but it definitely shows some merit.
If you wish to create a different view for the data you can access the raw data structure by running jQuery.getProfile();
.
The next stage of development for this plugin would be to reveal which methods are running inside other jQuery methods – in addition to monitoring other aspects of the application (such as timers, Ajax callbacks, etc.). I’m pleased with even this most-basic result – it gives me the ability to quickly, and easily, learn much more about a jQuery-using application.
4rn0 (June 16, 2008 at 2:04 am)
This is absolutely great John! :-)
You just amaze me time and again with your talent and productivity!
Martin (June 16, 2008 at 3:44 am)
This is really great stuff! Lovly! Cant get it to work on my own sites though.
Matt H (June 16, 2008 at 4:19 am)
This looks great!
Could this be used to create a plug-in for firebug, that would be very useful.
Eran (June 16, 2008 at 4:58 am)
Very nice idea. Unfurtunately it fails for a relatively complex jQuery-based app I tried to profile with it (giving plenty of NaN).
Also, I think you should output to an absolutely positioned div. Depending on the layout of the page, the profiler text could appear in a hidden part of it.
Tane Piper (June 16, 2008 at 5:24 am)
Very nice John, much easier to read than Firebug’s profile console.
Splover (June 16, 2008 at 6:47 am)
Thanks a lot for this. great job. It’s sometimes to evaluate the performance of my code. And this is a very important aspect of apps.
John Resig (June 16, 2008 at 7:32 am)
@Martin: Which site(s) did you try it on? What version of jQuery are you using?
@Matt H: Sure, it might work as a Firebug plugin. For now, at least, I’d like to keep it as generic JavaScript so that we can run it on any browser.
@Eran: Which page(s) did you try this on? What version of jQuery are you using? Obviously this is still very very hot-off-the-presses – incredibly alpha-quality code – and still needs a lot of testing. I’d appreciate any input that you can provide. Naturally, it could be an overlay – I’ll see what I can do.
John Resig (June 16, 2008 at 8:14 am)
@Martin and Eran: I did some more digging and fixed some bugs. Two things were causing problems: jQuery code being executed inline was passing NaN for its final times (fixed) and not all code was being shown to have executed (fixed). The second issue was due to event triggering occurring within the normal flow of execution (which is now taken into account).
For example the profiler now works on NBC.com: example. Also, viewing the above Github.com example now yields many more results.
Steve (June 16, 2008 at 8:45 am)
Wow, that is pretty awesome! I can’t wait to see where this goes. I’m so glad that tools like this are available and are continually being developed, with the web getting ever more complex it’s almost impossible to troubleshoot issues without such tools.
It’s also pretty neat to see that NBC.com uses jQuery, that’s gotta give you a great feeling to see it being adopted by so many people.
Kevin H (June 16, 2008 at 9:28 am)
Could this be made into a bookmarklet to be injected into any jQuery site? like this: http://www.learningjquery.com/2008/06/updated-jquery-bookmarklet
Dirceu Jr. (June 16, 2008 at 9:36 am)
nice!
with certainty can be used in any tool to eliminate the functions of jQuery that are not used in our applications ;)
John Resig (June 16, 2008 at 9:47 am)
@Kevin: Unfortunately injecting it via a bookmarklet is only partially useful (you could analyze events that take place after the injection, only). In order to get the most use (such as being able to analyze inline code and methods that fire on document ready) you’ll need to inject it directly in the page.
Roshan Bhattarai (June 16, 2008 at 11:03 am)
You’re the most wonderful and talented person in this world from my point of view ……..you just rock every time man…
Gimme some of ur talent man I’m jealous…
rektide (June 16, 2008 at 12:25 pm)
Raw timing would be more tangible than %’s. Otherwise the output looks perfect.
John Resig (June 16, 2008 at 1:10 pm)
@rektide: Note the second column – that gives you the raw time in milliseconds.
Sander Aarts (June 16, 2008 at 4:23 pm)
Instead of a bookmarklet, could it perhaps run as a user script (Greasemonkey/Opera)? I’m not sure whether these are triggered first or last though.
John Resig (June 16, 2008 at 4:59 pm)
@Sander Aarts: That might be a possibility – however you’d lose out on injecting it right inbetween where jQuery (and jQuery plugins) are included and inline JavaScript is executed. Not to mention that it wouldn’t work in Safari and Internet Explorer. If you’re doing performance testing it’s probably best to do testing on all platforms, regardless.
ganisha (June 17, 2008 at 2:44 am)
Nice code. Does anyone know s what happened to http://www.getfirebug.com? That site seems to be down for day’s now. (A little off topic, sorry)
Micheil (June 17, 2008 at 5:18 am)
hmm.. nice, I’ve recommended it to my latest employer.. Hopefully it’ll come in handy :D
matsukaz (June 18, 2008 at 11:14 am)
Great work!!
It’s really easy to use.
This plugin also could be nice tool to learn how jQuery works!
If you don’t need to profile the code inside “$(document).ready(fn);”, you can just call the function bellow instead of saving html and adding the code you have mentioned. :)
$(“body”).append(”);
matsukaz (June 18, 2008 at 11:17 am)
Oh sorry, I forgot to replace tags…
$(“body”).append(‘<script src=”http://dev.jquery.com/~john/plugins/profile/jquery-profile.js”></script>’);
Jon (June 18, 2008 at 4:29 pm)
Hey! No news on Firefox 3 release!?
Suplementy CaliVita (June 21, 2008 at 7:15 am)
Very fine, super
Fred G (June 21, 2008 at 5:39 pm)
Very useful. Would be nice if once it has initiated for it to automatically append updates to the same profiler box. My site runs jQuery events on mousemove, mouseover, etc and it would be nice to see this type of performance in real time. Once again though, very useful. I appreciate everything you bring to the community.
Fred G (June 21, 2008 at 7:50 pm)
Was just playing around and I thought this would be a great place to embed Google Charts API. I quickly hacked something together here… It’s pretty neat and could lean towards a full jQuery Testing Suite.
http://fredghosn.com/concept.html
What are your guys thoughts on this?
Sean Nieuwoudt (June 23, 2008 at 7:43 am)
Nice one John!
Thomas (July 14, 2008 at 2:29 pm)
Great plugin; although it doesn’t seem to log the AJAX/JSON calls (the action on the returned value/object). would be great if it handled that as well. Can you tell me if that’s my bad or if that is the case? thanks
Brad Jesness (August 2, 2008 at 8:52 am)
Thanks for your work providing more transparency to jQuery
Eric Martin (August 7, 2008 at 5:00 pm)
Very handy…much easier to understand than the Firebug profile tool!
Thanks John!