Measuring JavaScript App Performance with the Performance API

John Au-Yeung - Jan 22 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

With the JavaScript Performance API, we have an easy way to measure the performance of a front-end JavaScript apps.

In this article, we’ll look at how to use it to measure our app’s performance.

Performance

We can measure the performance of an app with a few methods. The Performance API provides a precise and consistent definition of time. The API will gives us with a high precision timestamp to mark the time when a piece of code starts running and finishes.

The timestamp is in milliseconds and it should be accurate to 5 microseconds. The browser can represent the value as time in milliseconds accurate to a millisecond if there’re hardware or software constraints that make our browser unable to provide value with the higher accuracy.

We can use it as in the following example:

const startTime = performance.now();
for (let i = 0; i <= 10000; i++) {
  console.log(i);
}
const endTime = performance.now();
console.log(endTime - startTime)
Enter fullscreen mode Exit fullscreen mode

In the code above, we used the performance object to mark the time when the loop starts running and finishes running.

Then we logged the time by subtracting endTime by startTime to give us the elapsed time when the loop ran in milliseconds.

Serializing the Performance object

The performance object is serialized by the toJSON method.

We can use it as follows:

const js = window.performance.toJSON();
console.log(JSON.stringify(js));
Enter fullscreen mode Exit fullscreen mode

Then we get something like:

{"timeOrigin":1579540433373.9158,"timing":{"navigationStart":1579540433373,"unloadEventStart":1579540433688,"unloadEventEnd":1579540433688,"redirectStart":0,"redirectEnd":0,"fetchStart":1579540433374,"domainLookupStart":1579540433376,"domainLookupEnd":1579540433423,"connectStart":1579540433423,"connectEnd":1579540433586,"secureConnectionStart":1579540433504,"requestStart":1579540433586,"responseStart":1579540433678,"responseEnd":1579540433679,"domLoading":1579540433691,"domInteractive":1579540433715,"domContentLoadedEventStart":1579540433715,"domContentLoadedEventEnd":1579540433716,"domComplete":1579540433716,"loadEventStart":1579540433716,"loadEventEnd":0},"navigation":{"type":0,"redirectCount":0}}
Enter fullscreen mode Exit fullscreen mode

logged.

Measuring Multiple Actions

We can use the mark method to mark our actions and the use the measure method to measure the time between actions by passing in the names.

For example, we can measure time with markings as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntriesByName('measureLoop'));
Enter fullscreen mode Exit fullscreen mode

In the code above, we called the mark method before the loop starts and after the loop ends.

Then we call the measure method with a name we create to get the time difference later and both markers so that we can get the time from them and get the time difference.

Then we called performance.getEntriesByName(‘measureLoop’) to get the computed duration with the duration property of the returned object.

‘measureLoop’ is the name we made up to get the time difference by name, and ‘beginLoop' and 'endLoop' are our time markers.

We can get entries marked by the mark method with the getEntriesByType method. It takes a string for the type. To do this, we can write:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntriesByType("mark"))
Enter fullscreen mode Exit fullscreen mode

Then the console.log should get us the following:

[
  {
    "name": "beginLoop",
    "entryType": "mark",
    "startTime": 133.55500000761822,
    "duration": 0
  },
  {
    "name": "endLoop",
    "entryType": "mark",
    "startTime": 1106.3149999827147,
    "duration": 0
  }
]
Enter fullscreen mode Exit fullscreen mode

There’s also a getEntriesByName method which takes the name and the type as the first and second arguments respectively.

For example, we can write:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntriesByName('beginLoop', "mark"));
Enter fullscreen mode Exit fullscreen mode

Then we get:

[
  {
    "name": "beginLoop",
    "entryType": "mark",
    "startTime": 137.6299999828916,
    "duration": 0
  }
]
Enter fullscreen mode Exit fullscreen mode

from the console.log .

We can also use getEntries by passing in an object with the name and entryType properties as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntries({
  name: "beginLoop",
  entryType: "mark"
}));
Enter fullscreen mode Exit fullscreen mode

Then we get something like:

