Alois Reitbauer (@aloisreitbauer) is a product guy, AppOps evangelist, performance addict and a sushi lover.
The W3C Web Performance Working Group has been working on standards to measure performance within the users browser. A number of standards have originated from this work. Navigation Timing with the ability to measure the load time of a page is the most prominent one. Resource timing also now already found its way into modern browser. User Timing enables custom measurements in the browser.
While these specifications were designed to measure the load time of pages and page components they can be used for much more. A little JavaScript magic can take us far. In this post, The following examples show how common web performance use cases can be answered by creatively using these standards.
Determine whether a resource was cached
While resource timing allows us to measure the load time of a resource, it provides no obvious hints about whether a resource was loaded from origin or retrieved from cache. As the impact of caching on end user performance this is vital information to tune and speed up web applications.
Although this information is not available out-of-the-box it can easily be derived from data that is available. The simplest while not most sophisticated way is to set a threshold on the duration a resource takes to load. The code snippet uses the duration measure and assumes that all resources that have a non zero duration actually came from cache.
var resourceList = window.performance.getEntriesByType(resource); for (i= 0; i < resourceList.length; i++){ if (resourceList[i].duration <= 0.0){ console.log('Resource ' + resourceList[i].name + ' was cached.'); } else{ console.log('Resource ' + resourceList[i].name + ' was NOT cached.'); } }
Find resources that failed to load
Even more important than whether a resource was cached is not, is to figure out whether it was loaded at all. Unfortunately the resource timing API does not track resources that failed to load. So, how can we find them?
Again some JavaScript magic will help a lot. The idea is pretty straightforward and exploits the fact that Resource Timing does not track resources that failed to load. All we have to do is compare the resources referenced in the DOM with the resources retrieved via resource timing. The difference then are the resources that failed to load. The code below does exactly this for images.
// find all images in the DOM var elems= document.getElementsByTagName('img'); var definedImages = new Array(); for (i = 0; i < elems.length; i++) { definedImages[definedImages.length] = "http://localhost:3000/" + elems[0].getAttribute('src'); } // find all images that were loaded var loadedImages = new Array (); var resourceList = window.performance.getEntriesByType("resource"); for (i = 0; i < resourceList.length; i++){ if (resourceList[i].initiatorType == "img") { loadedImages[loadedImages.length]= resourceList[i].name; } } // check the difference for (i = 0; i < definedImages.length; i++){ if(loadedImages.indexOf(definedImages[i]) < 0){ console.log('Image ' + definedImages[i] + ' failed to load'); } else { console.log('Image ' + definedImages[i] + ' loaded successfully'); } }
Measure JavaScript execution
A critique that I often hear when talking about W3C timing is that they are very network focussed and provide only little support for what is going in the browser; specifically when it comes to JavaScript execution. It is easy to find out how long it took to load a JavaScript file. However, there is no straightforward way to see what the overall impact on the page was including JavaScript execution.
Fortunately there is a workaround for this problem as well; at least for resources that are under your own control. The following code exploits the fact that it is very easy to determine the source of a script from within a script block and uses User Timing to store the information.
// top of JavaScript file // the currently execution script file is always the last added unless it is async/deferred var scripts = document.getElementsByTagName( 'script' ); var scriptSource = scripts[ scripts.length - 1 ].src; performance.mark(scriptSource + 'Start'); // end of JavaScript file performance.measure(scriptSource, scriptSource + 'Start'); // retrieve execution Time; var perfEntries = performance.getEntriesByType("measure"); for(i = 0; i < perfEntries.length; i++){ console.log(perfEntries[i].name + ' took ' + perfEntries[i].duration.toFixed(2) + ' ms'); }
Measuring the load time of the important part of a page
One of the most discussed topics in the WPO community is to define when a page is ready for the user. This is not the easiest question after all and it is influenced by quite a number of factors.
A straightforward approach is to mark all content on a page that is important with a specific attribute. In this example the prio attribute is used. Then we define a point in the markup that has to be readed to show all important content. The only thing that is then left is to iterate through the all the timing and find the latest end time.
<!-- annotate your resources --> <img src="..." prio="high" > <!-- set a mark after important text --> <script>performance.mark('mark_above_the_fold');</script>
//find all important pieces and get the timings // first get above the fold time from markup var doneLoading = performance.getEntriesByName('mark_above_the_fold')[0].startTime; console.log ('Reaching content done at ' + doneLoading.toFixed(2) + ' ms.'); // iterate through DOM and get item timings var elements = document.getElementsByTagName("img"); for (var i=0; i < elements.length; i++) { if (elements[i].getAttribute('prio') != undefined){ var source = elements[i].getAttribute("src"); var doneTime = performance.getEntriesByName(source)[0].responseEnd; console.log(source + ' took ' + doneTime.toFixed(2)); if (doneLoading < doneTime){ doneLoading = doneTime; } } } console.log('Page was done loading ' + doneLoading.toFixed(2) + ' ms.');
Wrap up
All the code including examples is available on GitHub. As a final disclaimer I have to note that this code is not designed for production use. It will require some performance tweaks before being rolled out with your applications.
If you have your own hacks based on W3C APIs or have a better solution to similar problems feel free to contribute. The goal of this repository is to make it easy for people to learn how to use these standards to get better insights into their applications.