March 30 2018

Displaying Page Load Metrics on Your Site

I was browsing Tim Kadlec’s website and I noticed he had added page load time metrics in the footer.

Tim Kadlec's site shows how long the page took to load in the footer Tim Kadlec’s site shows how long the page took to load in the footer.

Stoyan Stefanov also realized and wrote “This page loaded in X seconds”, a blog post describing the code used for this. Stoyan also created a bookmark that shows an alert with the load time of the current page. The data is obtained from window.performance.

I liked the idea and added a similar snippet that shows the page load time in the footer (you should see it if you scroll to the bottom). If your browser supports the Paint Timing API you will see a couple of extra metrics: First Paint and First Contentful Paint.

First Paint and First Contentful Paint

Page load time is a metric that tells us part of the story. Yet it might not reflect how fast the visible area loads. For instance, a page with lots of images will report a large page load time, since the load event will be triggered when all of them are fetched, even though the above-the-fold content might load way earlier. It is still a good idea, since it forces us to think about lazy-loading the resources when needed.

I have written before about user perceived performance and metrics that tell how long it takes to render something on the page. Using the Paint Timing API we can get the First Paint and First Contentful Paint metrics.

My code snippet extends Tim’s and Stoyan’s to report these metrics, obtained running performance.getEntriesByType('paint'):

window.addEventListener('load', () => {
  setTimeout(() => {
    const t = window.performance && performance.timing;
    const round2 = num => Math.round(num * 100) / 100;
    if (t) {
      const timingStats = document.querySelector('.timing-stats');
      const loadTime = (t.loadEventEnd - t.navigationStart) / 1000;
      let timingStatsHTML = `This page loaded in ${round2(loadTime)} seconds. `;
      const perfEntries = performance.getEntriesByType('paint');
      perfEntries.forEach((perfEntry, i, entries) => {
        timingStatsHTML += `${perfEntry.name} was ${round2(perfEntry.startTime / 1000)} seconds. `;
      });
      timingStats.innerHTML = timingStatsHTML;
    }
  }, 0);
});

In the future I would like to extend the reported metrics to include First Meaningful Paint (whenever it is implemented, see a description of the heuristics here) and Time to Interactive (using GoogleChromeLabs/tti-polyfill).

Slide from Web Performance: Leveraging the Metrics that Most Affect User Experience from Google I/O ‘17 showing different key moments during a page load.

Leonardo Zizzamia has been working on Perfume.js, a library to measure these metrics, annotate them to the dev tools timeline and optionally reporting them to Google Analytics. It also has a fallback for browsers that do not support the Paint Timing API.

You can read more about the library on his posts “First (Contentful) Paint with a touch of Perfume(.js) and “Time to Interactive with RUM“.

If you are into this topic, I also recommend you to watch the talk Web Performance: Leveraging the Metrics that Most Affect User Experience from Google I/O ‘17.

How to Calculate the Transfer Size

The Resource Timing API allows to know the transfer size of the assets fetched by the page. For CORS requests is necessary to include the timing-allow-origin header set up properly to return the transfer size. Otherwise they will report 0 as the transfer size.

Another caveat is that there doesn’t seem to be a way to know the transfer size of the page itself. One could calculate the length of the document’s innerHTML, but that won’t match the transfer size if the response was compressed (which hopefully was).

Let’s have a look at an example:

Calculating the transferred size of the requests using Resource Timing API. Calculating the transferred size of the request susing Resource Timing API.

The page that I’m loading, served from jmperezperez.com, makes requests to fetch assets from res.cloudinary.com and www.google-analytics.com, which are external domains. Once loaded, I run this code to calculate the transferred size:

const totalBytes = performance.getEntriesByType('resource')
  .reduce((a, r) => {
    return a + r.transferSize;
  }, 0);
console.log(`Page size is ${Math.round(totalBytes / 1024)} kB`);

The reported transferred size according to the dev tools is 113 kB, while the calculated using the code above (Resource Timing API) is 107 kB. Both res.cloudinary.com and www.google-analytics.com set the timing-allow-origin: * response header, which let us get the right transferSize.

You can read more about Cloudinary’s usage of Server Timing on their recent post “Inside the Black Box with Server-Timing”.

Although we can’t get the exact page load size, using these APIs get us way closer.

Reporting Metrics Inline and RUM

These new browser APIs allow us to access metrics from JavaScript, which previously could only be accessed manually using the developer tools. Access from the browser means we can show them to the user or report them to a Real User Monitoring solution to track and optimize user’s experience.

Displaying these metrics on our sites is a way to communicate publicly that we are taking performance seriously.


Tags: