🤯 50% Off! 700+ courses, assessments, and books

Discovering the User Timing API

Aurelio De Rosa
Share

A couple of months ago, I gave a talk about some HTML5 APIs which partially focused on how to measure performance. One technique is via the High Resolution Time API, an API described in a previous article of mine. The API allows you to retrieve the current time in sub-millisecond resolution without being subject to system clock skew or adjustments, which in turn enables us to accurately measure the performance of our JavaScript code. While this API is useful, it leaves us with the pain of introducing a bunch of variables in our code. In addition, if you want to measure the performance of code that is split among several files or modules, you must introduce global variables. To solve these problems, we can employ the User Timing API.

What’s the User Timing API

The User Timing API is defined as an interface to help web developers measure the performance of their applications by giving them access to high precision timestamps. Stated in other words, this API allows you to accurately measure and report the performance of JavaScript code, which is perfect when you need to benchmark your code. As of this API is a W3C Recommendation. Therefore, the specifications are stable and you can expect few changes.

This API deals with two main concepts: Mark and Measure. The first implements the PerformanceMark interface, while the second implements the PerformanceMeasure interface. Both these interfaces, extend the PerformanceEntry interface. A Mark represents an instant (timestamp), while a Measure represents the time elapsed between two Marks. Because both of them extend the PerformanceEntry interface, they own the same four, read-only properties:

  • name: A mnemonic name associated with the Mark or the Measure that is used to retrieve it.
  • entryType: Specifies the type of the object, that is if it’s a Mark or a Measure.
  • startTime: If the object is a Mark, this is a DOMHighResTimeStamp, a highly accurate timestamp retrieved using the performance.now() method of the High Resolution Time API. If the object is a Measure, it contains the DOMHighResTimeStamp of the start Mark of the Measure.
  • duration: If the object is a Mark, the value is always 0 (zero). If the object is a Measure, it contains the time elapsed between the two Marks.

This User Timing API exposes four methods that belong to the window.performance object. They are:

  • mark(name): Stores a DOMHighResTimeStamp with the associated name.
  • clearMarks([name]): Deletes one or all the stored Marks.
  • measure(name[, mark1[, mark2]]): Stores the time elapsed between two Marks with the provided name.
  • clearMeasures([name]): Deletes one or all of the stored Measures.

Please note that the names passed to the mark() and the measure() functions are not unique IDs. You can use the same name as many times as you want. In this case, when you perform a name retrieval, an array sorted by the startTime property is returned.

Before moving forward, let’s see a simple example that employs some of the methods described. Let’s say that we want to measure the execution time of a function and then delete the measurement without displaying the data. The code to perform this task is shown below:

performance.mark("startFoo");
// A time consuming function
foo();
performance.mark("endFoo");

performance.measure("durationFoo", "startFoo", "endFoo");

// Delete all Marks
performance.clearMarks();
// Delete the Measure "durationFoo"
performance.clearMeasure("durationFoo");

This snippet shows how we can call all of the previously introduced methods. However, storing timestamps and then deleting them without using the measurements is completely useless. To retrieve the data of the Marks and the Measures, we need to employ other two methods that belong to the Performance interface: getEntriesByType(type) and getEntriesByName(name). The former returns a list of entities of the type specified by the type parameter (ie. “mark” for Marks). The latter returns a list of the entities with the name specified by the name parameter. Both of them return the list sorted based on the startTime property.

Browser Support

Support for this API is decent both on desktop and mobile browsers. In addition, those that do support this API don’t employ a vendor prefix. The desktop and mobile browsers that implemented the User Timing API are Internet Explorer 10+, Chrome 25+, and Opera 15+. However, we can expect Firefox to support it very soon because of its current stage in the W3C recommendation process.

“OK, but what if I want to use this API in browsers that don’t support it?”

Glad you asked! Fortunately for us, there is a polyfill called usertiming.js that allows us to use the previously described methods. The bad news is that this polyfill only works in browsers that support the High Resolution Time API and its performance.now() method.

Demo