[
  {
    "name": "[https://fiddle.jshell.net/_display/](https://fiddle.jshell.net/_display/)",
    "entryType": "navigation",
    "startTime": 0,
    "duration": 0,
    "initiatorType": "navigation",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 0.2849999873433262,
    "domainLookupStart": 0.2849999873433262,
    "domainLookupEnd": 0.2849999873433262,
    "connectStart": 0.2849999873433262,
    "connectEnd": 0.2849999873433262,
    "secureConnectionStart": 0.2849999873433262,
    "requestStart": 2.3250000085681677,
    "responseStart": 86.29499998642132,
    "responseEnd": 94.03999999631196,
    "transferSize": 1486,
    "encodedBodySize": 752,
    "decodedBodySize": 1480,
    "serverTiming": [],
    "unloadEventStart": 101.23999998904765,
    "unloadEventEnd": 101.23999998904765,
    "domInteractive": 126.96500000311062,
    "domContentLoadedEventStart": 126.9800000009127,
    "domContentLoadedEventEnd": 127.21500001498498,
    "domComplete": 128.21500000427477,
    "loadEventStart": 128.2249999931082,
    "loadEventEnd": 0,
    "type": "navigate",
    "redirectCount": 0
  },
  {
    "name": "[https://fiddle.jshell.net/js/lib/dummy.js](https://fiddle.jshell.net/js/lib/dummy.js)",
    "entryType": "resource",
    "startTime": 115.49500000546686,
    "duration": 0,
    "initiatorType": "script",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 115.49500000546686,
    "domainLookupStart": 115.49500000546686,
    "domainLookupEnd": 115.49500000546686,
    "connectStart": 115.49500000546686,
    "connectEnd": 115.49500000546686,
    "secureConnectionStart": 0,
    "requestStart": 115.49500000546686,
    "responseStart": 115.49500000546686,
    "responseEnd": 115.49500000546686,
    "transferSize": 0,
    "encodedBodySize": 0,
    "decodedBodySize": 0,
    "serverTiming": []
  },
  {
    "name": "[https://fiddle.jshell.net/css/result-light.css](https://fiddle.jshell.net/css/result-light.css)",
    "entryType": "resource",
    "startTime": 115.77999999281019,
    "duration": 0,
    "initiatorType": "link",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 115.77999999281019,
    "domainLookupStart": 115.77999999281019,
    "domainLookupEnd": 115.77999999281019,
    "connectStart": 115.77999999281019,
    "connectEnd": 115.77999999281019,
    "secureConnectionStart": 0,
    "requestStart": 115.77999999281019,
    "responseStart": 115.77999999281019,
    "responseEnd": 115.77999999281019,
    "transferSize": 0,
    "encodedBodySize": 49,
    "decodedBodySize": 29,
    "serverTiming": []
  },
  {
    "name": "beginLoop",
    "entryType": "mark",
    "startTime": 128.3699999912642,
    "duration": 0
  },
  {
    "name": "measureLoop",
    "entryType": "measure",
    "startTime": 128.3699999912642,
    "duration": 887.0650000171736
  },
  {
    "name": "endLoop",
    "entryType": "mark",
    "startTime": 1015.4350000084378,
    "duration": 0
  }
]
Enter fullscreen mode Exit fullscreen mode

from the console.log .

With markers, we can name our time markers, so we can measure multiple actions.

Clearing Actions

We can clear performance markers by calling the clearMarks method. For example, we can do that as follows:

performance.mark("dog");
performance.mark("dog");
performance.clearMarks('dog');
Enter fullscreen mode Exit fullscreen mode

There’s also a clearMeasures method to clear measurements and clearResourceTimings to clear performance entries.

For example, we can use it as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
performance.clearMeasures("measureLoop");
console.log(performance.getEntriesByName('measureLoop'));
Enter fullscreen mode Exit fullscreen mode

Then we should see an empty array when we call getEntriesByName .

To remove all performance entries, we can use the clearResourceTimings method. It clears the performance data buffer and sets the performance data buffer to zero.

It takes no arguments and we can use it as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
performance.clearResourceTimings();
Enter fullscreen mode Exit fullscreen mode

In the code above, we called the clearResourceTimings method to reset the buffers and performance data to zero so that we can run our performance tests with a clean slate.

Conclusion

We can use the Performance API to measure the performance of a piece of front-end JavaScript code.

To do this, we can use the now method to get the timestamp and then find the difference between the 2.

We can also use the mark method to mark the time and then use the measure method to compute the measurement.

There’re also various ways to get the performance entries and clear the data.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .