Node.js Performance APIs: An Introduction
Tags: node
Published: 2024-03-27 13:00:00

Node.js offers a rich set of performance data through its APIs, similar to how modern web browsers expose performance metrics for webpages.

With tools like the Performance Measurement API and the `perf_hooks` module in Node.js, you can gain insights into your application's performance from the server's perspective, closely aligning with what the end user experiences.

In this article, the first of a two-part series about performance hooks, we'll explore how to approach performance optimization in Node.js. Along the way, we'll leverage Node's built-in timing APIs to enhance the efficiency of our application.

Let's get going!

## Prerequisites

To follow along with this tutorial, you'll need a basic knowledge of Node.js and a recent version installed, preferably the [latest LTS](https://nodejs.org/en/download).

## Understanding Fundamental Node.js Performance API Concepts

Before diving into Node.js performance APIs, it's important to understand some essential terms that frequently appear in discussions and documentation: a monotonic clock, high resolution time API, and performance timeline. Let's look at each of these in turn.

### 1. A Monotonic Clock

In JavaScript, the `Date.now()` function generates timestamps based on the system clock, counting from the Unix epoch (`1970-01-01T00:00:00Z`). However, using these timestamps for precise measurements can be unreliable because they are vulnerable to any alterations in the system clock.

In contrast, a [monotonic clock](https://w3c.github.io/hr-time/#dfn-monotonic-clock) offers a timekeeping mechanism that always progresses forward, without any backward movement. Its key advantage lies in its stability against system clock changes, making it ideal for accurately measuring the time span between events or the duration of specific tasks.

### 2. High Resolution Time API

The [High Resolution Time API](https://www.w3.org/TR/hr-time/) provides a JavaScript interface for obtaining highly accurate time measurements. It uses a sub-millisecond monotonic timestamp that remains unaffected by any changes to the system clock.

This API's functionality is encapsulated in the `performance.now()` method. It returns a timestamp in sub-milliseconds, calculated relative to `performance.timeOrigin` — a UNIX timestamp denoting the initiation of a current Node.js process.

Here's a quick example to illustrate its usage:

```javascript
import { performance } from "node:perf_hooks";

console.log(performance.now());
```

Running this code produces output such as:

```shell
26.664009988307953
```

This means that the `console.log` statement runs about 26 milliseconds from the initialization of the Node.js process.

Whenever you come across terms like "high resolution" or "high precision timestamp" in JavaScript, this refers to values generated by the `performance.now()` method.

### 3. Performance Timeline

The Performance Timeline in Node.js is a structured sequence of timing details and key performance milestones. These are captured both from the Node.js process itself and any code executed within your application.

This timeline consists of `PerformanceEntry` objects (and subclasses), each offering insights into the type and name of an event, along with precise timestamps indicating both the start and the duration of the event.

Here's an example of such entries:

```javascript
[
  PerformanceMark {
    name: 'query_start',
    entryType: 'mark',
    startTime: 49.083899974823,
    duration: 0,
    detail: null
  },
  PerformanceMark {
    name: 'query_end',
    entryType: 'mark',
    startTime: 674.0229699611664,
    duration: 0,
    detail: null
  }
]
```

The Performance Timeline is mainly used to monitor and extract comprehensive performance metrics throughout the entire lifecycle of a Node.js application.

This information is invaluable for real-time performance analysis and can be seamlessly integrated with monitoring systems like [AppSignal for Node.js](https://www.appsignal.com/nodejs) to enhance application performance insights and diagnostics.

## Inside the Node.js Performance API

Node.js offers a suite of Performance Measurement APIs within its `perf_hooks` module that are specifically designed to record Node.js performance data.

These APIs are inspired by, and partially implement, the
[Web Performance APIs](https://developer.mozilla.org/en-US/docs/Web/API/Performance) found in browser environments. However, they are tailored and adapted to suit server-side programming contexts.

In the following sections, we'll dive into some of the key APIs provided by this module:

1. `PerformanceNodeTiming`
2. `Performance.now()`
3. `Performance.mark()`
4. `Performance.getEntries()`
5. `Performance.measure()`
6. `Performance.clearMarks()`
7. `Performance.clearMeasures()`
8. `PerformanceResourceTiming`

### 1. `PerformanceNodeTiming`

In any Node.js program, the initial entry in the Performance Timeline is a `PerformanceNodeTiming` instance. It distinctively captures the timestamps of significant milestones during the startup phase of a process. To access this data, you can print the `performance` object, as demonstrated below:

```javascript
import { performance } from "node:perf_hooks";

console.log(performance);
```

This results in an output like:

```javascript
Performance {
  nodeTiming: PerformanceNodeTiming {
    name: 'node',
    entryType: 'node',
    startTime: 0,
    duration: 17.71333795785904,
    nodeStart: 0.256866991519928,
    v8Start: 2.246299982070923,
    bootstrapComplete: 9.73558497428894,
    environment: 6.018610000610352,
    loopStart: 13.11082899570465,
    loopExit: -1,
    idleTime: 0.03074
  },
  timeOrigin: 1700776448830.537
}
```

The `nodeTiming` object, as part of the [Node.js runtime timings](https://nodejs.org/api/perf_hooks.html#class-performancenodetiming), provides a detailed view of various stages in the process lifecycle. Here's a breakdown of its key properties:

- `name`: Identifies the performance entry, which is the Node.js process in this case.
- `entryType`: Specifies the type of performance entry.
- `startTime`: Indicates when the measurement started, with 0 denoting the beginning of the process execution.
- `duration`: Represents the duration from the start of the process to the point when the entry was recorded.
- `nodeStart`: A high resolution timestamp marking the start of the Node.js runtime initialization.
- `v8Start`: The time at which the V8 JavaScript engine began initializing.
- `bootstrapComplete`: The time taken for the Node.js environment to complete setup and get ready to execute user scripts.
- `environment`: The duration required to establish the Node.js environment.
- `loopStart`: The high resolution timestamp marking the start of the event loop.
- `loopExit`: Indicates when the event loop exited; a value of -1 suggests it was still active at the time of recording.
- `idleTime`: The amount of time the event loop spent idle, not engaged in processing tasks.

### 2. `Performance.now()`

To measure an operation's execution time in Node.js, you can use `performance.now()` to generate high-resolution timestamps.

By capturing two timestamps — one before the operation starts and another after its completion — and then calculating the difference between them, you can determine an operation's duration.

The following example demonstrates this:

```javascript
import { performance } from "node:perf_hooks";

const t0 = performance.now();
await fetch("https://jsonplaceholder.typicode.com/todos/1");
const t1 = performance.now();

console.log(`Fetch request took ${t1 - t0} milliseconds.`);
```

Running this script typically results in an output like:

```shell
Fetch request took 682.8348079919815 milliseconds.
```

While this works for simple cases, there are more flexible methods available, which we'll explore later in this tutorial.

### 3. `Performance.mark()`

The `performance.mark()` function is a powerful method for creating and adding named markers to the Performance Timeline. These markers can later be retrieved for performance analysis.

For instance, to track the start and end of an HTTP request, you might use it as follows:

```javascript
import { performance } from "node:perf_hooks";

const mark1 = performance.mark("mark_fetch_start");
await fetch("https://jsonplaceholder.typicode.com/todos/1");
const mark2 = performance.mark("mark_fetch_end");

console.log(mark1);
console.log(mark2);
```

This will result in an output similar to:

```javascript
PerformanceMark {
  name: 'mark_fetch_start',
  entryType: 'mark',
  startTime: 17.95012092590332,
  duration: 0,
  detail: null
}
PerformanceMark {
  name: 'mark_fetch_end',
  entryType: 'mark',
  startTime: 959.4822289943695,
  duration: 0,
  detail: null
}
```

In this example, the `startTime` for each `PerformanceMark` object reflects a high-resolution timestamp, akin to what `performance.now()` provides. The `duration` for such objects is always zero since they represent specific moments in time.

You can also enrich these markers with additional information using the `detail` property:

```javascript
import { performance } from "node:perf_hooks";

const url = "https://jsonplaceholder.typicode.com/todos/1";
const options = {
  detail: {
    url,
  },
};

const mark1 = performance.mark("mark_fetch_start", options);
await fetch(url);
const mark2 = performance.mark("mark_fetch_end", options);

console.log(mark1);
console.log(mark2);
```

Resulting in:

```javascript
// outputs:
PerformanceMark {
  name: 'mark_fetch_start',
  entryType: 'mark',
  startTime: 25.053369998931885,
  duration: 0,
  detail: { url: 'https://jsonplaceholder.typicode.com/todos/1' }
}
PerformanceMark {
  name: 'mark_fetch_end',
  entryType: 'mark',
  startTime: 690.2271919250488,
  duration: 0,
  detail: { url: 'https://jsonplaceholder.typicode.com/todos/1' }
}
```

By adding the `detail` property, the performance marks provide not just timing information but also contextual data, enhancing the comprehensiveness of your performance analysis.

### 4. `Performance.getEntries()`

A `PerformanceEntry` is a unique entity representing a single performance metric within an application's Performance Timeline. For instance, the `PerformanceMark` objects we discussed earlier are subclasses of `PerformanceEntry`, characterized by an `entryType` of `mark`.

Use `getEntries()` to retrieve an array of all the `PerformanceEntry` objects logged up to a specific moment in your program's execution. Here's an example:

```javascript
import { performance } from "node:perf_hooks";

performance.mark("mark_fetch_start");
await fetch("https://jsonplaceholder.typicode.com/todos/1");
performance.mark("mark_fetch_end");

console.log(performance.getEntries());
```

Running this snippet of code produces an array output like this:

```javascript
[
  PerformanceMark {
    name: 'mark_fetch_start',
    entryType: 'mark',
    startTime: 49.083899974823,
    duration: 0,
    detail: null
  },
  PerformanceMark {
    name: 'mark_fetch_end',
    entryType: 'mark',
    startTime: 674.0229699611664,
    duration: 0,
    detail: null
  }
]
```

For situations where your Performance Timeline contains numerous entries and you wish to access only specific subsets, Node.js offers `getEntriesByName()` and `getEntriesByType()` methods. These methods allow for filtering based on the `name` and `entryType` properties of the performance entries:

```javascript
// get all entries whose name is `mark_fetch_start`
console.log(performance.getEntriesByName("mark_fetch_start"));

// get all entries whose name is `mark_fetch_start` and whose type is `mark`
console.log(performance.getEntriesByName("mark_fetch_start", "mark"));

// get all entries whose entryType is `mark`
console.log(performance.getEntriesByType("mark"));
```

These methods provide a more focused approach to examining performance data, enabling you to isolate and analyze specific aspects of your application's performance.

### 5. `Performance.measure()`

The `Performance.measure()` method creates `PerformanceMeasure` objects that quantify the duration between two specific points in time (often marked by `PerformanceMark` objects) within an application's Performance Timeline:

```javascript
performance.measure(name[, startMarkOrOptions[, endMark]])
```

If only the `name` parameter is supplied, it will generate a
`PerformanceMeasure` object capturing the program's duration up until the call:

```javascript
console.log(performance.measure("measureProgram"));
```

This command results in an output like:

```javascript
PerformanceMeasure {
  name: 'measureProgram',
  entryType: 'measure',
  startTime: 0,
  duration: 936.0637300014496
}
```

A more practical usage of `measure()` is calculating the elapsed time between two previously recorded `PerformanceMark` objects:

```javascript
import { performance } from "node:perf_hooks";

const url = "https://jsonplaceholder.typicode.com/todos/1";
const mark1 = performance.mark("mark_fetch_start");
await fetch(url);
const mark2 = performance.mark("mark_fetch_end");

console.log(
  performance.measure("measureProgram", "mark_fetch_start", "mark_fetch_end")
);
```

This will output something similar to the following:

```javascript
PerformanceMeasure {
  name: 'measureFetch',
  entryType: 'measure',
  startTime: 22.075212001800537,
  duration: 903.0410770177841
}
```

In this scenario, `startTime` corresponds to the start time of the first mark (`mark_fetch_start`), and `duration` is the difference in time between the two marks.

This effectively measures the time that the HTTP request takes, offering a precise and practical way to assess specific durations within your application's performance timeline.

### 6. `Performance.clearMarks()`

The `performance.clearMarks()` function removes `PerformanceMark` objects from the Performance Timeline. This feature provides flexibility, allowing you to either clear all existing marks or remove specific ones identified by a name.

This is particularly useful for managing and resetting performance data, especially in long-running applications or during repeated testing phases.

The function can be employed as follows:

```javascript
// Remove all `PerformanceMark` objects
performance.clearMarks();

// Remove only `PerformanceMark` objects named `mark_fetch_start`
performance.clearMarks("mark_fetch_start");
```

### 7. `Performance.clearMeasures()`

This function does the same thing as `clearMarks()` but removes `PerformanceMeasure` objects instead.

### 8. `PerformanceResourceTiming`

The `PerformanceResourceTiming` API is a specialized tool for capturing detailed network timing data, allowing you to track the duration of network requests in your code.

While we've used methods like `performance.now()`, `performance.mark()`, and `performance.measure()` to track the duration of an HTTP request, they are more general-purpose tools.

In practice, the `fetch` API automatically gathers performance timings for each request, which are then recorded in the Performance Timeline as `PerformanceResourceTiming` objects.

```javascript
import { performance } from "node:perf_hooks";

const url = "https://jsonplaceholder.typicode.com/posts/1";
const response = await fetch(url);
await response.json();
console.log(performance.getEntries());
```

Executing this script will result in an output resembling:

```javascript
[
  PerformanceResourceTiming {
    name: 'https://jsonplaceholder.typicode.com/posts/1',
    entryType: 'resource',
    startTime: 33.411154985427856,
    duration: 1665.9909150600433,
    initiatorType: 'fetch',
    nextHopProtocol: undefined,
    workerStart: 0,
    redirectStart: 0,
    redirectEnd: 0,
    fetchStart: 33.411154985427856,
    domainLookupStart: undefined,
    domainLookupEnd: undefined,
    connectStart: undefined,
    connectEnd: undefined,
    secureConnectionStart: undefined,
    requestStart: 0,
    responseStart: 0,
    responseEnd: 1699.4020700454712,
    transferSize: 300,
    encodedBodySize: 0,
    decodedBodySize: 0
  }
]
```

This `PerformanceResourceTiming` object encapsulates
[several measurements](https://nodejs.org/api/perf_hooks.html#class-performanceresourcetiming) for the HTTP request. Key attributes include:

- `startTime`, `fetchStart`: The high resolution timestamp marking the commencement of the resource fetch.
- `responseEnd`: The high resolution timestamp right after Node.js receives the last byte of the resource (or just before the transport connection closes, whichever occurs first).
- `duration`: The difference between `responseEnd` and `startTime`.
- `transferSize`: The total size of the fetched resource, including both response headers and body, measured in octets (commonly the same as bytes).

It's important to note that this data gets recorded in the timeline only when the response is actually used. Omitting the `await response.json()` line, for instance, would result in an empty array.

Finally, let's take a quick look at how to manage resource buffer sizes.

### Managing Resource Buffer Sizes in Node

The Performance Timeline has a default capacity limit for `PerformanceResourceTiming` objects, set at 250. To adjust this limit, you can utilize the `setResourceTimingBufferSize()` method:

```javascript
performance.setResourceTimingBufferSize(500);
```

This command increases the buffer size, allowing up to 500 `PerformanceResourceTiming` objects to be stored simultaneously in the Performance Timeline.

If you need to clear all the existing `PerformanceResourceTiming` objects from the timeline, the `clearResourceTimings()` method comes in handy:

```javascript
performance.clearResourceTimings();
```

You can also listen for the `resourcetimingbufferfull` event to take actions when the performance resource timing buffer is full. For example, you could increase the buffer capacity or clear the buffer so that new entries can be added:

```javascript
performance.addEventListener("resourcetimingbufferfull", (event) => {
  if (performance.getEntriesByType("resource").length < 1000) {
    performance.setResourceTimingBufferSize(1000);
  } else {
    performance.clearResourceTimings();
  }
});
```

And that's it for now!

## Up Next: Practical Uses of Node.js Performance APIs

In this blog post, we focused on examining the most important Node.js Performance APIs and their various uses.

In the next and final part of this series, we'll dive into practical implementations of these APIs. I'll demonstrate their real-world value to optimize and resolve performance issues in your Node.js applications.

Stay tuned, and happy coding!

**P.S. If you liked this post, [subscribe to our JavaScript Sorcery list](https://blog.appsignal.com/javascript-sorcery) for a monthly deep dive into more magical JavaScript tips and tricks.**

**P.P.S. If you need an APM for your Node.js app, go and [check out the AppSignal APM for Node.js](https://www.appsignal.com/nodejs).**