Performance profiling using DevTools code snippets.

Find bottlenecks in live web application using Chrome DevTools code snippets.

Chrome DevTools code snippets became my favorite tool when investigating performance bottlenecks in web applications. A JavaScript fragment can be stored as a named snippet in the "Sources" DevTools panel and executed in the current page's context, just as if it were a code executed in the browser's console.

To create a new code snippet, open the Chrome DevTools, select the "Sources" panel, and then the "Snippets" tab on the left. It is next to the page's own scripts and loaded extensions' tabs. Right click anywhere in the snippets' list and select "New" option.

To quickly execute a code snippet, press CMD+Enter or right click and select "Run" option.

Simple examples

A simple code snippet could show time to first paint event using the browser's performance structure. This example is taken from the excellent Wait, Chrome DevTools can do that? video.

first paint

Building up on the previous example, one can get all the page's time information by creating a code snippet that contains addyosmani/timing.js. Running this snippet gives lots of interesting numbers for the current page.

timing

Main Code Snippets features

The above two examples give a taste of the code snippets. Their important traits are:

  • You can set breakpoints and debug the snippet's code just like a regular JavaScript source file.
  • You are targeting the latest Chrome browser only, thus the cross-browser compatibility and the obsolete browser support are not a problem.
  • Any library already included on the page or a global variable can be used from the snippet.
  • Remember to wrap the code snippet in a closure to prevent leaking variables into the global namespace.
  • The returned value from the code snippet is printed by the console, thus it prints undefined in the examples above.
  • You cannot pass any arguments to a code snippet, thus you need to modify the snippet itself.
  • The code snippets are a lot more user and developer friendly than the bookmarklets - the code is clearly visible and can be modified and saved at will.
  • One can even include an entire library in a code snippet, or download new scripts dynamically, see remote download section.

I have already shown two useful code snippets that report the timing information for the current page. There are lots of useful general snippets one can apply as performance tools. I will show several performance CPU profiling and space measurement snippets.

Profiling live applications

Compared to the synthetic benchmarks, code snippets allow us to profile a specific feature in a deployed web application. For example let us find first N prime numbers when a user clicks a button.

primes screenshot

In a typical use case scenario showing, the application performs fine when N is small. Then the users who enter the larger values of N start complaining that the application takes too long to compute the result. To resolve the complains, we will analyze the application performance using the code snippets.

We can inspect the application's source to find that the application executes a callback on the button click event.

1
2
3
4
5
6
7
8
9
10
11
12
13
var primesApp = {
findFirstPrimes: function () {
var n = Number(document.querySelector('#n').value);
console.log('finding first', n, 'primes');
var primes = findFirstPrimes(n);
renderPrimes(primes);
}
};
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('#find').addEventListener('click', function () {
primesApp.findFirstPrimes();
});
});

We can start the performance improvement process by first measuring how long the method primesApp.findFirstPrimes takes for different values of N. It is a method of the object primesApp and thus can be easily wrapped from the outside. To show how long this method runs, I am using console.time and console.timeEnd calls from the Chrome console api. The code below is in the time-method-call.js code snippet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create new code snippet time-method-call.js
(function timeMethodCall() {
// edit the object reference and method name to match your application
var object = primesApp;
var methodName = 'findFirstPrimes';
var originalMethod = object[methodName];
console.assert(typeof originalMethod === 'function', 'cannot find method ' + methodName);
object[methodName] = function () {
console.time(methodName);
originalMethod.call(object);
console.timeEnd(methodName);
// restore the original methodName
object[methodName] = originalMethod;
};
}());

We can run this code snippet multiple times to discover by how much the find method slows down for larger values of N

time method call

We now see that the method's performance clearly deteriorates for larger values of N. But which part of our code is the actual bottleneck? Let us get this information by using the profile-method-call.js snippet. This snippet uses profile(); ... profileEnd(); instead of time(); ... timeEnd(); to wrap the desired method. This creates the detailed CPU profile, shown below as a chart. The CPU profile collected by the snippet is a lot more precise than starting and stopping the profiler manually, especially for short method calls.

chart

The bottom row of the chart shows the individual calls to the isPrime function. The individual calls are too small to see, but there are thousands of them. We can get a better picture by switching to the Heavy (Bottom Up) view:

heavy

Not only we see that the function isPrime takes the majority of the execution time, Chrome v8 JavaScript engine also warns us that it cannot optimize this function because it contains the try - catch block.

1
2
3
4
5
6
7
8
9
10
11
12
13
function isPrime(n) {
try {
var k;
for (k = 2; k < n; k += 1) {
if (n % k === 0) {
return false;
}
}
} catch (err) {
console.error(err);
}
return true;
}

In this case the try - catch statement is completely unnecessary. Let us remove it and profile the application again. The time to find the first 1000 primes drops from 8 to 3 seconds. The bottleneck in isPrime has been completely removed - it now takes just a few milliseconds.

removed try/catch

The difference between the original unoptimized JavaScript and optimized can be extreme. Using these code snippets against the live application and executing the same use case scenario as the actual user, we can continue profiling the application and removing bottlenecks one by one until we are happy with the overall performance.

Space storage measurements

The application's performance depends often on the amount of storage and memory used. For example, if loading previously saved data from the local storage becomes slow over time, we can quickly measure the amount of data stored using local-storage-size.js

1
2
3
4
5
6
7
// based on the answer to the question
// http://stackoverflow.com/questions/4391575/how-to-find-the-size-of-localstorage
(function showLocalStorageSize(){
...
var sizes = Object.keys(localStorage).map(toSize).map(toSizeMB);
console.table(sizes);
}());

Running this script shows the entries with large space requirements, and sometimes shows additional interesting facts. For example the pivotaltracker.com dashboard stores its JavaScript functions in the local storage to speed up the startup.

pivotal

There are other scripts that can help analyze storage requirements in a live web application:

Conclusions

Code snippets are powerful and useful tools for profiling live applications. They are external to the application's code and the development workflow, yet execute in the context of the application without any restrictions. They can time, profile and measure almost anything in the application. The only limitations are constraints of the JavaScript language itself and your imagination.

Additional information: