Measure execution times in browsers & Node.js
Tags: node, typescript, javascript, webdev
Published: 2021-04-18 17:55:36

---
title: Measure execution times in browsers & Node.js
published: true
description: Learn how to measure your JavaScript function's execution time. Also works with TypeScript!
tags: nodejs, typescript, javascript, webdev
cover_image: https://images.unsplash.com/photo-1508962914676-134849a727f0
---

Measuring your apps performance is very important when your code is being used in production. You should therefore know the execution time of your most frequently used functions. Modern browsers and the Node.js platform provide great APIs to measure performance. In this article, I am presenting a few selected ones using JavaScript and TypeScript code examples.
<!--more-->
## Business Logic

First of all, we need a function that mimics our business logic. To make it simple, I am using a function which will return a value after 2 seconds (2000ms):

```ts
function businessLogic(): Promise<number> {
  return new Promise((resolve) => {
    setTimeout(resolve, 2000);
  });
}
```

## Console Timers (`console.time`)

The easiest way to print the execution time of a function to the console, is to use a [console timer](https://developer.mozilla.org/en-US/docs/Web/API/Console/time). Everything that has to be done, is calling `console.time` and `console.timeEnd` with the same identifier:

```ts
(async () => {
  console.time('businessLogic');
  await businessLogic();
  console.timeEnd('businessLogic');
})();
```

As a result, we get the execution time printed to our console in a human-readable format (can be milliseconds, seconds, or other resolutions):

```bash
businessLogic: 2.012s
```

## High Resolution Timers (`performance.now`)

If you want to customize the output of your [profiling](https://en.wikipedia.org/wiki/Profiling_(computer_programming)), then you can use a [high resolution timer](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) like `performance.now`. It will return the measured execution time in 1 millisecond increments:

```ts
(async () => {
  const start = performance.now();
  await businessLogic();
  const stop = performance.now();
  const inSeconds = (stop - start) / 1000;
  const rounded = Number(inSeconds).toFixed(3);
  console.log(`businessLogic: ${rounded}s`);
})();
```

```bash
businessLogic: 2.012s
```

### Time tracking util

You can also build your own utility function to track execution time with `performance.now`:

```ts
// Util function to track execution time in seconds
export async function trackInSeconds(fn: Function): Promise<string> {
  const start = performance.now();
  await fn();
  const end = performance.now();
  const inSeconds = (end - start) / 1000;
  return Number(inSeconds).toFixed(0);
}

(async () => {
  // Your business logic
  const myFunction = () => {
    return new Promise(resolve => {
      // Function completes after 5s
      setTimeout(resolve, 5000);
    });
  };

  const timeInSeconds = await trackInSeconds(myFunction);
  console.log(`Execution took ${timeInSeconds} seconds.`);
})();
```

```bash
Execution took 5 seconds.
```

## Performance Hooks (`perf_hooks`)

Node.js provides [performance measurement APIs](https://nodejs.org/api/perf_hooks.html) to profile JavaScript and TypeScript functions. With the `perf_hooks` module it becomes very convenient to profile multiple functions at once.

### TypeScript Typings

To use the `perf_hooks` module with TypeScript, we have to install type definitions that match our Node.js version (I am using v14):

```bash
npm install --save @types/node@14
```

### Performance Observer

We have seen that `console.time` doesn't let us customize the output and `performance.now` is very difficult to control if you want to monitor several functions. That's why Node.js provides a [performance observer](https://nodejs.org/api/perf_hooks.html#perf_hooks_class_perf_hooks_performanceobserver). The performance observer can listen to different kinds of measurements and receives [entries](https://nodejs.org/api/perf_hooks.html#perf_hooks_class_performanceentry) that return the measured time in milliseconds.

To make the performance collection asynchronous, the `buffered` flag can be used, so that multiple entries will be buffered internally:

```ts
import {PerformanceObserver} from 'perf_hooks';

const observer = new PerformanceObserver(list => list.getEntries().forEach(entry => console.info(entry)));
observer.observe({buffered: true, entryTypes: ['measure']});
```

### Performance Marks (`performance.mark`)

After setting up the performance observer, we can start a measurement. The simplest way is to set markings. It works similar to the `console.time` approach with the difference that we need to use different labels for the start and the stop:

```ts
import {performance, PerformanceObserver} from 'perf_hooks';

function businessLogic(): Promise<number> {
  return new Promise((resolve) => {
    setTimeout(resolve, 2000);
  });
}

(async () => {
  const observer = new PerformanceObserver(list => list.getEntries().forEach(entry => console.info(entry)));
  observer.observe({buffered: true, entryTypes: ['measure']});

  performance.mark('start');
  await businessLogic();
  performance.mark('stop');

  performance.measure('Business Logic', 'start', 'stop');
})();
```

💡 Please note that our observer listens to entries of type `'measure'`.

**Output:**

```bash
PerformanceEntry {
  name: 'Business Logic',
  entryType: 'measure',
  startTime: 3020.9561,
  duration: 2007.4025
}
```

### Performance Instrumentation (`performance.timerify`)

For more convenience, there is the `performance.timerify` function. It wraps new functions automatically into performance marks, so that we don't need to declare start and stop. In that case our observer must listen to the entry type `'function'`:

```ts
(async () => {
  const observer = new PerformanceObserver(list => list.getEntries().forEach(entry => console.info(entry)));
  observer.observe({buffered: true, entryTypes: ['function']});

  const wrapped = performance.timerify(businessLogic);
  await wrapped();
})();
```

```bash
PerformanceEntry {
  name: 'businessLogic',
  entryType: 'function',
  startTime: 2221.5801,
  duration: 0.6079
}
```

💡 As you can see, the tracked `duration` is different from our measurements with `performance.mark`. That's because `performance.timerify` doesn't work out of the box with asynchronous functions on Node.js v14. 

[James M Snell](https://jasnell.me/) from the Node team [tweeted me](https://twitter.com/jasnell/status/1383864819292381188) that `performance.timerify` will work with async functions in Node v16 and above.

With Node.js v14 we have to use the `async_hooks` module to register callbacks tracking the lifetime of asynchronous resources. There is good documentation with an example on [measuring the duration of async operations](https://nodejs.org/api/perf_hooks.html#perf_hooks_measuring_the_duration_of_async_operations).

### Completion of measurement

It's recommended to disconnect the performance observer from all incoming notifications, when you are done with your measurements:

```ts
observer.disconnect();
```

## Inclusion in Unit Tests 

If you want to ensure the execution speed of your functions in the long run, you can make them part of your unit tests. Many testing frameworks (like [Jest](https://jestjs.io/), [Jasmine](https://jasmine.github.io/), and others) allow you to set a timeout for the execution of your test. The timeout feature can be used to mark a test as failed if the tested function takes too long to execute. 

Here is a timeout example with the Jasmine testing framework:

**businessLogic.ts**

```ts
export function businessLogic(): Promise<number> {
  return new Promise((resolve) => {
    setTimeout(resolve, 2000);
  });
}
```

**businessLogic.test.ts**

```ts
import {businessLogic} from './businessLogic';

describe('businessLogic', () => {
  it('does not take longer than 3 seconds (3000ms)', async () => {
    await businessLogic();
  }, 3000);
});
```

## Get connected 🔗

Please [follow me on Twitter](https://twitter.com/bennycode) or [subscribe to my YouTube channel](https://www.youtube.com/c/typescripttv) if you liked this post. I would love to hear from you what you are building. 🙂 Best, Benny