Time to Interactive with RUM
You got me, I do love tiki bars! 🍹 Especially the one near my place in Hayes Valley, San Francisco.
Update Nov 5th 2018: measure TTI with lighthouse and measure FID in the wild with Perfume.js (http://perfumejs.com/#/first-input-delay).
Kidding aside, RUM on the web refers to Real User Measurement. It records all the metrics from users loading and interacting with your web application, and exposes them as Field data.
Although automated tools like Lighthouse have popularized Synthetic measurements, they can be run against any web page from your laptop and have all the Lab Data, which gives the ability to perform exhaustive debugging in different scenarios. In Chrome, combining DevTools and Lighthouse, it’s easy to get a sense of how the latest feature will likely behave in the real world and even catch potential issues before they make it through production.
The main difference between the two metrics is twofold:
- Lab Data is easy to measure and import in our development cycle, but misses the full picture of real users with different cache conditions, networks, devices, etc.
- Field Data is less documented and needs some minor coding, but completes our development process with real-world user experience and enables correlation to business key performance indicators.
Web performance APIs are the tools used to collect field data, and they are available in multiple browsers. In my previous article, First (Contentful) Paint, we introduced two Real User Measurements, and we focused on the initial loading to answer the question, “Is it happening?”. Now, we’ll move on to “Is it usable?” in the next key moment of the navigation and web page usage experience.
Is it Usable?
The third key question defines the moment when the user can interact with the page. This opens the conversation to the final and most important metric for a page load, Time to Interactive.
Time to Interactive is defined as the point at which the layout has stabilized, key webfonts are visible, and the main thread is available enough to handle user input.
One of the main parameters to define the metric is the Long task threshold. It refers to an event loop task that exceeds 50ms which comes from the RAIL idle guideline. While the page is loading, long tasks often tie up the main thread and prevent the user from interacting with the page even though the page is already visually rendered. The RAIL performance model suggests that applications should respond to user input in under 100ms.
This number comes from a research conducted by Robert Miller in 1968 (Response time in man-computer conversational transactions), and the number was confirmed in 1991 at first by research at Xerox-PARC and later in 1994 by Jakob Nielsen in his book Usability Engineering.
In practice, the TTI algorithm is a combination of observing the main thread and network activity. The three steps to define the metric are:
- In the main thread timeline we define a lower bound for the TTI (lower limit the metric can’t be before), like FirstContentfulPaint or DomContentLoadedEnd;
- From the lower limit we search forward for a quiet network window of 5 seconds (not containing long tasks);
- Once we found it, we look backward to the end of the last long task and report the time as Time to Interactive.
Notes to keep in mind when we measure Time to Interactive:
- If DOMContentLoadedEnd occurs after the start of the window, it reports that as TTI;
- We cannot use First Meaningful Paint as the lower bound since that is not yet standardized;
- We cannot detect TTI until the network is quiet, but the actual TTI value is always at the end of a long task;
- We have to wait at least 5 seconds after the page actually becomes interactive before we can report it;
- We wouldn’t measure TTI on a simple static web app, we would probably measure overall response time.
Now let’s see how the TTI polyfill will help us measure the metric with field data and have some RUM into practice. Cheers again! 🍹
Right now you can measure the Time To Interactive by using a polyfill available on npm that works in any browser that supports the Long Tasks API (at the moment only on Chrome 58+). To use the tti-polyfill is a two-step process:
- First, you need to add a snippet of code to the head of your document which will create a PerformanceObserver instance and start observing long tasks in your main thread.
- In your application, invoke the method getFirstConsistentlyInteractive() from the ttiPolyfill module which accepts an optional lower bound value to start forward-searching for the network quiet window, the default value is DOMContentLoaded.
Let’s play with a live demo. We have a simple demo page with a title showing the value of TTI as soon as it’s ready from the polyfill.
To emulate an average interactive delay on a Single Page App there is a DOM manipulation loop with over 40k iterations after 500ms from the First Paint. The long tasks generated from the loop are perfect as a showcase because upon initial loading, if we try to click on the “+1” button nothing will change in the Document until the main thread is free from long tasks and the browser is free to paint the latest changes.
1. Cross-browser support will use the modern native APIs when available and avoid observing long tasks in the opposite case;
2. Reporting the results to Google Analytics as User timing will support the custom user timing variable timingVar:”name”.
The best part of RUM is the possibility to report the results to Google Analytics where the field data provided is the real metric on your SPA performance with a complete understanding of when the page is ready, and usably-free from long tasks. Start optimizing with a focus not only on content visibility, but also an extra care for user interaction, reducing Long Tasks and measuring both Synthetic and RUM.
Now you’re free to take a few spare minutes to start measuring the Time to Interactive for your app!
Have fun 🔥 🚀 🌕
For extra questions, send me a tweet or a private message at Twitter @zizzamia.