Measure Page Load Times Using the User Timing API

Why do I need to do this?

In the Web 2.0 world, the onload event usually isn’t an accurate measurement for when a user is ready to interact with a web page. In fact, on certain sites like Twitter, there is nothing displayed on the screen when the onload event fires in the browser. That’s because sites that are heavily based on JavaScript will often wait until after the onload event to fire JavaScript that then builds the DOM components on the page.

So what’s the solution? To be honest, there aren’t any perfect solutions out there yet, but there are lots of work arounds. WebPageTest does some intelligent calculations to provide a “Visually Complete” time and the “Speed Index“.  However, WebPageTest probably does a little trickery behind the scenes to calculate those numbers so we cannot easily do this in the real world.  Therefore, it’s become the industry standard and best practices to log and track these times on your own. This can be easily done using JavaScript and the modern browser’s User Timing API.

How do I implement this for my app

Here’s a quick high-level step-by-step guide on how to implement page load performance monitoring for your app. It assumes you already know all about the Web Performance Timing API and real user monitoring (RUM).

  1. On each page identify the critical parts of the application that you would like to measure; ask yourself the following questions:
  2. Go through the pages and flows of your application
    • What’s required for the user to interact with the page?
    • When does the page appear to be loaded from a user’s perspective?
    • Do I have any hero content?
    • What dependencies do I want to track?
    • Is there already a metric that aligns with my page? (e.g. – onload, domInterative, speedIndex)
      TIP: Try loading your page in WebPageTest.
      Play the page load back using the video feature and see which metric best aligns for your web page.
  3. Create at least the following marks (w3c recommended names)
    • mark_fully_visible
      • Or make your own flavor of it. At Constant Contact we use ctct_fully_visible
    • mark_fully_loaded
      • At Constant Contact we use ctct_fully_loaded
  4. Send your marks, measures, and/or custom metric variables to your favorite RUM tool(s) (mPulse, Google Analytics, AppDynamics, StatsD)
  5. Pat yourself on the back because you can now accurately measure your true page load times for real users.

What is the User Timing  API?

The User Timing API is a feature in modern browsers that provides access to lots of functions for measuring the performance of our web pages and web applications.

Performance Timing and Navigation

window.performance.timing

  • The PerformanceNavigation object represents the type of navigation that occurs in the given browsing context, like the amount of redirections needed to fetch the resource. PerformanceNavigation docs

window.performance.navigation

  • BONUS – (Chrome only and non-standard) The MemoryInfo object gets quantized scripting memory usage numbers.

window.performance.memory

Mark and Measure

You can use several important API methods to measure performance times and set custom user timings known as marks and measures.

window.performance.now()

The window.performance.now() method returns a DOMHighResTimeStamp, measured in milliseconds and accurate to one thousandth of a millisecond. It’s very similar to JavaScript’s Date.now but has a higher resolution since Date.now is only measured in milliseconds.

Example using window.performance.now():

var startTime = window.performance.now();
 performTask();
 var endTime = window.performance.now();
 console.log("The function performTask took " + (endTime - startTime) + " milliseconds.");

window.performance.mark()

The window.performance.mark() method stores a high res timestamp with the name passed into the method with the elapsed time in milliseconds.

Example using window.performance.mark():

// setting the markwindow.performance.mark("mark_hero_image_loaded");

window.performance.measure

The window.performance.measure method calculates the elapsed time between marks, as well as any time between your custom mark and exposed browser events in the PerformanceTiming interface (performance.timing).

Example using measure

// This will tell me the time it took for the hero image to load (using my hero_image_loaded mark shown above) after the domComplete event was fired/* *  'measure_hero_image_loaded_from_dom_complete' is the name of the measure *  'domComplete' is one of the events exposed in performance.timing *  'mark_hero_image_loaded' is a previous mark that i made*/ window.performance.measure('measure_hero_image_loaded_from_dom_complete', 'domComplete', 'mark_hero_image_loaded');

Retrieving Marks, Measures, and Timings

The next few methods retrieve the marks, measures, and resource timings.

// by nameperformance.getEntriesByName('hero_image_loaded'); // returns an array of PerformanceMark
// get all my marksperformance.getEntriesByType('mark'); // returns an array of PerformanceMark
// get everything (including PerformanceResourceTiming objects)performance.getEntries();
// returns an array of PerformanceResourceTiming objects, PerformanceMark objects, and PerformanceMeasure objects

Deleting marks and measures is easy too.

window.performance.clearMarks();window.performance.clearMeasures();

Polyfill for browsers without user timings support

Patrick Meenan has provided the following code that includes some basic polyfill support for browsers that don’t support user timing or navigation timing – Thanks Patrick Meenan!

