How to Practically Use Performance API to Measure Performance

840
LogRocket
Record and Replay for Redux apps

This post is by Ananya Neogi

Historically we’ve had limited information regarding performance metrics on the client-side of performance monitoring. We’ve also been met with limitations in API browsers that obstructed us from accurately measuring performance.

Fortunately, this is starting to change thanks to new performance-oriented APIs. Now, the browser’s Performance API provides tools to accurately measure the performance of Web pages.

Before we dig into what these Performance APIs are, let’s look at some compelling reasons for why you should be using them.

Benefits of using Performance API

  • These APIs augment experience when using performance profiling in dev tools
  • Chrome dev tools and other tools like Lighthouse are only helpful during the development phase. But with the Performance APIs, we can get real user measurement (RUM) in production.
  • We can get really precise timestamps, which makes the analysis of these performance metrics very accurate.

Now let’s talk about what these APIs are.

“Performance API is part of the High Resolution Time API, but is enhanced by the Performance Timeline API, the Navigation Timing API, the User Timing API, and the Resource Timing API.” – MDN

You’ll encounter a confusing slew of terms such as High Resolution Time, Performance Timeline API, etc, whenever reading about the Performance API, which makes it hard to understand what exactly it is and how you can make use of it to measure web performance.

Let’s break down these terms to get a better understanding.

High resolution time

A high resolution time is precise up to fractions of a millisecond.

Comparatively, time based on the Date is accurate only up to the millisecond. This precision makes it ideal for yielding accurate measurements of time.

A high resolution time measured by User-Agent (UA) does not change with any changes in system time because it is taken from a global clock created by the UA.

Every measurement measured in the Performance API is a high resolution time. That’s why you’ll always hear that Performance API is a part of the High Resolution Time API.

Performance timeline API

The Performance Timeline API is an extension of the Performance API. The extension provides interfaces to retrieve performance metrics based on specific filter criteria.

Performance Timeline API provides the following three methods, which are included in the performance interface:

  • getEntries()
  • getEntriesByName()
  • getEntriesByType()

Each method returns a list of performance entries gathered from all of the other extensions of the Performance API.

PerformanceObserver is another interface included in the API. It watches for new entries in a given list of performance entries and notifies of the same.

Performance entries

The things we measure with the Performance API are referred to as entries. These are the performance entries that are available to us:

  • mark
  • measure
  • navigation
  • resource
  • paint
  • frame

We’ll make use of these entries with the respective APIs to measure performance.

What can we measure?

Let’s look at some practical measurements we can do with these APIs.

Using navigation timing API and resource timing API

There is a significant overlap between these two APIs, so we’ll discuss them together.

Both are used to measure different resources. We will not go into the details of this overlap, but if you’re curious you can have a look at this processing model which might help you understand this overlap better.

// Get Navigation Timing entries:
const navigationEntries = performance.getEntriesByType("navigation")[0]; // returns an array of a single object by default so we're directly getting that out.

// output:
{
  "name": "https://awebsite.com",
  "entryType": "navigation",
  "startTime": 0,
  "duration": 7816.495000151917,
  "initiatorType": "navigation",
  "nextHopProtocol": "",
  "workerStart": 9.504999965429306,
  "redirectStart": 0,
  "redirectEnd": 0,
  "fetchStart": 39.72000000067055,
  "domainLookupStart": 39.72000000067055,
  "domainLookupEnd": 39.72000000067055,
  "connectStart": 39.72000000067055,
  "connectEnd": 39.72000000067055,
  "secureConnectionStart": 0,
  "requestStart": 39.72000000067055,
  "responseStart": 6608.200000133365,
  "responseEnd": 6640.834999969229,
  "transferSize": 0,
  "encodedBodySize": 0,
  "decodedBodySize": 0,
  "serverTiming": [],
  "unloadEventStart": 0,
  "unloadEventEnd": 0,
  "domInteractive": 6812.060000142083,
  "domContentLoadedEventStart": 6812.115000095218,
  "domContentLoadedEventEnd": 6813.680000137538,
  "domComplete": 7727.995000081137,
  "loadEventStart": 7760.385000146925,
  "loadEventEnd": 7816.495000151917,
  "type": "navigate",
  "redirectCount": 0
}
// Get Resource Timing entries
const resourceListEntries = performance.getEntriesByType("resource");

This will return an array of resource timing objects. A single object will look like this:

