SERIOUS CONFUSION with Resource Timing

November 25, 2014 1:20 pm | 13 Comments

or “Duration includes Blocking”

Resource Timing is a great way to measure how quickly resources download. Unfortunately, almost everyone I’ve spoken with does this using the “duration” attribute and are not aware that “duration” includes blocking time. As a result, “duration” time values are (much) greater than the actual download time, giving developers unexpected results. This issue is especially bad for cross-origin resources where “duration” is the only metric available. In this post I describe the problem and a proposed solution.

Resource Timing review

The Resource Timing specification defines APIs for gathering timing metrics for each resource in a web page. It’s currently available in Chrome, Chrome for Android, IE 10-11, and Opera. You can gather a list of PerformanceEntry objects using getEntries(), getEntriesByType(), and getEntriesByName(). A PerformanceEntry has these properties:

  • name – the URL
  • entryType – typically “resource”
  • startTime – time that the resource started getting processed (in milliseconds relative to page navigation)
  • duration – total time to process the resource (in milliseconds)

The properties above are available for all resources – both same-origin and cross-origin. However, same-origin resources have additional properties available as defined by the PerformanceResourceTiming interface. They’re self-explanatory and occur pretty much in this chronological order:

  • redirectStart
  • redirectEnd
  • fetchStart
  • domainLookupStart
  • domainLookupEnd
  • connectStart
  • connectEnd
  • secureConnectionStart
  • requestStart
  • responseStart
  • responseEnd

Here’s the canonical processing model graphic that shows the different phases. Note that “duration” is equal to (responseEnd – startTime).

Take a look at my post Resource Timing Practical Tips for more information on how to use Resource Timing.

Unexpected blocking bloat in “duration”

The detailed PerformanceResourceTiming properties are restricted to same-origin resources for privacy reasons. (Note that any resource can be made “same-origin” by using the Timing-Allow-Origin response header.) About half of the resources on today’s websites are cross-origin, so “duration” is the only way to measure their load time. And even for same-origin resources, “duration” is the only delta provided, presumably because it’s the most important phase to measure. As a result, all of the Resource Timing implementations I’ve seen use “duration” as the primary performance metric.

Unfortunately, “duration” is more than download time. It also includes “blocking time” – the delay between when the browser realizes it needs to download a resource to the time that it actually starts downloading the resource. Blocking can occur in several situations. The most typical is when there are more resources than TCP connections. Most browsers only open 6 TCP connections per hostname, the exceptions being IE10 (8 connections), and IE11 (12 connections).

This Resource Timing blocking test page has 16 images, so some images incur blocking time no matter which browser is used. Each of the images is programmed on the server to have a 1 second delay. The “startTime” and “duration” are displayed for each of the 16 images. Here are WebPagetest results for this test page being loaded in Chrome, IE10, and IE11. You can look at the screenshots to read the timing results. Note how “startTime” is approximately the same for all images. That’s because this is the time that the browser parsed the IMG tag and realized it needed to download the resource. But the “duration” values increase in steps of ~1 second for the images that occur later in the page. This is because they are blocked from downloading by the earlier images.

In Chrome, for example, the images are downloaded in three sets – because Chrome only downloads 6 resources at a time. The first six images have a “duration” of ~1.3 seconds (the 1 second backend delay plus some time for establishing the TCP connections and downloading the response body). The next six images have a “duration” of ~2.5 seconds. The last four images have a “duration” of ~3.7 seconds. The second set is blocked for ~1 second waiting for the first set to finish. The third set is blocked for ~2 seconds waiting for sets 1 & 2 to finish.

Even though the “duration” values increase from 1 to 2 to 3 seconds, the actual download time for all images is ~1 second as shown by the WebPagetest waterfall chart.

The results are similar for IE10 and IE11. IE10 starts with six parallel TCP connections but then ramps up to eight connections. IE11 also starts with six parallel TCP connections but then ramps up to twelve. Both IE10 and IE11 exhibit the same problem – even though the load time for every image is ~1 second, “duration” shows values ranging from 1-3 seconds.

proposal: “networkDuration”

Clearly, “duration” is not an accurate way to measure resource load times because it may include blocking time. (It also includes redirect time, but that occurs much less frequently.) Unfortunately, “duration” is the only metric available for cross-origin resources. Therefore, I’ve submitted a proposal to the W3C Web Performance mailing list to add “networkDuration” to Resource Timing. This would be available for both same-origin and cross-origin resources. (I’m flexible about the name; other candidates include “networkTime”, “loadTime”, etc.)

The calculation for “networkDuration” is as follows. (Assume “r” is a PerformanceResourceTiming object.)