This section provides a simple demo that allows you to experiment with the concepts explained in this article. The demo defines a simple form with two input fields. Inside them, we have two numbers that we’ll use to simulate a time consuming function of a given duration. We also test for browser support and display an “API not supported” message if the user’s browser does not support the API. If the browser supports the User Timing API, we attach a listener to the click event of the button inside the form. Once clicked, we run the two simulated functions and store the timestamps. Then, we measure the elapsed time and display some of the stored information. A live demo of the code below is available here.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta name="author" content="Aurelio De Rosa">
    <title>User Timing API Demo by Aurelio De Rosa</title>
    <style>
      body
      {
        max-width: 500px;
        margin: 2em auto;
        font-size: 20px;
      }

      h1
      {
        text-align: center;
      }

      .hidden
      {
        display: none;
      }

      .field-wrapper
      {
        margin-bottom: 1em;
      }

      .button-demo
      {
        padding: 0.5em;
        display: block;
        margin: 1em auto;
      }

      .author
      {
        display: block;
        margin-top: 1em;
      }
    </style>
  </head>
  <body>
    <h1>User Timing API</h1>
    <form>
      <div class="field-wrapper">
        <label for="count-ut-1">Test 1: Count until</label>
        <input type="number" id="count-ut-1" value="100000" />
      </div>

      <div class="field-wrapper">
        <label for="count-ut-2">Test 2: Count until</label>
        <input type="number" id="count-ut-2" value="1000000" />
      </div>

      <button type="button" id="button-play-ut" class="button-demo">Run demo</button>
    </form>
    <span id="ut-unsupported" class="hidden">API not supported</span>
    <div id="ut-results" class="hidden">
      <h2>Marks</h2>
      <div id="ut-marks"></div>
      <h2>Measures</h2>
      <div id="ut-measures"></div>
    </div>

    <small class="author">
      Demo created by <a href="http://www.audero.it">Aurelio De Rosa</a>
      (<a href="https://twitter.com/AurelioDeRosa">@AurelioDeRosa</a>)
    </small>

    <script>
      window.performance = window.performance || {};
      if (performance.mark === undefined) {
        document.getElementById('ut-unsupported').classList.remove('hidden');
        document.getElementById('button-play-ut').setAttribute('disabled', 'disabled');
      } else {
        document.getElementById('button-play-ut').addEventListener('click', function() {
          var i;
          var perfMarks;
          var perfMeasures;

          document.getElementById('ut-results').classList.remove('hidden');
          // A time consuming function
          performance.mark("startTime1");
          for(i = 0; i < parseInt(document.getElementById('count-ut-1').value); i++);
            performance.mark("endTime1")

            // Another time consuming function
            performance.mark("startTime2");
            for(i = 0; i < parseInt(document.getElementById('count-ut-2').value); i++);
              performance.mark("endTime2");
              performance.measure("durationTime1", "startTime1", "endTime1");
              performance.measure("durationTime2", "startTime2", "endTime2");
              performance.measure("durationTimeTotal", "startTime1", "endTime2");

              // Print marks
              perfMarks = performance.getEntriesByType("mark");
              document.getElementById('ut-marks').innerHTML = '';
              for (i = 0; i < perfMarks.length; i++) {
                document.getElementById('ut-marks').innerHTML +=
                  "Name: " + perfMarks[i].name + " - " +
                  "Start Time: " + perfMarks[i].startTime + "<br />";
              }

              // Print measures
              perfMeasures = performance.getEntriesByType("measure");
              document.getElementById('ut-measures').innerHTML = '';
              for (i = 0; i < perfMeasures.length; i++) {
                document.getElementById('ut-measures').innerHTML +=
                  "Name: " + perfMeasures[i].name + " - " +
                  "Duration: " + perfMeasures[i].duration + "<br />";
              }
              performance.clearMarks();
              performance.clearMeasures();
        });
      }
    </script>
  </body>
</html>

Conclusion

This article has explored the User Timing API and showed how it can help you in testing the performance of your JavaScript code. Performance is really important and we should fight for even the slightest improvement.

This API doesn’t not introduce too many concepts, so it should not be hard for you to digest its properties and methods. In addition, its support among browsers is pretty good, so you can use it reliably right now. However, for those that don’t support the User Timing API (most notably Firefox), a polyfill is available.