{
  "name": "https://awebsite.com/images/image.png",
  "entryType": "resource",
  "startTime": 237.85999999381602,
  "duration": 11.274999938905239,
  "initiatorType": "img",
  "nextHopProtocol": "h2",
  "workerStart": 0,
  "redirectStart": 0,
  "redirectEnd": 0,
  "fetchStart": 237.85999999381602,
  "domainLookupStart": 237.85999999381602,
  "domainLookupEnd": 237.85999999381602,
  "connectStart": 237.85999999381602,
  "connectEnd": 237.85999999381602,
  "secureConnectionStart": 0,
  "requestStart": 243.38999995961785,
  "responseStart": 244.40500000491738,
  "responseEnd": 249.13499993272126,
  "transferSize": 0,
  "encodedBodySize": 29009,
  "decodedBodySize": 29009,
  "serverTiming": []
}

  • Measure DNS time: When a user requests a URL, the Domain Name System (DNS) is queried to translate a domain to an IP address.

Both Navigation and Resource Timing expose two DNS-related metrics:

–domainLookupStart: marks when a DNS lookup starts.

–domainLookupEnd: marks when a DNS lookup ends.

// Measuring DNS lookup time
const dnsTime = navigationEntries.domainLookupEnd - navigationEntries.domainLookupStart;

Gotcha: Both domainLookupStart and domainLookupEnd can be 0 for a resource served by a third party if that host doesn’t set a proper Timing-Allow-Origin response header.

  • Measure request and response timings

Both Navigation and Resource Timing describe requests and responses with these metrics-

  • fetchStart marks when the browser starts to fetch a resource. This doesn’t directly mark when the browser makes a network request for a resource, but rather it marks when it begins checking caches (like HTTP and service worker caches) to see if a network request is even necessary.
  • requestStart is when the browser issues the network request
  • responseStart is when the first byte of the response arrives
  • responseEnd is when the last byte of the response arrives
  • workerStart marks when a request is being fetched from a service worker. This will always be 0 if a service worker isn’t installed for the current page.
// Request + Request Time
const totalTime = navigationEntries.responseEnd - navigationEntries.requestStart;
// Response time with cache seek
const fetchTime = navigationEntries.responseEnd - navigationEntries.fetchStart;

// Response time with Service worker
let workerTime = 0;
if (navigationEntries.workerStart > 0) {
workerTime = navigationEntries.responseEnd - navigationEntries.workerStart;
}

// Time To First Byte
const ttfb = navigationEntries.responseStart - navigationEntries.requestStart;

// Redirect Time
const redirectTime = navigationEntries.redirectEnd - navigationEntries.redirectStart;

  • Measure HTTP header size
const headerSize = navigationEntries.transferSize - navigationEntries.encodedBodySize;

transferSize is the total size of the resource including HTTP headers.

encodedBodySize is the compressed size of the resource excluding HTTP headers.

decodedBodySize is the decompressed size of the resource (again, excluding HTTP headers).

  • Measure load time of resources
resourceListEntries.forEach(resource => {
  if (resource.initiatorType == 'img') {
    console.info(`Time taken to load ${resource.name}: `, resource.responseEnd - resource.startTime);
  }
});

The initiatorType property returns the type of resource that initiated the performance entry. In the above example, we are only concerned with images, but we can also check for script, css, xmlhttprequest, etc.

  • Get metrics for a single resource

We can do this by using getEntriesByName, which gets a performance entry by its name. Here it will be the URL for that resource:

const impResourceTime = performance.getEntriesByName("https://awebsite.com/imp-resource.png");

Additionally, document processing metrics are also available to us such as domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, and domComplete.

The duration property conveys the load time of the document.

Using paint timing API

Painting is any activity by the browser that involves drawing pixels on the browser window. We can measure the “First Time to Paint” and “First Contentful Paint” with this API.

first-paint: The point at which the browser has painted the first pixel on the page

first-contentful-paint: The point at which the first bit of content is painted – i.e. something which is defined in the DOM. This could be text, image, or canvas render.

const paintEntries = performance.getEntriesByType("paint");

This will return an array consisting of two objects:

[
  {
    "name": "first-paint",
    "entryType": "paint",
    "startTime": 17718.514999956824,
    "duration": 0
  },
  {
    "name": "first-contentful-paint",
    "entryType": "paint",
    "startTime": 17718.519999994896,
    "duration": 0
  }
]

From the entries, we can extract out the metrics:

paintEntries.forEach((paintMetric) => {
  console.info(`${paintMetric.name}: ${paintMetric.startTime}`);
});

Using user timing

The User Timing API provides us with methods we can call at different places in our app, which lets us track where the time is being spent.

We can measure performance for scripts, how long specific JavaScript tasks are taking, and even the latency in how users interact with the page.

The mark method provided by this API is the main tool in our user timing analysis toolkit.

It stores a timestamp for us. What’s super useful about mark() is that we can name the timestamp, and the API will remember the name and the timestamp as a single unit.

Calling mark() at various places in our application lets us work out how much time it took to hit that mark in our web app.

performance.mark('starting_calculations')
const multiply = 82 * 21;
performance.mark('ending_calculations')

performance.mark('starting_awesome_script')
function awesomeScript() {
  console.log('doing awesome stuff')
}
performance.mark('ending_awesome_script');

Once we’ve set a bunch of timing marks, we then want to find out the elapsed time between these marks.

This is where the measure() method comes into play.

The measure() method calculates the elapsed time between marks, and can also measure the time between our mark and any of the well-known event names in the PerformanceTiming interface, such as paint, navigation, etc.

The measure method takes in 3 arguments: first is the name of the measure itself (which can be anything), then the name of the starting mark, and finally the name of the ending mark.

So, the above example with measure would be:

performance.mark('starting_calculations')
const multiply = 82 * 21;
performance.mark('ending_calculations')
+ performance.measure("multiply_measure", "starting_calculations", "ending_calculations");

performance.mark('starting_awesome_script')
function awesomeScript() {
  console.log('doing awesome stuff')
}
performance.mark('starting_awesome_script');
+ performance.measure("awesome_script", "starting_awesome_script", "starting_awesome_script");

To get all our measures, we can use our trusty getEntriesByType:

const measures = performance.getEntriesByType('measure');
    measures.forEach(measureItem => {
      console.log(`${measureItem.name}: ${measureItem.duration}`);
    });

This API is great for narrowing down the performance hot-spots in our web app to create a clear picture of where time is being spent.

Awesome! We have gathered all sorts of performance metrics. Now we can send all this data back to our monitoring tool, or send it to be stored someplace and analyzed for later.

Keep in mind, these APIs are not available everywhere. But, the great thing is that methods like getEntriesByType won’t throw errors if they can’t find anything.

So we can check if anything is returned by getEntriesByType or not and then do our PerformanceAPI measurements:

if (performance.getEntriesByType("navigation").length > 0) {
  // We have Navigation Timing API
}

Bonus: Use Performance API with Puppeteer

Puppeteer is a headless Node library that provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default.

Most things that you can do manually in the browser can be done using Puppeteer!

Here’s an example of using Navigation Timing API to extract timing metrics:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://awebsite.com'); // change to your website


  // Executes Navigation API within the page context
  const performanceTiming = JSON.parse(
      await page.evaluate(() => JSON.stringify(window.performance.timing))
  );
  console.log('performanceTiming', performanceTiming)
  await browser.close();
})();

This returns a timing object as seen previously in the Navigation Timing API section:

{
  "navigationStart": 1570451005291,
  "unloadEventStart": 1570451005728,
  "unloadEventEnd": 1570451006183,
  "redirectStart": 0,
  "redirectEnd": 0,
  "fetchStart": 1570451005302,
  "domainLookupStart": 1570451005302,
  "domainLookupEnd": 1570451005302,
  "connectStart": 1570451005302,
  "connectEnd": 1570451005302,
  "secureConnectionStart": 0,
  "requestStart": 1570451005309,
  "responseStart": 1570451005681,
  "responseEnd": 1570451006173,
  "domLoading": 1570451006246,
  "domInteractive": 1570451010094,
  "domContentLoadedEventStart": 1570451010094,
  "domContentLoadedEventEnd": 1570451010096,
  "domComplete": 1570451012756,
  "loadEventStart": 1570451012756,
  "loadEventEnd": 1570451012801
}

You can learn more about Puppeteer on the official website and also check out some of its uses in this repo.

Measure Performance in Production Environments

Whether you decide on React, Angular, Vue, or another framework, monitoring performance is key. If you’re interested in understanding performance issues in your production app, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on performance issues to quickly understand the root cause.

