DEV Community

loading...
Cover image for Measure execution times in browsers & Node.js

Measure execution times in browsers & Node.js

bennycode profile image Benny Neugebauer ・4 min read

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.

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):

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

Console Timers (console.time)

The easiest way to print the execution time of a function to the console, is to use a console timer. Everything that has to be done, is calling console.time and console.timeEnd with the same identifier:

(async () => {
  console.time('businessLogic');
  await businessLogic();
  console.timeEnd('businessLogic');
})();
Enter fullscreen mode Exit fullscreen mode

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

businessLogic: 2.012s
Enter fullscreen mode Exit fullscreen mode

High Resolution Timers (performance.now)

If you want to customize the output of your profiling, then you can use a high resolution timer like performance.now. It will return the measured execution time in 1 millisecond increments:

(async () => {
  const start = performance.now();
  await businessLogic();
  const end = performance.now();
  const inSeconds = (end - start) / 1000;
  const rounded = Number(inSeconds).toFixed(3);
  console.log(`businessLogic: ${rounded}s`);
})();
Enter fullscreen mode Exit fullscreen mode
businessLogic: 2.012s
Enter fullscreen mode Exit fullscreen mode

Performance Hooks (perf_hooks)

Node.js provides performance measurement APIs 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):

npm install --save @types/node@14
Enter fullscreen mode Exit fullscreen mode

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. The performance observer can listen to different kinds of measurements and receives entries 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:

import {PerformanceObserver} from 'perf_hooks';

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

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:

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');
})();
Enter fullscreen mode Exit fullscreen mode

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

Output:

PerformanceEntry {
  name: 'Business Logic',
  entryType: 'measure',
  startTime: 3020.9561,
  duration: 2007.4025
}
Enter fullscreen mode Exit fullscreen mode

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':

(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();
})();
Enter fullscreen mode Exit fullscreen mode
PerformanceEntry {
  name: 'businessLogic',
  entryType: 'function',
  startTime: 2221.5801,
  duration: 0.6079
}
Enter fullscreen mode Exit fullscreen mode

💡 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 from the Node team tweeted me 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.

Completion of measurement

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

observer.disconnect();
Enter fullscreen mode Exit fullscreen mode

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, Jasmine, 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

export function businessLogic(): Promise<number> {
  return new Promise((resolve) => {
    setTimeout(resolve, 2000);
  });
}
Enter fullscreen mode Exit fullscreen mode

businessLogic.test.ts

import {businessLogic} from './businessLogic';

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

Want more?

If you liked this post, then I invite you to take a look at the YouTube channel of TypeScript TV. Together with friends, I am publishing videos on best practices with TypeScript whenever we find something exciting.

Discussion (0)

pic
Editor guide