Performance Profiling
Find the real bottleneck with perf_hooks, flame graphs, and event-loop lag — not guesses.
The first rule of performance work: measure before you optimize. Node ships enough tooling to find the actual hot path instead of guessing.
The tools, from quick to deep
perf_hooks—performance.now(), marks/measures, andPerformanceObserverfor precise in-code timing.--profthen--prof-process— the built-in V8 tick profiler; zero deps, produces a text breakdown.- Flame graphs —
node --cpu-profwrites a.cpuprofileyou open in Chrome DevTools or VS Code; the clinic.js suite (clinic flame,clinic doctor) makes this turnkey. - Event-loop lag —
monitorEventLoopDelay()tells you when the loop is blocked, the single most useful production health metric for Node.
const { performance } = require('node:perf_hooks');
const t0 = performance.now();
expensive();
console.log(`took ${(performance.now() - t0).toFixed(1)}ms`);Blocked loop = the usual culprit
Most Node latency problems are not slow I/O — they are a synchronous CPU spike (a big JSON.parse, a regex, a sync crypto call) blocking the loop so every in-flight request stalls. Event-loop delay percentiles catch this where average response time hides it.
Example
// A lightweight, always-on performance harness: event-loop delay histogram
// plus GC observation. Ship something like this and you will SEE the spike.
const { monitorEventLoopDelay, PerformanceObserver, constants } = require('node:perf_hooks');
// 1) Event-loop lag histogram — the vital sign of a Node service.
const loop = monitorEventLoopDelay({ resolution: 10 });
loop.enable();
setInterval(() => {
const ms = (ns) => (ns / 1e6).toFixed(1);
console.log(`loop delay p50=${ms(loop.percentile(50))}ms p99=${ms(loop.percentile(99))}ms max=${ms(loop.max)}ms`);
loop.reset();
}, 5000).unref();
// 2) Observe GC pauses — long ones correlate with latency cliffs.
const gcObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn(`long GC: ${entry.duration.toFixed(1)}ms kind=${entry.detail?.kind}`);
}
}
});
gcObserver.observe({ entryTypes: ['gc'] });
// Simulate a blocking spike to see p99 lag jump in the next report.
setTimeout(() => {
const end = Date.now() + 300;
while (Date.now() < end) {} // synchronous stall: DON'T do this in real code
}, 2000);When to use it
- An SRE uses `clinic.js flame` to generate a flame graph of a Node.js API under load and identifies a hot path in JSON serialization taking 40% of CPU.
- A developer uses `node --prof` and `node --prof-process` to find that a regular-expression in a middleware is O(n^2) on long strings.
- A memory-leak investigation uses `heapdump` to capture two snapshots -- one before and one after a leak -- and diffs them in Chrome DevTools to find the growing object.
More examples
CPU profiling with --prof
Uses Node's built-in V8 profiler to collect CPU ticks and `--prof-process` to find the hottest functions.
# 1. Run app with V8 profiler
node --prof server.js
# (send traffic, then Ctrl+C)
# 2. Process the generated isolate-*.log
node --prof-process isolate-*.log > profile.txt
# 3. Look for high-tick functions
grep -A5 '\[Bottom up\]' profile.txt | head -20Heap snapshot for memory leak
Uses `v8.writeHeapSnapshot()` to dump the current heap to a file that can be analyzed in Chrome DevTools.
const v8 = require('v8');
const fs = require('fs');
// Take a heap snapshot and write it to disk
const snap = v8.writeHeapSnapshot();
console.log('Snapshot written:', snap);
// Open the .heapsnapshot file in Chrome DevTools -> MemoryMeasure async event-loop lag
Monitors event-loop delay with a histogram and logs min/mean/p99 latency every second to detect CPU-blocking code.
const { monitorEventLoopDelay } = require('perf_hooks');
const histogram = monitorEventLoopDelay({ resolution: 10 });
histogram.enable();
setInterval(() => {
console.log({
min: (histogram.min / 1e6).toFixed(2) + 'ms',
mean: (histogram.mean / 1e6).toFixed(2) + 'ms',
p99: (histogram.percentile(99) / 1e6).toFixed(2) + 'ms',
});
histogram.reset();
}, 1000);
Discussion