Performance is always an important thing to think about when developing an application. It has a great impact on user satisfaction—the faster the code, the smoother the experience. But, as we all know, not all code is written perfectly from the start, and even small inefficiencies pile up over time. So, once this happens and you need to improve the performance of your project, where would you start?
The First Step
Determining where to look is the first step to solving the performance issues of an application. Narrowing down the inefficient suspects from the whole application into separate pages, components or even functions can greatly help the investigation since it is much easier to spot low performance patterns in smaller chunks of code. Fortunately, there are readily available tools to help with this kind of analysis. At MEWS, we chose New Relic and its powerful Event API.
Meet New Relic
The New Relic Telemetry Data Platform provides a selection of tools for collecting, exploring and alerting on any data collected by your application. One of its features – Event API – is perfect for detecting and investigating performance issues. It allows you to send large amounts of custom data from your application and run SQL-like queries on it. Collecting the right data and making the right queries can provide the information you need to identify the inefficient parts of your code.
Using It in Code
The New Relic Event API operates via POST requests, so no additional JavaScript libraries are needed. You just need to get a New Relic account and generate a license key, and you’re ready to go.
Whenever you want to submit an event (more on that later), just follow these three steps. Firstly, you need to create an object with the event itself:
const event = { eventType: ‘yourEventType’, customKey1: ‘customValue1’, … }
The object can contain any data you wish to track and an eventType
, which you will use to find these events in the New Relic Explorer. The allowed values are strings and numbers (integers or floats), and, unfortunately, you cannot use nested objects.
Once you have your event, you should compress it. It is not strictly necessary, but it is very advisable. The two accepted encodings are gzip
and deflate
. At MEWS we are using pako for compression, but the specific approach is up to you.
import { gzip } from ‘pako’; const compressedEvent = gzip(JSON.stringify(event));
Finally, you just need to send the POST request to log the event:
fetch(https://insights-collector.newrelic.com/v1/accounts/YOUR_ACCOUNT_ID/events, { method: ‘POST’, headers: { ‘Api-Key’: YOUR_LICENSE_KEY, ‘Content-Type’: ‘application/json’, ‘Content-Encoding’: ‘gzip’, }, body: compressedEvent, });
You can also send multiple events by putting them into an array before compression. This is especially useful for batching. Instead of sending the events immediately after they occur, you can save them in an array and only occasionally call the API. For this to work, however, you would also need to provide a timestamp for each event since, by default, the time of the event occurrence is calculated at the time of the API call.
What To Measure?
Once you have the code for sending the events, the obvious question to ask is “what should the events be?”. There are many possibilities, but at MEWS we are measuring three main things—memory consumption, request timings and Long Tasks.
Memory
The basic memory information can be found in the window.performance.memory object. The most important attribute is usedJSHeapSize
, which contains the active memory allocated in the heap. If this value significantly grows over time, your application might have a memory leak.
Request timings
We use the PerformanceObserver for detecting and reporting information about the loading of the application’s resources. When called with performanceObserver.observe({ entryTypes: ['resource'] })
, it provides instances of PerformanceResourceTiming, which we unpack and report to New Relic. This information can then be used to track the application load time, API call durations and many other things.
Long Tasks
The same PerformanceObserver
can detect Long Tasks when called with preformanceObserver.observe({ entryTypes: ['longtask'] })
. The Long Tasks API provides information about JavaScript tasks that take longer than 50 milliseconds to execute. Long tasks keep the main UI thread busy and make the application feel slow, so tracking and fixing them is important for improving performance.
These metrics don’t have the best browser support, but even the information from a fraction of your users will greatly help you with identifying performance problems in your application.
Extra Data
The metrics above are useful, but they can be better. In our applications we extend each event with extra data—the URL of the page, user ID, session ID, environment and many others. This allows us to filter the events by these values and find the correlations between them and the performance. The more of these you have, the easier it will be to narrow down the problem to separate users, pages or even functions and methods.
Inspecting the Data
Once you have the data coming in from your application, it’s finally time to explore it using the tools provided by New Relic. Most of the data analysis for the events is done using NRQL—an SQL-like query language. The official documentation provides a lot of information on how to build the queries, but here are a few examples of what you can do:
- Show all logged events with
{ eventType: 'LongTask' }
. This is mostly useful to inspect the shape of the data before making more complicated queries.
SELECT * FROM LongTask
- Plot the 50th, 95th and 99th percentiles of Long Task durations for the last week (with 1 hour precision). This can be used for tracking the performance of the app over time.
SELECT percentile(duration, 50, 95, 99) FROM LongTask SINCE 1 week ago TIMESERIES 1 hour
- Get the URLs with the highest 95th percentile of Long Task durations. This can be used for detecting the pages with the highest performance impact.
SELECT percentile(duration, 95) from LongTask FACET normalizedUrl
- Get the 95th percentile of Long Task durations grouped by application uptime (in minutes). This confirms degradation of performance throughout a single session.
SELECT percentile(duration, 95) FROM LongTask FACET buckets(startTime / (1000 * 60), width: 50, buckets: 5)
As you can see, the querying language can be used to inspect and monitor the data as well as identify potential weak points via grouping. If, however, you need to have more precise control over the data or need to make more complicated charts, New Relic has you covered with its Custom Visualizations. This article won’t go into detail about it, but it’s definitely something worth checking out.
Preventing the Problem
New Relic helps identify and localize existing performance problems, but it’s also great at preventing them in the first place. Any query you make can be equipped with an alert that will notify you when something goes wrong before the small inefficiencies pile up and become a big performance hit. The official documentation will guide you through the process of creating the alerts and working with them.
To Sum Up
Application speed is important. Improving performance is a tricky problem but knowing where to look helps a lot. And New Relic is a great tool built exactly for that.
You can also watch Alex speaking about this topic at a meetup organized by Frontendisti: