DEV Community

Cover image for Measure execution times in browsers & Node.js
Benny Code for TypeScript TV

Posted on • Edited on

Measure execution times in browsers & Node.js

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 stop = performance.now();
  const inSeconds = (stop - 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

Time tracking util

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

// 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.`);
})();
Enter fullscreen mode Exit fullscreen mode
Execution took 5 seconds.
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

Get connected 🔗

Please follow me on Twitter or subscribe to my YouTube channel if you liked this post. I would love to hear from you what you are building. 🙂 Best, Benny

Top comments (0)