(react-) frontend perfomance monitoring

Frontend performance monitoring from scratch

The first question you should ask yourself when thinking about Performance optimizations is the following:

Is my service unique enough to focus more on features and only do rudimentary performance optimizations, or do I rely on being the best in the business to attract costumers?

If you need to be the best because of generic features you should invest in the performance part, and here it is all about perceived performance.
The beginning of this talk captures the concept of perceived performance quite well.

A great metaphor taken from it:

Imagine going into a restaurant and after ordering you get everything at once. Your Drinks, Appetizers, Main Course and Dessert.
You would probably be wondering if anything will come at all and then be quite annoyed after waiting so long for everything.

I need performance!

With the resolve that you need to invest in performance you need to figure out what exactly can increase it for your project.
To do this you have to analyse your users and what they need.
As always there is no silver bullet!
Dont blindly believe anyone who is trying to sell it you, if you are serious about it.

To have meaningful data for your project you should first identify what parts are important and for which you want to reduce the time to interactive.
If you know your parts we can now implement monitoring for them, which is the goal of this post.

Please note, that react is assumed as the monitoring target, but any stack would work.
The github repos are linked at the end of the article.

The setup

The basic setup will be made out of 3 services.
One service to receive metrics and store them, our metrics API.
One service to make visual sense out of that data, and the application that is to be monitored.
All Repos can be found at the end of this post.

Metrics API

This service just receives new entries under its index route via a POST request. The payload looks like following:

{
    component: string,
    time: number
}

If you hit the index via GET, you will get a list of all stored entries.

Metrics visualization

For now the service just asks the API for all stored entries and displays them in a list. Simple, but shows the idea. Later, graphs etc. can be added.

Check out the code here

Implementing the monitoring

Here is the simple test app:

screenshot of app

Once our setup is in place we can start sending post requests on important events in our Frontend. For this application, the most important part is when the HOME component is loaded.
Another important metric is the time, in detail when did our user get the time from the clock component displayed?
These should not be too far apart, as this is the most crucial part of the app.

To do this, we simply have to add 2 POST requests. The first one we will fire when the HOME component was mounted.

componentDidMount() {
    try {
      const timeSinceStart = Date.now() - timer.start;
      api.post(config.METRIC_SERVER, {
        component: "Home",
        time: timeSinceStart
      });
    } catch (e) {
      console.log(e);
    }
  }

The second one will come right after the data for the Clock has been fetched in the Clock component.

getTime = async () => {
  try {
    const data = await api.get(config.TIME_API);
    const timeSinceStart = Date.now() - timer.start;
    this.setState({ time: new Date(data.date).toTimeString() });
    api.post(config.METRIC_SERVER, {
      component: "Clock loaded",
      time: timeSinceStart
    });
  } catch (e) {
    console.log(e);
  }
};

Notice that we dont want to await the response of those POST calls. We just fire and forget. (statsd)[https://github.com/etsy/statsd] f.e. uses UDP, we are sending stuff via REST from the Browser though, but still dont care if some of our metrics get dropped. We will rely on the amount of data that will come in over time.

The timer

You might have noticed the timeSinceStart const. Especially the timer.start.
To get this time we need to attach the timer to the window object as soon as the page loads. The examples are Server-side rendered react apps.
In order to get this running we can inject the following code into the inital HTML.

   <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta charset="utf-8" />
        <title>Welcome to Razzle</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script>window.__timer__ = {start: Date.now()};</script>
        ${
          assets.client.css
            ? `<link rel="stylesheet" href="${assets.client.css}">`
            : ""
        }
        ${
          process.env.NODE_ENV === "production"
            ? `<script src="${assets.client.js}" defer></script>`
            : `<script src="${assets.client.js}" defer crossorigin></script>`
        }
    </head>

It just stores the time, when the JS starts executing on a __timer__ variable on the window. Also this code is one of the first to execute. Giving us a better estimate of the real time when the page was rendered.

In the code we can then access this via a module. Since the window is not available on the node side we have to add some check like follows:

let timer = { start: Date.now() };

if (typeof window !== "undefined") {
  timer = window.__timer__;
}

export default timer;

Now we can calculate the time since page load, by substracting the start time of the timer from the current time, when we send out out requests to the Metrics API.

The resuls

Now lets analyse our results.

results of monitoring

We can clearly see, that it takes much longer for our user to see the time than it takes for the Home component to load.
Now we can focus on fixing this, to provide as much value, as early as possible (Hint: dont make the user click!)

Repos

Back to overview