bugl
bugl
HomeLearnPatternsSearch
HomeLearnPatternsSearch

Loading lesson path

Learn/Node.js/Node.js Advanced
Node.js•Node.js Advanced

Node.js Performance Hooks Module

What are Performance Hooks?

The perf_hooks module provides a set of APIs for performance measurement based on the W3C Performance Timeline specification.

These tools are essential for:

Measuring the time taken by specific operations

Finding performance bottlenecks

Comparing the performance of different implementations

Tracking application performance over time

The module includes several useful features such as high-resolution timers, performance marks, measures, observers, and histograms.

Using the Performance Hooks Module

To use the Performance Hooks module, you need to require it in your code:

// Import the entire module const { performance, PerformanceObserver } = require('perf_hooks');
// Or using destructuring for specific parts const { performance } = require('perf_hooks');

Basic Time Measurement

The most basic use of the performance API is to measure elapsed time with high precision:

const { performance } = require('perf_hooks');
// Get the current high-resolution time const startTime = performance.now();
// Perform some operation let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
// Get the end time const endTime = performance.now();
// Calculate and display the elapsed time in milliseconds console.log(`Operation took ${(endTime - startTime).toFixed(2)} milliseconds`);
The performance.now()

Formula

method returns a high - resolution timestamp in milliseconds, measured from the time the current Node.js process started.

Performance Marks and Measures

Marks

Performance marks are specific points in time that you want to track:

const { performance } = require('perf_hooks');
// Create marks at specific points in your code performance.mark('startProcess');
// Simulate some work let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i);
}
// Create another mark performance.mark('endProcess');
// Get all the marks console.log(performance.getEntriesByType('mark'));

Measures

Performance measures calculate the time duration between two marks:

const { performance } = require('perf_hooks');
// Create a start mark performance.mark('start');
// Simulate some work let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i);
}
// Create an end mark performance.mark('end');
// Create a measure between the two marks performance.measure('processTime', 'start', 'end');
// Get the measure const measure = performance.getEntriesByName('processTime')[0];
console.log(`Process took ${measure.duration.toFixed(2)} milliseconds`);
// Clear marks and measures performance.clearMarks();
performance.clearMeasures();

Performance Observer

The

PerformanceObserver allows you to observe performance events asynchronously:

const { performance, PerformanceObserver } = require('perf_hooks');
// Create a performance observer const obs = new PerformanceObserver((items) => {
// Process all entries const entries = items.getEntries();
entries.forEach((entry) => {
console.log(`Name: ${entry.name}, Type: ${entry.entryType}, Duration: ${entry.duration.toFixed(2)}ms`);
});
});
// Subscribe to specific entry types obs.observe({ entryTypes: ['measure'] });
// First task performance.mark('task1Start');
// Simulate work setTimeout(() => {
performance.mark('task1End');
performance.measure('Task 1', 'task1Start', 'task1End');
// Second task performance.mark('task2Start');
setTimeout(() => {
performance.mark('task2End');
performance.measure('Task 2', 'task2Start', 'task2End');
// Clean up performance.clearMarks();
performance.clearMeasures();
obs.disconnect();
}, 1000);
}, 1000);

Performance Timeline API

The Performance Timeline API provides methods to retrieve performance entries:

const { performance } = require('perf_hooks');
// Create some performance entries performance.mark('mark1');
performance.mark('mark2');
let sum = 0;
for (let i = 0; i < 100000; i++) {
sum += i;
}
performance.mark('mark3');
performance.measure('measure1', 'mark1', 'mark2');
performance.measure('measure2', 'mark2', 'mark3');
// Get all performance entries console.log('All entries:');
console.log(performance.getEntries());
// Get entries by type console.log('\nMarks:');
console.log(performance.getEntriesByType('mark'));
// Get entries by name console.log('\nMeasure 1:');
console.log(performance.getEntriesByName('measure1'));

Performance Timing Levels

Node.js provides different performance timing APIs with varying levels of precision:

const { performance, monitorEventLoopDelay } = require('perf_hooks');
// 1. Date.now() - millisecond precision const dateStart = Date.now();
const dateEnd = Date.now();
console.log(`Date.now() difference: ${dateEnd - dateStart}ms`);
// 2. process.hrtime() - nanosecond precision const hrStart = process.hrtime();
const hrEnd = process.hrtime(hrStart);
console.log(`process.hrtime() difference: ${hrEnd[0]}s ${hrEnd[1]}ns`);
// 3. performance.now() - microsecond precision const perfStart = performance.now();
const perfEnd = performance.now();
console.log(`performance.now() difference: ${(perfEnd - perfStart).toFixed(6)}ms`);
// 4. Event loop delay monitoring (available in Node.js 12.0.0+)
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();
setTimeout(() => {
histogram.disable();
console.log('Event loop delay metrics:');
console.log(`  Min: ${histogram.min}ns`);
console.log(`  Max: ${histogram.max}ns`);
console.log(`  Mean: ${histogram.mean.toFixed(2)}ns`);
console.log(`  Stddev: ${histogram.stddev.toFixed(2)}ns`);
console.log(`  Percentiles: 50=${histogram.percentile(50).toFixed(2)}ns, 99=${histogram.percentile(99).toFixed(2)}ns`);
}, 1000);

