JavaScript Profiler

Use the Profiler tool to find bottlenecks in your JavaScript code. The Profiler periodically samples the current JavaScript call stack and compiles statistics about the samples.

You can launch the Profiler by selecting "Profiler" from the "Web Developer" menu. You'll find the "Web Developer" menu under the "Tools" menu on Linux and OS X, and directly under the "Firefox" menu on Windows.

The Toolbox will open, with the Profiler selected.

Sampling Profilers

The JavaScript profiler is a sampling profiler. This means that it periodically samples the state of the JavaScript engine, and records the stack for the code executing at the time the sample was taken. Statistically, the number of samples taken in which we were executing a particular function corresponds to the amount of time the browser is spending executing it, so you can identify bottlenecks in your code.

For example, consider a program like this:

function doSomething() {
  var x = getTheValue();
  x = x + 1;                   // -> sample A

function getTheValue() {
  return 5;

function logTheValue(x) {
 console.log(x);               // -> sample B, sample C


Suppose we run this program with the profiler active, and in the time it takes to run, the profiler takes three samples, as indicated in the inline comments above.

They're all taken from inside doSomething(), but the second two are inside the logTheValue() function called by doSomething(). So the profile would consist of three stack traces, like this:

Sample A: doSomething()
Sample B: doSomething() > logTheValue()
Sample C: doSomething() > logTheValue()

This obviously isn't enough data to tell us anything, but with a lot more samples we might be able to conclude that logTheValue() is the bottleneck in our code.

Creating a profile

Click the stopwatch button in the Profiler to start recording samples. When Profiler is recording, the stopwatch button will be highlighted. Click on it again to stop recording and save the new profile:

Once you've clicked "Stop", the new profile will open automatically:

This pane's divided into two parts:

  • The left-hand side lists all the profiles you've captured and allows you to load each one. Just above this there are two buttons: the stopwatch button allows you to record a new profile while the Import... button allows you to import previously saved data. When profile is selected, you can save its data as a JSON file by clicking the Save button.
  • The right-hand side displays the currently loaded profile.

Analyzing a profile

The profile is split into two parts:

Profile timeline

The profile timeline occupies the top part of the profile display:

The horizontal axis is time, and the vertical axis is call stack size at that sample. Call stack represents the amount of active functions at the time when the sample was taken.

Red samples along the chart indicate the browser was unresponsive during that time, and a user would notice pauses in animations and responsiveness. If a profile has red samples, consider breaking this code up into several events, and look into using requestAnimationFrame and Workers.

You can examine a specific range within the profile by clicking and dragging inside the timeline:

A new button then appears above the timeline labeled "Sample Range [AAA, BBB]". Clicking that button zooms the profile, and the details view underneath it, to that timeslice:

Profile details

The profile details occupy the bottom part of the profile display:

When you first open a new sample, the sample pane contains a single row labeled "(total)", as in the screenshot above. If you click the arrow next to "(total)", you'll see a list of all the top-level functions which appear in a sample.

Running time shows the total number of samples in which this function appeared1, followed by the percentage of all samples in the profile in which this function appeared. The first row above shows that there were 2021 samples in the entire profile, and the second row shows that 1914, or 94.7%, of them were inside the detectImage() function.

Self shows the number of samples in which the sample was taken while executing this function itself, and not a function it was calling. In the simple example above, doSomething() would have a Running time of 3 (samples A, B, and C), but a Self value of 1 (sample A).

The third column gives the name of the function along with the filename and line number (for local functions) or basename and domain name (for remote functions). Functions in gray are built-in browser functions: functions in black represent JavaScript loaded by the page. If you hover the mouse over a row you'll see an arrow to the right of the function's identifier: click the arrow and you'll be taken to the function source.

Expanding the call tree

In a given row, if there are any samples taken while we were in a function called by this function (that is, if Running Time is greater than Self for a given row) then an arrow appears to the left of the function's name, enabling you to expand the call tree.

In the simple example above, the fully-expanded call tree would look like this:

Running Time Self  
3            100% 1 doSomething()
2              67% 2 logTheValue()

To take a more realistic example: in the screenshot below, looking at the second row we can see that 1914 samples were taken inside the detectImage() function. But all of them were taken in functions called by detectImage() (the Self cell is zero). We can expand the call tree to find out which functions were actually running when most of the samples were taken:

This tells us that 6 samples were taken when we were actually executing detectAtScale(), 12 when we were executing getRect() and so on.


  1. If the function is called multiple times from different sources, it will be represented multiple times in the Profiler output. So generic functions like forEach will appear multiple times in the call tree.