LogRocket instruments your app to record requests/responses with headers + bodies along with contextual information about the user to get a full picture of an issue. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Make performance a priority – Start monitoring for free.

Additional resources

  1. MDN Performance API
  2. A Primer for Web Performance Timing APIs
  3. Test website performance with Puppeteer
LogRocket
Record and Replay for Redux apps
Tools mentioned in article
Open jobs at LogRocket
Developer Advocate
Boston, MA
Get in on the ground floor at one of Boston's top startups and help solve a huge challenge for developers: understanding customer experience. We're looking for a highly motivated technologist to join our team as a Developer Advocate and help grow the LogRocket brand. Our Developer Advocate will continue to foster LogRocket's relationships within the web development community, keeping tabs on what product teams need to improve customer experiences. Additionally, the Developer Advocate will help users understand LogRocket’s benefits and value proposition. As a Developer Advocate, you will be speaking for LogRocket, so it’s essential that you create and cultivate LogRocket's voice through all of your outbound interactions. You should be excited about talking to frontend engineers all day with the aim of creating and empowering a strong LogRocket community. 
  • Grow online communities: social/Dev.to/Reddit/etc.
  • Product marketing: Address common user questions with website copy, docs, and blog posts
  • Team activation: Help activate and educate high-value teams and identify potential case studies and customer references
  • Content: Working with our Content Director, you’ll contribute regularly to our editorial calendar. Topics will have a broad scope, from educational content creation (resources, tutorial), to thought leadership and some product marketing
  • Over time, help build out our developer advocate team by training and mentoring new LogRocket team members

  • Engineering background and ability to get into the nitty gritty of frontend engineering
  • Strong technical writing skills
  • Ability to move fluidly between strategy and execution
  • Experience creating and distributing content across multiple platforms
  • Excellent communicator, natural bridge-builder and conflict defuser
  • Self-motivated and organized, ability to work independently
  • Competitive salary and equity package
  • Health, dental, and vision plans
  • Unlimited vacation time and generous holiday breaks
  • Culture of learning and development
  • 401k and commuter benefits
  • Catered lunch
  • Senior Engineer - Mobile
    Boston, MA
    Get in on the ground floor at one of Boston's top startups and help us solve a huge challenge for developers and product teams - understanding customer experience. LogRocket is the first system that gives these teams complete visibility into their customer's experience using their web apps - through pixel-perfect replays of user sessions and clear insight into logs, errors and network activity. We've already attracted an elite roster of customers (such as Reddit, Bloomberg and NBC) and recently raised a $15M Series B to continue building a world-class product and team. We’re looking for an experienced developer to join our team as the first Senior Mobile Engineer. This person will play a critical role in leading our mobile initiatives and expanding LogRocket's services to the mobile developer market. As the Senior Mobile Engineer, your extensive engineering background and knowledge will prove to be vital for delivering our first-of-its-kind mobile product to LogRocket customers. If you love building tools for other developers and solving exciting challenges, let’s talk!
  • Lead all initiatives related to native mobile support
  • Own the development, launch, and support of new mobile features
  • Monitor early adoption and leverage user feedback to make improvements
  • Work directly with customers and internal stakeholders to troubleshoot complex issues
  • Identify opportunities to increase adoption of LogRocket's mobile solution
  • You’re passionate about developer tools, with an extensive engineering background
  • Experience designing, building, testing, and delivering in agile environment
  • Deep knowledge of mobile frameworks at the OS level (iOS and Android)
  • Familiar with latest mobile application trends and technologies
  • Strong collaboration and communication skills
  • You get excited about low-level performance tuning and optimizing bandwidth usage
  • You're interested in compilers
  • You have a history of working with applications focused on security and privacy
  • Catered lunch throughout the week and a fully stocked kitchen with all your favorite snacks (healthy AND non-healthy)
  • Open vacation policy - we all work hard and take time for ourselves when we need it, no strings attached
  • Extensive Health, Dental, Vision benefits paid for by us, along with 401k and Commuter benefits
  • Generous stock options - we all get to own a piece of what we’re building
  • Regular team outings and activities (craft nights, boat cruises, excursions out of the city, and many more!)
  • Flexible working hours and location
  • Ample opportunities to learn and take on new responsibilities in a fast-paced, growth-mode startup
  • Rust Software Engineer
    Boston, MA
    Rust Software Engineer Get in on the ground floor at one of Boston's top startups and help us solve a huge challenge for developers and product teams - understanding customer experience. LogRocket is the first system that gives these teams complete visibility into their customer's experience using their web apps - through pixel-perfect replays of user sessions and clear insight into logs, errors and network activity. We've already attracted an elite roster of customers (such as Reddit, Bloomberg and NBC) and recently raised our Series B to continue building a world-class product and team. About the Role We’re looking for an experienced software engineer to lead LogRocket's expansion to the mobile developer market. You will be working on our SDK that provides LogRocket's monitoring and session replay functionality to native iOS and Android apps, working closely with business and technical leadership to bring this new product to market. We chose Rust to power our SDKs for its performance, portability, and ease-of-use. It also helps that it’s just plain fun to work with :)
  • Lead all initiatives (development, launch and support) related to our native mobile SDKs
  • Monitor early adoption and leverage user feedback to make improvements
  • Work directly with customers and internal stakeholders to troubleshoot complex issues
  • Identify opportunities to increase adoption of LogRocket's mobile solution
  • You get excited about low-level performance tuning and optimizing bandwidth usage
  • Experience designing, building, testing, and delivering in agile environment
  • Ability and interest to write production-quality Rust code (or strong desire to learn Rust)
  • Strong collaboration and communication skills
  • Experience building native iOS and/or Android SDKs
  • You have a history of working with applications focused on security and privacy
  • Catered lunch throughout the week and a fully stocked kitchen with all your favorite snacks (healthy AND non-healthy)
  • Open vacation policy - we all work hard and take time for ourselves when we need it, no strings attached
  • Extensive Health, Dental, Vision benefits paid for by us, along with 401k and Commuter benefits
  • Generous stock options - we all get to own a piece of what we’re building
  • Regular team outings and activities (craft nights, boat cruises, excursions out of the city, and many more!)
  • Flexible working hours and location
  • Ample opportunities to learn and take on new responsibilities in a fast-paced, growth-mode startup
  • Site Reliability Engineer
    Boston, MA
    Site Reliability Engineer LogRocket's mission is to help engineering and product teams create excellent experiences for their customers. By recording videos of user session, along with logs and network data, LogRocket intelligently highlights UX issues and surfaces the root cause of every bug. In this role, you'll build and maintain the tooling and infrastructure that allows LogRocket and our customers to deploy, scale, and monitor a high-throughput event processing system. You will handle on-call events, help with issue resolution, own post-mortems, and advocate for changes to improve the stability and security of the platform. For customers with on-premise hosting, you will be a technical resource to help them effectively run LogRocket in uniquely constrained environments across a variety of cloud offerings. We recently raised a $15M Series B and are currently doubling in usage every few weeks. It's a great time to join!
  • Work with members of the engineering team to identify and resolve spikes in processing latency in our ingestion worker pool
  • Help build tools to streamline the onboarding and release process for customers using LogRocket's On-Premise offering
  • Collaborate with our sales engineers to set up a customer’s LogRocket installation in a completely air-gapped environment
  • Overhaul a fleet of nginx load-balancers handling 100s of thousands of requests per second without incurring downtime
  • Automate database scaling to improve operating cost while maintaining the ability to respond to traffic spikes
  • Familiarity with the state of the art in cloud technologies, including common providers, specific tools of the trade, and their strengths and weaknesses
  • Experience operating applications and databases with demanding scalability or availability requirements
  • Proven expertise in modern container orchestration practices (we use Kubernetes on GKE)
  • A strong understanding of the performance, architecture, tooling, and cost of cloud systems
  • A security focused mindset with a solid understanding of incident response and risk mitigation
  • A strong collaborator who is transparent about progress on tasks, seeks feedback early and often, works effectively with the team and customers
  • Catered lunch throughout the week and a fully stocked kitchen with all your favorite snacks (healthy AND non-healthy)
  • Open vacation policy - we all work hard and take time for ourselves when we need it, no strings attached
  • Extensive Health, Dental, Vision benefits paid for by us, along with 401k and Commuter benefits
  • Generous stock options - we all get to own a piece of what we’re building
  • Regular team outings and activities (craft nights, boat cruises, excursions out of the city, and many more!)
  • Flexible working hours and location
  • Ample opportunities to learn and take on new responsibilities in a fast-paced, growth-mode startup
  • Verified by
    You may also like