Event Loop Monitoring

The monitorEventLoopDelay function provides a way to monitor the delay in the event loop:

const { monitorEventLoopDelay } = require('perf_hooks');

Formula

// Create a histogram const histogram = monitorEventLoopDelay({ resolution: 10 });
// Enable monitoring histogram.enable();
// Simulate load on the event loop const operations = [];
for (let i = 0; i < 10; i++) {
operations.push(new Promise((resolve) => {
setTimeout(() => {
// Simulate CPU-intensive work let sum = 0;
for (let j = 0; j < 10000000; j++) {
sum += j;
}
resolve(sum);
}, 100);
}));
}

// After all operations complete

Promise.all(operations).then(() => {
// Disable monitoring histogram.disable();
// Print statistics console.log('Event Loop Delay Statistics:');
console.log(`  Min: ${histogram.min}ns`);
console.log(`  Max: ${histogram.max}ns`);
console.log(`  Mean: ${histogram.mean.toFixed(2)}ns`);
console.log(`  Stddev: ${histogram.stddev.toFixed(2)}ns`);
// Percentiles console.log('\nPercentiles:');
[1, 10, 50, 90, 99, 99.9].forEach((p) => {
console.log(`  p${p}: ${histogram.percentile(p).toFixed(2)}ns`);
});
});

Event loop monitoring is particularly useful for detecting when your application might be experiencing issues with responsiveness due to long-running tasks blocking the event loop.

Performance Tracking in Async Operations

Tracking performance in asynchronous operations requires careful mark placement:

const { performance, PerformanceObserver } = require('perf_hooks');
const fs = require('fs');
// Create observer for the measures const obs = new PerformanceObserver((items) => {
items.getEntries().forEach((entry) => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['measure'] });
// Measure async file read operation performance.mark('readStart');
fs.readFile(__filename, (err, data) => {
if (err) throw err;
performance.mark('readEnd');
performance.measure('File Read', 'readStart', 'readEnd');
// Measure async processing time performance.mark('processStart');
// Simulate processing the file data setTimeout(() => {
const lines = data.toString().split('\n').length;
performance.mark('processEnd');
performance.measure('File Processing', 'processStart', 'processEnd');
console.log(`File has ${lines} lines`);
// Clean up performance.clearMarks();
performance.clearMeasures();
}, 100);
});

Tracking Promises

Measuring the performance of promises requires similar techniques:

const { performance, PerformanceObserver } = require('perf_hooks');
// Set up the observer const obs = new PerformanceObserver((items) => {
items.getEntries().forEach((entry) => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['measure'] });
// Function that returns a promise function fetchData(delay) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ data: 'Sample data' });
}, delay);
});
}
// Function to process data function processData(data) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ processed: data.data.toUpperCase() });
}, 200);
});
}
// Measure Promise chain async function run() {
performance.mark('fetchStart');
const data = await fetchData(300);
performance.mark('fetchEnd');
performance.mark('processStart');
const processed = await processData(data);
performance.mark('processEnd');
// Create measures performance.measure('Fetch Data', 'fetchStart', 'fetchEnd');
performance.measure('Process Data', 'processStart', 'processEnd');
performance.measure('Total Operation', 'fetchStart', 'processEnd');
console.log('Result:', processed);
}
run().finally(() => {
// Clear after execution performance.clearMarks();
performance.clearMeasures();
});

Performance Timing Caveats

When using performance APIs, be aware of certain caveats:

Timing resolution varies between platforms

Clock drift can occur in long-running processes

Background activity can affect timing measurements

JavaScript JIT compilation can cause inconsistent first-run times const { performance } = require('perf_hooks');
// For accurate benchmarking, perform multiple runs function benchmark(fn, iterations = 1000) {

Formula

// Warm - up run (for JIT optimization)
fn();
const times = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
fn();
const end = performance.now();
times.push(end - start);
}
// Calculate statistics times.sort((a, b) => a - b);
const sum = times.reduce((a, b) => a + b, 0);
const avg = sum / times.length;
const median = times[Math.floor(times.length / 2)];
const min = times[0];
const max = times[times.length - 1];
return {

average: avg, median: median, min: min, max: max, samples: times.length

};
}
// Example usage function testFunction() {
// Function to benchmark let x = 0;
for (let i = 0; i < 10000; i++) {
x += i;
}
return x;
}
const results = benchmark(testFunction);
console.log('Benchmark Results:');
console.log(`  Samples: ${results.samples}`);
console.log(`  Average: ${results.average.toFixed(4)}ms`);
console.log(`  Median: ${results.median.toFixed(4)}ms`);
console.log(`  Min: ${results.min.toFixed(4)}ms`);
console.log(`  Max: ${results.max.toFixed(4)}ms`);

NodeJS Performance Hooks vs Browser Performance API

The Node.js Performance Hooks API is based on the W3C Performance Timeline specification, but there are some differences compared to the browser's Performance API:

Feature

Previous

Node.js HTTP/2 Module

Next

Node.js VM Module