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);
});
}
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');
})();
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
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`);
})();
businessLogic: 2.012s
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.`);
})();
Execution took 5 seconds.
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
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']});
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');
})();
๐ก Please note that our observer listens to entries of type 'measure'
.
Output:
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'
:
(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();
})();
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 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();
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);
});
}
businessLogic.test.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 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)