Node.js Performance Tracking with Mark and Measure
Node.js provides robust, built-in tools for monitoring application
performance through the Performance Measurement APIs, specifically using
performance.mark() and performance.measure().
This article explores how these APIs work, demonstrating how to pinpoint
execution bottlenecks by establishing high-resolution timestamps,
calculating the exact duration between specific application events, and
retrieving these metrics using performance observers.
Understanding perf_hooks
Node.js exposes its performance measurement capabilities through the
native perf_hooks module. This module implements the W3C
User Timing Level 2 specification, allowing developers to collect
high-resolution time metrics that are consistent with browser-based
performance tooling.
To use these APIs, you first need to import the
performance object:
const { performance } = require('perf_hooks');How performance.mark() Works
The performance.mark() method creates a local timestamp
in the performance timeline. This timestamp is highly accurate,
utilizing DOMHighResTimeStamp which measures time in
milliseconds with microsecond precision.
When you call performance.mark('mark-name'), Node.js
records the exact time that line of code executed. You can place these
marks before and after complex database queries, API calls, or heavy CPU
operations to establish boundaries.
performance.mark('start-database-query');
// ... database operation occurs ...
performance.mark('end-database-query');How performance.measure() Works
Once you have established start and end marks, you can calculate the
elapsed time between them using performance.measure().
The performance.measure(measureName, startMark, endMark)
method calculates the duration between the two specified marks, creates
a new entry in the performance timeline, and stores the result.
performance.measure(
'Database Query Duration',
'start-database-query',
'end-database-query'
);If you omit the start and end marks,
performance.measure() will calculate the time elapsed from
the very start of the Node.js process to the moment the measure function
is called.
Retrieving Performance Metrics
Creating marks and measures is only useful if you can retrieve and
analyze the data. Node.js provides two main ways to access these
measurements: direct retrieval and the PerformanceObserver
API.
1. Using PerformanceObserver (Recommended)
The PerformanceObserver class allows you to subscribe to
the performance timeline asynchronously. Whenever a new measure is
created, the observer’s callback is triggered. This is the most
efficient way to collect metrics without blocking the main event
loop.
const { performance, PerformanceObserver } = require('perf_hooks');
// Initialize the observer
const obs = new PerformanceObserver((items) => {
items.getEntries().forEach((entry) => {
console.log(`Name: ${entry.name}`);
console.log(`Duration: ${entry.duration.toFixed(4)} ms`);
console.log(`Start Time: ${entry.startTime}`);
});
});
// Observe 'measure' entry types
obs.observe({ entryTypes: ['measure'], buffered: true });
// Run the monitored operation
performance.mark('start-process');
setTimeout(() => {
performance.mark('end-process');
performance.measure('Total Process Time', 'start-process', 'end-process');
}, 500);2. Querying the Performance Timeline
Alternatively, you can retrieve entries synchronously using the
performance.getEntriesByName() or
performance.getEntriesByType() methods.
// Retrieve all measures
const measures = performance.getEntriesByType('measure');
// Retrieve a specific measure by name
const databaseMeasures = performance.getEntriesByName('Database Query Duration');Clearing the Timeline
To prevent memory leaks in long-running Node.js processes, it is important to clean up the performance timeline once you have processed the metrics. You can clear specific marks and measures using:
performance.clearMarks('mark-name')(or call without arguments to clear all marks)performance.clearMeasures('measure-name')(or call without arguments to clear all measures)