dns = r.domainLookupEnd - r.domainLookupStart;
tcp = r.connectEnd - r.connectStart; // includes ssl negotiation
waiting = r.responseStart - r.requestStart;
content = r.responseEnd - r.responseStart;
networkDuration = dns + tcp + waiting + content;

Developers working with same-origin resources can do the same calculations as shown above to derive “networkDuration”. However, providing the result as a new attribute simplifies the process. It also avoids possible errors as it’s likely that companies and teams will compare these values, so it’s important to ensure an apples-to-apples comparison. But the primary need for “networkDuration” is for cross-origin resources. Right now, “duration” is the only metric available for cross-origin resources. I’ve found several teams that were tracking “duration” assuming it meant download time. They were surprised when I explained that it also including blocking time, and agreed it was not the metric they wanted; instead they wanted the equivalent of “networkDuration”.

I mentioned previously that the detailed time values (domainLookupStart, connectStart, etc.) are restricted to same-origin resources for privacy reasons. The proposal to add “networkDuration” is likely to raise privacy concerns; specifically that by removing blocking time, “networkDuration” would enable malicious third party JavaScript to determine whether a resource was read from cache. However, it’s possible to remove blocking time today using “duration” by loading a resource when there’s no blocking contention (e.g., after window.onload). Even when blocking time is removed, it’s ambiguous whether a resource was read from cache or loaded over the network. Even a cache read will have non-zero load times.

The problem that “networkDuration” solves is finding the load time for more typical resources that are loaded during page creation and might therefore incur blocking time.

Takeaway

It’s not possible today to use Resource Timing to measure load time for cross-origin resources. Companies that want to measure load time and blocking time can use “duration”, but all the companies I’ve spoken with want to measure the actual load time (without blocking time). To provide better performance metrics, I encourage the addition of “networkDuration” to the Resource Timing specification. If you agree, please voice your support in a reply to my “networkDuration” proposal on the W3C Web Performance mailing list.

 

13 Responses to SERIOUS CONFUSION with Resource Timing

  1. Hm… I think both are valuable. Including the total time (including time blocked) is actually really important because it’s the thing that actually can prevent rendering in the case of CSS/JS. In the example you give it could help you make different decisions about how to concatenate your files given the TCP restrictions.

  2. Eric: I totally agree that both are valuable. I’m definitely not suggesting we remove “duration”; just that we should add “networkDuration”. I’m not so sure I would find a lot of value in “duration” by itself since there’s no way to know how much is blocking and how much is network delays. I am DEFINITELY excited to start gathering blocking time metrics. I’ll only be able to do this for same-origin resources, but that’s okay. I expect that if the median blocking time goes from 300 to 350 ms, there’s probably a big degradation in user experience (and page load and rendering times).

  3. +1

    I said it in May 2013 at WebPerfDays in Amsterdam: duration is useless, exactly because “…since there’s no way to know how much is blocking and how much is network delays”
    Slide 15 in http://www.slideshare.net/turbobytes/state-of-the-resource-timing-api

    Ilya also said something in Barcelona about something you propose coming to Resource Timing, but I think he said this would be for same origin responses and responses with TAO header only

  4. Hi Steve,
    Regarding waiting time – I’d change that a bit and separate between TTFB and the actual request time.
    So instead of waiting, I’d go with two parts:
    1. Requesting = request.end – request.start
    2. Waiting / TTFB = response.start – request.end

    That’s because having this separation can help once you want to isolate performance of the time it took the server to process your request, without the affect of how much time it took your to actually ask for it. It is mostly useful in POST and uploads.

    What are your thoughts of that?

  5. Shmuel: “request.end” doesn’t exist in the spec.

  6. Well, neither the networkDuration.

  7. Nice post.

    What could be a resource with duration 0 and transfer 4?

    I have some data with duration 0 and low values for transfer. (<1RTT)

    I was assuming that could be cached too.

  8. Guilherme: How are you measuring “transfer”? Can you list all the property values?

  9. Steve:
    duration : parseInt(perfEntries[i].duration),

    transfer : parseInt(perfEntries[i].responseEnd -perfEntries[i].responseStart)

    perfEntries = all webfonts resources

  10. Guilherme: According to the spec it’s not possible for (responseEnd – responseStart) to be less than duration. Please file a bug with the browser exhibiting this behavior.

  11. Would it be possible to use the User Timing API to calculate the duration? Put a mark() right before the script and another mark() right after its done? Using the marks you could event check how long the script took to execute. The only issue would be browser support.

  12. Matt: That approach won’t work because downloading resources is independent of script execution. The preloader will start downloading scripts before the SCRIPT tag is actually parsed by the HTML parser.

  13. Good idea use “networkDuration” to the Resource Timing specification.

    Perhaps this question is not on paper. How can I get more than 90 points from the service google to verify the download speed – developers.google.com/speed/pagespeed/insights/.