Profiling Node.js Apps
A profiler measures where your program actually spends its time, so you fix the real bottleneck instead of guessing. Node has profiling built into V8: a sampling CPU profiler that records which functions are on the stack thousands of times per second, plus tooling to render the result as a flame graph. This page covers the quick --prof flag, the richer --cpu-prof that feeds Chrome DevTools, reading flame graphs, the 0x tool, and the difference between sampling and instrumentation profilers.
The quickest profile: --prof
# 1. Run with the V8 profiler — writes isolate-*.log (raw tick data): node --prof app.js # 2. Process the log into a human-readable summary: node --prof-process isolate-*.log > profile.txt
[Summary]:
ticks total nonlib name
8412 72.1% 91.3% JavaScript
520 4.5% 5.6% C++
2901 24.9% GC
[Bottom up (heavy) profile]:
ticks parent name
6210 53.2% LazyCompile: *hashPassword bcrypt.js:42
1804 15.5% LazyCompile: *parseBody body-parser.js:88Richer profiles for DevTools: --cpu-prof
# Writes a .cpuprofile you can load into Chrome DevTools' Performance panel: node --cpu-prof --cpu-prof-dir=./profiles app.js # → ./profiles/CPU.20260621.<pid>.cpuprofile (drag into DevTools → Performance)
Profile only a specific section programmatically
import inspector from 'node:inspector'
import fs from 'node:fs'
const session = new inspector.Session()
session.connect()
session.post('Profiler.enable', () => {
session.post('Profiler.start', () => {
doExpensiveWork() // the code you want to profile
session.post('Profiler.stop', (err, { profile }) => {
fs.writeFileSync('section.cpuprofile', JSON.stringify(profile))
})
})
})Reading a flame graph
┌──────────────────────────────────────────────┐
width = │ handleRequest (100%) │
time ├───────────────────────┬──────────────────────┤
spent │ queryDatabase (30%) │ renderResponse (65%) │ ← widest = hottest
├──────────┬────────────┤ ┌───────────────────┤
stack │ parse(8) │ serialize │ │ hashPassword (60%)│ ← the real cost
depth ▼ └──────────┴────────────┘ └───────────────────┘
(y-axis = call stack; x-axis = time, NOT chronological order)Flame graphs with 0x
npx 0x app.js # Exercise the app (run load against it), then Ctrl-C. # 0x generates an interactive flame-graph HTML file and opens it. # Or against a load test: npx 0x -- node app.js # then run autocannon/k6 at it, stop, view the graph
Sampling vs instrumentation profilers
Sampling (V8, | Instrumentation | |
|---|---|---|
How | Periodically records the call stack | Wraps every function with timing hooks |
Overhead | Low — safe for near-production loads | High — can distort the very timings it measures |
Accuracy | Statistical — misses very short, rare calls | Exact call counts and durations |
Best for | Finding hot paths in real workloads | Precise call-count analysis of a small area |