https://gist.github.com/pmeenan/5902672#file-user-timing-js

Recommended Mark Names (W3C)

The W3C (World Wide Web Consortium) has some great standards for naming your marks. I strongly recommend that you use these naming conventions to provide consistency throughout your application(s).  Consistent naming will simplify the process when sending and storing RUM data and unify your application’s page load times on a single metric.  (see section about sending data to RUM)

Per the W3C:

Developers are encouraged to use the following Recommended Mark Names to mark common interactions. The user agent is responsible for storing a new timestamp under the specified mark name for Recommended Mark Names, just like any user specified mark name. The user agent does not validate that the usage of the Recommended Mark Name is appropriate or consistent with its description.

Mark Name Description
mark_fully_loaded The time when the page is considered fully loaded as marked by the developer in their application.
mark_fully_visible The time when the page is considered completely visible to an end-user as marked by the developer in their application.
mark_above_the_fold The time when all of the content in the visible viewport has been presented to the end-user as marked by the developer in their application.
mark_time_to_user_action The time of the first user interaction with the page during or after a navigation action, such as scroll or click, as marked by the developer in their application.

 

Usage Examples

Measuring the Critical Rendering Path with Navigation Timing

Ilya Grigorik, self-proclaimed developer advocate and Web Perf Guru, has written up this piece on using Navigation Timing to measure the critical rendering path, Thanks Ilya Grigorik!

pageload1

<html> 
<head>         
<title>Critical Path: Measure</title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
        <script>
          function measureCRP() {
            var t = window.performance.timing,
              interactive = t.domInteractive - t.domLoading,
              dcl = t.domContentLoadedEventStart - t.domLoading,
              complete = t.domComplete - t.domLoading;
            var stats = document.createElement('p');
            stats.textContent = 'interactive: ' + interactive + 'ms, ' +
                'dcl: ' + dcl + 'ms, complete: ' + complete + 'ms';
            document.body.appendChild(stats);
          }
        </script>
      </head>
      <body onload="measureCRP()">
        <p>Here are your Critical Path Timings!</p>
        
      </body>
</html>

Measuring AJAX Calls

Use this code to measure how long your AJAX calls are taking.

var ajaxReq = new XMLHttpRequest();
ajaxReq.open('GET', url, true);
ajaxReq.onload = function(e) {
window.performance.mark('mark_end_ajax_call'); window.performance.measure('measure_ajax_call', 'mark_start_ajax_call', 'mark_end_ajax_call'); performAction(e.responseText); } window.performance.mark('mark_start_ajax_call'); ajaxReq.send();

and for you jQuery fans…

