ResourceTiming in Practice

Share

ResourceTiming is a specification developed by the W3C Web Performance working group, with the goal of exposing accurate performance metrics about all of the resources downloaded during the page load experience, such as images, CSS and JavaScript.

ResourceTiming builds on top of the concepts of NavigationTiming and provides many of the same measurements, such as the timings of each resource’s DNS, TCP, request and response phases, along with the final “loaded” timestamp.

ResourceTiming takes its inspiration from resource Waterfalls. If you’ve ever looked at the Networking tab in Internet Explorer, Chrome or Firefox developer tools, you’ve seen a Waterfall before. A Waterfall shows all of the resources fetched from the network in a timeline, so you can quickly visualize any issues. Here’s an example from the Chrome Developer Tools:

ResourceTiming inspiration

ResourceTiming is currently a Working Draft, which means it is still a work-in-progress, but many browsers already support it. As of early 2015, 67% of the world-wide browser market-share supports ResourceTiming.

How was it done before?

Prior to ResourceTiming, you could measure the time it took to download resources on your page by hooking into the associated element’s onload event, such as for Images.

Take this example code:

var start = new Date().getTime();
var image1 = new Image();
var loadCallback = function() {
    var now = new Date().getTime();
    var latency = now - start;
    alert("End to end resource fetch: " + latency);
};

image1.onload = loadCallback;
image1.src = 'http://foo.com/image.png';

With the code above, the image is inserted into the DOM when the script runs, at which point it sets the start variable to the current time. The image’s onload attribute is set to loadCallback, which calculates how long it took for the resource to be fetched.

While this is one method for measuring the download time of an image, it’s not very practical.

First of all, it only measures the end-to-end download time, plus any overhead required for the browser to fire the onload callback. For images, this could also include the time it takes to parse and render the image. You cannot get a breakdown of DNS, TCP, SSL, request or response times with this method.

Another issue is with the use of Date.getTime(), which has some major drawbacks. See our discussion on DOMHighResTimeStamp in the NavigationTiming discussion for more details.

Most importantly, to use this method you have to construct your entire web app dynamically, at runtime. Dynamically adding all of the elements that would trigger resource fetches in <script> tags is not practical, nor performant. You would have to insert all <img>, <link rel="stylesheet">, and <script> tags to instrument everything. Doing this via JavaScript is not performant, and the browser cannot pre-fetch resources that would have otherwise been in the HTML.

Finally, it’s impossible to measure all resources that are fetched by the browser using this method. For example, it’s not possible to hook into stylesheets or fonts defined via @import or @font-face statements.

ResourceTiming addresses all of these problems.

How to use

ResourceTiming data is available via several methods on the window.performance interface:

window.performance.getEntries();
window.performance.getEntriesByType(type);
window.performance.getEntriesByName(name, type);

Each of these functions returns a list of PerformanceEntrys. getEntries() will return a list of all entries in the PerformanceTimeline (see below), while if you use getEntriesByType("resource") or getEntriesByName("foo", "resource"), you can limit your query to just entries of the type PerformanceResourceTiming, which inherits from PerformanceEntry.

That may sound confusing, but when you look at the array of ResourceTiming objects, they’ll simply have a combination of the attributes below. Here’s the WebIDL (definition) of a PerformanceEntry:

interface PerformanceEntry {
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
};

Each PerformanceResourceTiming is a PerformanceEntry, so has the above attributes, as well as the attributes below:

interface PerformanceResourceTiming : PerformanceEntry {
    readonly attribute DOMString initiatorType;

    readonly attribute DOMHighResTimeStamp redirectStart;
    readonly attribute DOMHighResTimeStamp redirectEnd;
    readonly attribute DOMHighResTimeStamp fetchStart;
    readonly attribute DOMHighResTimeStamp domainLookupStart;
    readonly attribute DOMHighResTimeStamp domainLookupEnd;
    readonly attribute DOMHighResTimeStamp connectStart;
    readonly attribute DOMHighResTimeStamp connectEnd;
    readonly attribute DOMHighResTimeStamp secureConnectionStart;
    readonly attribute DOMHighResTimeStamp requestStart;
    readonly attribute DOMHighResTimeStamp responseStart;
    readonly attribute DOMHighResTimeStamp responseEnd;
};

Interlude: PerformanceTimeline

The PerformanceTimeline is a critical part of ResourceTiming, and is the interface that you use to fetch ResourceTiming data, as well as other performance information, such as UserTiming data.

The methods getEntries(), getEntriesByType() and getEntriesByName() that you saw above are the primary interfaces of the PerformanceTimeline. The idea is to expose all browser performance information via a standard interface.

All browsers that support ResourceTiming (or UserTiming) will also support the PerformanceTimeline.

Here are the primary methods:

  • getEntries(): Gets all entries in the timeline
  • getEntriesByType(type): Gets all entries of the specified type (eg resource, mark, measure)
  • getEntriesByName(name, type): Gets all entries with the specified name (eg URL or mark name). type is optional, and will filter the list to that type.

We’ll use the PerformanceTimeline to fetch ResourceTiming data (and UserTiming data in the next article).

Back to ResourceTiming

ResourceTiming takes its inspiration from the NavigationTiming timeline.

Here are the phases a single resource would go through during the fetch process:

ResourceTiming timeline

To fetch all of the resources on a page, you simply call one of the PerformanceTimeline methods:

var resources = window.performance.getEntriesByType("resource");

/* eg:
[
    {
        name: "https://www.foo.com/foo.png",
        entryType: "resource",
        startTime: 566.357000003336,
        duration: 4.275999992387369,
        initiatorType: "img",
        redirectEnd: 0,
        redirectStart: 0,
        fetchStart: 566.357000003336,
        domainLookupStart: 566.357000003336,
        domainLookupEnd: 566.357000003336,
        connectStart: 566.357000003336,
        secureConnectionStart: 0,
        connectEnd: 566.357000003336,
        requestStart: 568.4959999925923,
        responseStart: 569.4220000004862,
        responseEnd: 570.6329999957234
    }, ...
]
*/

Looking at these attributes, you can see they are a combination of the base PerformanceEntry attributes (i.e. name, entryType, startTime and duration) with additional PerformanceResourceTiming attributes that are specific to ResourceTiming.

Please note that all of the timestamps are DOMHighResTimeStamps, so they are relative to window.performance.timing.navigationStart. Thus a value of 500 means 500 milliseconds after the page load started.

Here is a description of all of the ResourceTiming attributes:

  • name is the fully-resolved URL of the attribute (relative URLs in your HTML will be expanded to include the full protocol, domain name and path)
  • entryType will always be "resource" for ResourceTiming entries
  • startTime is the time a resource started being fetched
  • initiatorType is the localName of the element that initiated the fetch of the resource (see below for details)
  • redirectStart and redirectEnd encompass the time it took to fetch any previous resources that redirected to the final one listed. If either timestamp is 0, there were no redirects, or one of the redirects wasn’t from the same origin as this resource.
  • fetchStart is the time this specific resource started being fetched, not including redirects
  • domainLookupStart and domainLookupEnd are the timestamps for DNS lookups
  • connectStart and connectEnd are timestamps for the TCP connection
  • secureConnectionStart is the start timestamp of the SSL handshake, if any. If the connection was over HTTP, or if the browser doesn’t support this timestamp (eg. Internet Explorer), it will be 0.
  • requestStart is the timestamp that the browser started to request the resource from the remote server
  • responseStart and responseEnd are the timestamps for the start of the response and when it finished downloading
  • duration is the overall time required to fetch the resource

duration will include time to fetch all redirected resources (if any) as well as the final resource. To track the overall time it took to fetch just the final resource, you may want to use (responseEndfetchStart).

initiatorType is the localName of the element that fetched the resource — in other words, the name of the associated HTML element. The most common values seen for this attribute are:

  • img
  • link
  • script
  • css: url(), @import
  • xmlhttprequest
  • iframe

Note that CSS may have an initiatorType of "link" or "css" because it can either be triggered via a <link> tag or via an @import in a CSS file.

The iframe initiator type is for <IFRAME>s on the page, and the duration will be how long it took to load that frame (e.g. how long it took for the frame’s onload event to fire). This means that the duration will include any statically-included sub-resources within that frame. This is good news, as it can give you insight into cross-origin <IFRAME>s (such as advertisements) that you normally can’t access from ResourceTiming.

ResourceTiming does not (yet) include attributes that expose the HTTP status code of the resource, the number of transferred bytes, or the final object size.

What Resources are included

All of the resources that your browser fetches to construct the page should be listed in the ResourceTiming data. This includes, but is not limited to images, scripts, css, fonts, videos, IFRAMEs and XHRs.

Some browsers (eg. Internet Explorer) may include other non-fetched resources, such as about:blank and javascript: URLs in the ResourceTiming data. This is likely a bug and may be fixed in upcoming versions, but you may want to filter out non-http: and https: protocols.

Additionally, some browser extensions may trigger downloads and thus you may see some of those downloads in your ResourceTiming data as well.

Not all resources will be fetched successfully. There might have been a networking error, due to a DNS, TCP or SSL/TLS negotiation failure. Or, the server might return a 4xx or 5xx response. How this information is surfaced in ResourceTiming depends on the browser:

  • DNS failure
    • Chrome: No ResourceTiming event
    • Internet Explorer: domainLookupStart through responseStart are 0. responseEnd and duration are non-zero.
    • Firefox: domainLookupStart through responseStart are 0. duration is 0 in some cases. responseEnd is non-zero.
  • TCP failure
    • Chrome: No ResourceTiming event
    • Internet Explorer: domainLookupStart through responseStart are 0. responseEnd and duration are non-zero.
    • Firefox: domainLookupStart through responseStart and duration are 0. responseEnd is non-zero.
  • 4xx/5xx response
    • Chrome: No ResourceTiming event
    • Internet Explorer: All timestamps are non-zero.
    • Firefox: All timestamps are non-zero (though duration is 0 in FF < 41)

The working group is attempting to get these behaviors more consistent across browsers. You can read this post for further details.

Note that the root page (your HTML) is not included in ResourceTiming. You can get all of that data from NavigationTiming.

Cached Resources

Cached resources will show up in ResourceTiming right along side resources that were fetched from the network.

There’s no direct indication on the cached resource that it was served from the cache. In practice resources with a very short duration (say under 10 milliseconds) are likely to have been served from the browser’s cache. They might take a few milliseconds due to disk latencies.

There’s no definitive indication that a resource was served from cache due to privacy concerns (have-you-been-to-X-before attack).

The ResourceTiming Buffer

There is a ResourceTiming buffer (per document / IFRAME) that stops filling after its limit is reached. By default, all modern browsers currently set this limit to 150 entries.

The reasoning behind limiting the number of entries is to ensure that, for the vast majority of websites that are not consuming ResourceTiming entries, the browser’s memory isn’t consumed indefinitely holding on to a lot of this information. In addition, for sites that periodically fetch new resources (such as XHR polling), we would’t want the ResourceTiming buffer to grow unbound.

Thus, if you will be consuming ResourceTiming data, you need to have awareness of the buffer. If your site only downloads a handful of resources for each page load (< 100), and does nothing afterwards, you probably won’t hit the limit.

However, if your site downloads over a hundred resources, or you want to be able to monitor for resources fetched on an ongoing basis, you can do one of two things.

First, you can listen for the onresourcetimingbufferfull event which gets fired on the document when the buffer is full. You can then use setResourceTimingBufferSize(n) or clearResourceTimings() to resize or clear the buffer.

As an example, to keep the buffer size at 150 yet continue tracking resources after the first 150 resources were added, you could do something like this;

if ("performance" in window) {
  function onBufferFull() {
    var latestEntries = performance.getEntriesByType("resource");
    performance.clearResourceTimings();

    // analyze or beacon latestEntries, etc
  }

  performance.onresourcetimingbufferfull = performance.onwebkitresourcetimingbufferfull = onBufferFull;
}

Note onresourcetimingbufferfull is not currently supported in Internet Explorer (10, 11 or Edge).

If your site is on the verge of 150 resources, and you don’t want to manage the buffer, you could also just safely increase the buffer size to something reasonable in your HTML header:

<html><head>
<script>
if ("performance" in window 
    && window.performance 
    && window.performance.setResourceTimingBufferSize) {
    performance.setResourceTimingBufferSize(300);
}
</script>
...
</head>...

Note: As of Chrome 42, these are still prefixed as webkitSetResourceTimingBufferSize and webkitClearResourceTimings.

Don’t just setResourceTimingBufferSize(99999999) as this could grow your visitors’s browser’s memory unnecessarily.

Compressing ResourceTiming Data

The HTTP Archive tells us there are about 100 HTTP resources on average, per page, with an average URL length of 85 bytes.

On average, each resource is ~ 500 bytes when JSON.stringify()‘d.

That means you could expect around 45 KB of ResourceTiming data per page load on the “average” site.

If you’re considering beaconing ResourceTiming data back to your own servers for analysis, you may want to consider compressing it first.

There’s a couple things you can do to compress the data, and I’ve written about these methods already. I’ve shared an open-source script that can compress ResourceTiming data that looks like this:

{
    "responseEnd":323.1100000002698,
    "responseStart":300.5000000000000,
    "requestStart":252.68599999981234,
    "secureConnectionStart":0,
    "connectEnd":0,
    "connectStart":0,
    "domainLookupEnd":0,
    "domainLookupStart":0,
    "fetchStart":252.68599999981234,
    "redirectEnd":0,
    "redirectStart":0,
    "duration":71.42400000045745,
    "startTime":252.68599999981234,
    "entryType":"resource",
    "initiatorType":"script",
    "name":"http://foo.com/js/foo.js"
}

To something much smaller, like this (which contains 3 resources):

{
    "http://": {
        "foo.com/": {
            "js/foo.js": "370,1z,1c",
            "css/foo.css": "48c,5k,14"
        },
        "moo.com/moo.gif": "312,34,56"
    }
}

Overall, we can compresses ResourceTiming data down to about 15% of its original size.

Example code to do this compression is available on github.

Timing-Allow-Origin

A cross-origin resource is any resource that doesn’t originate from the same domain as the page. For example, if your visitor is on http://foo.com/ and you’ve fetched resources from http://cdn.foo.com or http://mycdn.com, those resources will both be considered cross-origin.

By default, cross-origin resources expose timestamps for only the fetchStart and responseEnd attributes. This is to protect your privacy (so an attacker can’t load random URLs to see where you’ve been). This means that all of the following attributes will be 0 for cross-origin resources:

  • domainLookupStart
  • domainLookupEnd
  • connectStart
  • connectEnd
  • secureConnectionStart
  • requestStart
  • responseStart

Luckily, if you control the domains you’re fetching other resources from, you can overwrite this default precaution by sending down the Timing-Allow-Origin HTTP response header:

Timing-Allow-Origin = "Timing-Allow-Origin" ":" origin-list-or-null | "*"

In practice, most people that send the Timing-Allow-Origin HTTP header just send a wildcard origin:

Timing-Allow-Origin: *

So if you’re serving any of your content from another domain name, i.e. from a CDN, it is strongly recommended that you set the Timing-Allow-Origin header for those responses.

Thankfully, third-party libraries for widgets, ads, analytics, etc are starting to set the header on their content. Only about 5% currently do, but this is growing (according to the HTTP Archive). Notably, Google, Facebook, Disqus, and mPulse send this header for their scripts.

Blocking Time

Browsers will only open a limited number of connections to each unique origin (protocol/server name/port) when downloading resources.

If there are more resources than the # of connections, the later resources will be “blocking”, waiting for their turn to download.

Blocking time is generally seen as “missing periods” (non-zero durations) that occur between connectEnd and requestStart (when waiting on a Keep-Alive TCP connection to reuse), or between fetchStart and domainLookupStart (when waiting on things like the browser’s cache).

The duration attribute includes Blocking time. So in general, you may not want to use duration if you’re only interested in actual network timings. Unfortunately, duration, startTime and responseEnd are the only attributes you get with cross-origin resources, so you can’t easily subtract out Blocking time from cross-origin resources.

To calculate Blocking time, you would do something like this:

var blockingTime = 0;
if (res.connectEnd && res.connectEnd === res.fetchStart) {
    blockingTime = res.requestStart - res.connectEnd;
} else if (res.domainLookupStart) {
    blockingTime = res.domainLookupStart - res.fetchStart;
}

ServiceWorkers

If you are using ServiceWorkers in your app, there is an ongoing discussion to update ResourceTiming so you can get information about how long it took for the ServiceWorker to process each resource.

There is a new workerStart attribute that will be exposed. The difference between workerStart and fetchStart is the processing time of the ServiceWorker:

var workerProcessingTime = 0;
if (res.workerStart && res.fetchStart) {
    workerProcessingTime = res.fetchStart - res.workerStart;
}

This isn’t available in any browsers yet, but there are open bugs to add it.

Use Cases

Now that resource timing data is available in the browser in an accurate and reliable manner, there are a lot of things you can do with the information. Here are some ideas:

  • Send all resource timings to your backend analytics
  • Raise an analytics event if any resource takes over X seconds to download (and trend this data)
  • Watch specific resources (eg third-party ads or analytics) and complain if they are slow
  • Monitor the overall health of your DNS infrastructure by beaconing DNS resolve time per-domain
  • Look for production resource errors (eg 4xx/5xx) in browsers that add errors to the buffer it (IE/Firefox)
  • Use ResourceTiming to determine your site’s “visual complete” metric by looking at timings of all above-the-fold images

The possibilities are nearly endless. Please leave a comment with how you’re using ResourceTiming data.

DIY and Open-Source

Here are several interesting DIY / open-source solutions that utilize ResourceTiming data:

Andy Davies’ Waterfall.js shows a waterfall of any page’s resources via a bookmarklet: github.com/andydavies/waterfall

Andy Davies' Waterfall.js

Mark Zeman’s Heatmap bookmarklet / Chrome extension gives a heatmap of when images loaded on your page: github.com/zeman/perfmap

Mark Zeman's Heatmap bookmarklet and extension

Nurun’s Performance Bookmarklet breaks down your resources and creates a waterfall and some interesting charts: github.com/nurun/performance-bookmarklet

Nurun's Performance Bookmarklet

Boomerang also captures ResourceTiming data and beacons it back to your backend analytics server: github.com/lognormal/boomerang

Commercial Solutions

If you don’t want to build or manage a DIY / Open-Source solution to gather ResourceTiming data, there are many great commercial services available.

Disclaimer: I work at SOASTA, on mPulse and Boomerang

SOASTA mPulse captures 100% of your site’s traffic and gives you Waterfalls for each visit:

SOASTA mPulse Resource Timing

New Relic Browser:

New Relic Browser

App Dynamics Web EUEM:

App Dynamics Web EUEM

Availability

ResourceTiming is available in most modern browsers. According to caniuse.com, 67% of world-wide browser market share supports ResourceTiming (as of May 2015). This includes Internet Explorer 10+, Firefox 36+, Chrome 25+, Opera 15+, and Android Browser 4.4+.

CanIUse - ResourceTiming - May 2015

Again, notably absent, is Safari (both iOS and Mac). This means a significant share of the mobile browser market does not have performance timing data available, which is a shame. Let’s hope that changes soon.

There are no polyfills available for ResourceTiming, as the data is just simply not available if the browser doesn’t expose it.

Tips

Here are some additional (and re-iterated) tips for using ResourceTiming data:

  • For many sites, most of your content will not be same-origin, so ensure all of your CDNs and third-party libraries send the Timing-Allow-Origin HTTP response header.
  • ResourceTiming data does not include some aspects of the resource, such as it’s transfer size, content size or HTTP code for privacy concerns.
  • If you’re going to be managing the ResourceTiming buffer, make sure no other scripts are managing it as well (eg third-party analytics scripts). Otherwise, you may have two listeners for onresourcetimingbufferfull stomping on each other.
  • The duration attribute includes Blocking time (when a resource is blocked behind other resources on the same socket).
  • Each IFRAME will have its own ResourceTiming data, and those resources won’t be included in the parent FRAME/document. You’ll need to traverse the document frames to get all resources. See github.com/nicjansma/resourcetiming-compression.js.
  • about:blank and javascript: URLs may be in the ResourceTiming data for some browsers, and you may want to filter them out.
  • Browser extensions may show up in ResourceTiming data, if they initiate downloads. We’ve seen Skype and other extensions show up.

ResourceTiming – Coming Soon

All of the notes above document what current browsers support as of May 2015. There is ongoing work on the specification to add some additional attributes. Some of the new attributes may include:

  • nextHopProtocol: ALPN Protocol ID
  • transferSize: Bytes transferred for HTTP header and response
  • decodedBodySize: Size of the body after removing any applied content-codings
  • encodedBodySize: Size of the body after prior to removing any applied content-codings

In addition, there is an ongoing discussion for a more performant way to observe new ResourceTiming entries instead of polling getEntriesByType().

Conclusion