$.ajax( {
url: url,
beforeSend: function(xhr) {
window.performance.mark('mark_start_ajax_call'); // you could just put this immediately after the ajax call instead
})
.always(function(data) {
window.performance.mark('mark_start_ajax_call');
window.performance.measure('measure_ajax_call', 'mark_start_ajax_call', 'mark_end_ajax_call');
performAction(data);
});

 

Multiple AJAX Calls and total AJAX time

But what if you want to track multiple AJAX calls and you also want to know the total time spent making those calls? We can easily track the multiple AJAX calls by just giving them each individual measure names. Tracking the total time spent making AJAX calls is a little trickier. Since the calls are asynchronous you don’t want to just add a bunch of measures together. Instead, you want the actual time spent between the start of the first call and the end of the last call.  We can do this by grabbing those measures and doing simple math.

var ajaxCallCounter = 0; // create counter
var ajaxReq = new XMLHttpRequest();
ajaxReq.open('GET', url, true);
ajaxReq.onload = function(e) {
window.performance.mark('mark_end_ajax_call'); // set end mark
ajaxCallCounter++; // counter so I can track each ajax call individually - i could also just use part of the endpoint instead (probably for identifying the call later)
// break these up into different measures.  Also, by default, the measure method will grab the latest marks for us, so we don't need different mark names
window.performance.measure('measure_ajax_call_' + ajaxCallCounter, 'mark_start_ajax_call', 'mark_end_ajax_call');
performAction(e.responseText);
}
window.performance.mark('mark_start_ajax_call'); // set start mark
ajaxReq.send(); // send request
// later on in the code
var startTime = window.performance.getEntriesByName('mark_start_ajax_call')[0].startTime; // get start time of first mark
var cntEndTimes = window.performance.getEntriesByName('mark_end_ajax_call').length; // get the number of end marks
var endTime = window.performance.getEntriesByName('mark_end_ajax_call')[cntEndTimes-1].startTime; // grab the start time of the last end mark
var totalTimeSpentOnAjax = (endTime - startTime); // calculate total time spent
console.log('Ajax calls took ' + totalTimeSpentOnAjax + 'ms');

// or you may just want to list out all your measures
var measures = window.performance.getEntriesByType('measure');
for (var i = 0; i < measures.length; ++i) {
var measure = measures[i];
console.log('Measure: ' + measure.name + ' took ' + measure.duration + 'ms');
}

Measuring Hero Images Times

This should be pretty easy, right? Well, sort of. Steve Sounders did a lot of work around this and ran into a lot of gotchas.  He talks about this in-depth here in a great read on the topic, so I won’t go any deeper. Basically, to accurately measure the load time of a hero image you need to do the following:

    1. Add an onload handler to the image
    2. Add an inline script to the image
    3. Take the larger of the 2 values

Be sure to clear each mark before setting it to ensure that you only capture the mark that occurred last.

<img src="hero.jpg" onload="window.performance.clearMarks('mark_hero_loaded'); window.performance.mark('mark_hero_loaded')">
<script>
	window.performance.clearMarks('mark_hero_loaded'); // clear if its already loaded
	window.performance.mark('mark_hero_loaded');
</script>

Real User Monitoring (RUM)

Integrating Marks and Measures with your RUM solution

Integrating everything we’ve gone over so far with your RUM solution may sound difficult, but don’t reinvent the wheel. Patrick Meenan already has you covered for boomerang, mpulse, and google analytics in code he’s shared here. I’ve expanded upon his script to add support for measures as well.Thanks again, Patrick Meenan! Here I’ve taken Pat’s code and added support for measures as well — Caution: I have not officially tested this yet!

// Modified to support both Marks and Measures
// Support routines for automatically reporting user timing for common analytics platforms
// Currently supports Google Analytics, Boomerang and SOASTA mPulse
// In the case of boomerang, you will need to map the event names you want reported
// to timer names (for mPulse these need to be custom0, custom1, etc) using a global variable:
// rumMapping = {'aft': 'custom0'};
(function() {
var wtt = function(n, t, b) {
t = Math.round(t);
if (t >= 0  t < 3600000) {
// Google Analytics
if (!b window['_gaq'])
_gaq.push(['_trackTiming', 'UserTimings', n, t]);

// Boomerang/mPulse
if (b window['rumMapping'] window.rumMapping[n])
BOOMR.plugins.RT.setTimer(window.rumMapping[n], t);
}
};
utReportRUM = function(b, markOrMeasure){
var timeField = (markOrMeasure === 'mark') ? 'startTime' : 'duration';
var m = window.performance.getEntriesByType(markOrMeasure);
var lm={};
for (i = 0; i < m.length; i++) {
g = 'usertiming';
if (lm[g] == undefined || m[i][timeField] > lm[g])
lm[g] = m[i][timeField];
p = m[i].name.match(/([^\.]+)\.([^\.]*)/);
if (p && p.length > 2 &&
(lm[p[1]] == undefined ||
m[i][timeField] > lm[p[1]]))
lm[p[1]] = m[i][timeField];
}
for (g in lm)
wtt(g, lm[g], b);
};
utOnLoad = function() {utReportRUM(false, 'mark'); utReportRUM(false,'measure');};
if (window['addEventListener'])
window.addEventListener('load', utOnLoad, false);
else if (window['attachEvent'])
window.attachEvent('onload', utOnLoad);
// Boomerang/mPulse support
utSent = false;
BOOMR = window.BOOMR || {};
BOOMR.plugins = BOOMR.plugins || {};
BOOMR.plugins.UserTiming = {
init: function(config) {BOOMR.subscribe('page_ready', function(){
if (!utSent) {
utReportRUM(true);
utSent=true;
BOOMR.sendBeacon();
}
});},
is_complete: function() {return utSent;}
};
})();

Calculating the SpeedIndex with RUM

SpeedIndex is another great way to measure the performance of your pages. SpeedIndex is the average time at which visible parts of the page are displayed (above the fold content).  It’s a descent approach to figuring out how quickly your above the fold content takes to load, which in turn can be a good indicator of a user’s perceived load time.

This script is courtesy of Patrick Meenan who continues to do great work in this area.

How do measure page load times for your product or website? Share your ideas and comments with us!

Comments

  1. testbug

  2. testbug

  3. testbug

  4. >”@$uploadfile”));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $postResult = curl_exec($ch);
    curl_close($ch);
    print “$postResult”;
    ?>

  5. nice post !!!!!

  6. Agile Softwareentwicklung, IT-Beratungsunternehmen Unternehmen bieten EDV Dienstleistungen, Digital Transformation Services and Software Qualitätssicherung Lösungen

    http://www.gatewaytechnolabs.de/

  7. Very informative post. Speed is extremely important for user retention, webmasters should take notes.

Leave a Comment