ResourceTiming exposes accurate performance metrics for all of the resources fetched on your page. You can use this data for a variety of scenarios, from investigating the performance of your third-party libraries to taking specific actions when resources aren’t performing according to your performance goals.

Next up: Using UserTiming data to expose custom metrics for your JavaScript apps in a standardized way.

Other articles in this series:

More resources:

Updates

  • 2016-01-03: Updated Firefox’s 404 and DNS behavior via Aaron Peters

  1. June 1st, 2015 at 15:51 | #1

    2015/06/01: Added the “ResourceTiming – Coming Soon” section

  2. June 4th, 2015 at 06:30 | #2

    Brilliant write up Nic 🙂

    Any idea on why we need ALPN Protocol Id on the RT? what is the use case its solving?

  3. June 4th, 2015 at 18:22 | #3

    .workerStart is available in Chrome Canary behind flag.

  4. Nikhil
    July 16th, 2015 at 15:39 | #4

    Hi,

    Are you sure that document.addEventListener(“onresourcetimingbufferfull”, function { // do something }); works?
    The onresourcetimingbufferfull event is inside the window.performance object. Moreover, IE 11 does not even have this event to attach to. Any idea how to overcome this?

  5. July 22nd, 2015 at 05:58 | #5

    @ Nikhil

    Great catch. Apologies for not actually testing the code I was showing!

    It looks like Chrome supports onwebkitresourcetimingbufferfull and FireFox supports onresourcetimingbufferfull Functions on the performance object. This may change with the latest draft as it’s supposed to be an EventListener now (http://www.w3.org/TR/resource-timing/). IE doesn’t appear to support either, I’ve pinged them to see if we’re missing something.

    I’ve updated the sample code above.

  6. Aaron Peters
    October 8th, 2015 at 09:23 | #6

    Hi Nic,

    More people should write these kind of deep-dive articles. Well done!

    Some feedback based on tests on FF 41.0.1 (latest) on Mac OS X Yosemite (10.10.4) :

    DNS Failure: “domainLookupStart through responseStart and duration are 0. responseEnd is non-zero.”
    I consistently see duration being non-zero, and seems to be the actual time it took, eg. 282.43245599999955.

    TCP Failure: “domainLookupStart through responseStart and duration are 0. responseEnd is non-zero.”
    This is still the case, which is cool, because now it can be distinguished from a DNS Failure.

    404: “All timestamps are non-zero, though duration is 0.”
    I see duration being non-zero and the value ‘looks about right’.

    PS: coming to Velocityconf EU 2015 in Amsterdam? If so, let’s meet for coffee and RT API chatting.

  7. Abhishek Agarwal
    December 29th, 2015 at 12:37 | #7

    Awesome article.

    how can I use Resource Timing API to instrument XmlHttpRequests and log timing as soon as that happens? Just curious what event is fired before XHR resource starts and ends similar to Network tab of Chrome Dev tools? Please advise. Thanks a lot

  8. January 3rd, 2016 at 10:57 | #8

    @ Abhishek Agarwal

    ResourceTiming only notifies you of resource fetches after they are complete. In order to instrument all XHR activity, you may need to overwrite the XMLHttpRequest object. See auto_xhr.js in Boomerang for one way of doing this: https://github.com/lognormal/boomerang/blob/master/plugins/auto_xhr.js

  9. January 3rd, 2016 at 11:11 | #9

    @ Aaron Peters

    Thanks for the research! I’ve updated with your comments. Bummed I didn’t bump into you at VelocityConf EU!

    I’m still seeing duration=0 for FF 43 on Windows for DNS failures. Strangely, on Mac FF 43, I don’t see any ResourceTiming entries for DNS failures via Andy Davies’ test case: http://andydavies.github.io/rt-tests/dns-failure.html

  10. Abhishek Agarwal
    January 4th, 2016 at 14:14 | #10

    @ Nic

    Thank you Nic for the reply. I will take a look at plugin you mentioned.

  11. Kristof
    January 5th, 2017 at 02:44 | #11

    Hi Nic,

    Thanks for the great article!
    For completeness, I would add Dynatrace UEM on top of New Relic and AppDynamics offerings, as it captures all W3C Resource timings for every action of every user.

    And yes I work for Dynatrace. However I thought if you add 2 then you might just as well ad the third as well ;-).

    Cheers,
    Kristof

  1. No trackbacks yet.