Node.js Performance Profiling
CPU Profiling with clinic.js
# Install clinic.js
npm install -g clinic
# CPU profiler (flame graphs)
clinic flame -- node server.js
# Doctor (event loop, memory, CPU overview)
clinic doctor -- node server.js
# Bubbleprof (async bottlenecks)
clinic bubbleprof -- node server.js
Built-in Node.js Profiler
# Start with profiling enabled
node --prof server.js
# Run load test
npx autocannon http://localhost:3000/api/heavy -d 10
# Process profiling data
node --prof-process isolate-0x*.log > profile.txt
# View flame chart
node --prof-process --preprocess isolate-0x*.log | flamebearer
Memory Leak Detection
// Detect memory growth
const startMemory = process.memoryUsage();
setInterval(() => {
const mem = process.memoryUsage();
const heapGrowth = mem.heapUsed - startMemory.heapUsed;
if (heapGrowth > 100 * 1024 * 1024) { // 100MB growth
console.warn('Potential memory leak detected', {
heapUsed: Math.round(mem.heapUsed / 1024 / 1024) + 'MB',
heapTotal: Math.round(mem.heapTotal / 1024 / 1024) + 'MB',
rss: Math.round(mem.rss / 1024 / 1024) + 'MB',
});
}
}, 30000);
# Heap snapshot with node-inspector
node --inspect server.js
# Open Chrome DevTools -> Memory -> Take Heap Snapshot
# Compare snapshots before and after suspected leak
# heapdump programmatically
npm install heapdump
import heapdump from 'heapdump';
process.on('SIGUSR2', () => {
const filename = `/tmp/heapdump-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename, (err, filename) => {
if (!err) console.log('Heap snapshot saved:', filename);
});
});
// Trigger: kill -SIGUSR2 <pid>
Event Loop Monitoring
import { monitorEventLoopDelay } from 'perf_hooks';
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
const mean = h.mean / 1e6; // Convert to ms
const p99 = h.percentile(99) / 1e6;
if (p99 > 100) {
console.warn(`High event loop delay: mean=${mean.toFixed(1)}ms, p99=${p99.toFixed(1)}ms`);
}
h.reset();
}, 5000);
Common Performance Issues
// Problem: Synchronous operations blocking event loop
import fs from 'fs';
// Bad: Blocks event loop
const data = fs.readFileSync('/large-file.json');
// Good: Non-blocking
const data = await fs.promises.readFile('/large-file.json');
// Problem: Parsing large JSON synchronously
// Bad: Blocks for large objects
const parsed = JSON.parse(largeJsonString);
// Good: Use streams for large data
import { createReadStream } from 'fs';
import { createInterface } from 'readline';
const rl = createInterface({ input: createReadStream('data.ndjson') });
for await (const line of rl) {
const item = JSON.parse(line);
await processItem(item);
}
// Problem: CPU-intensive tasks on main thread
// Use worker_threads for CPU work
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
if (!isMainThread) {
const result = heavyComputation(workerData.input);
parentPort!.postMessage(result);
}
async function runInWorker(input: unknown): Promise<unknown> {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData: { input } });
worker.on('message', resolve);
worker.on('error', reject);
});
}
Benchmarking with autocannon
# Basic benchmark
npx autocannon http://localhost:3000/api/users -d 10 -c 100
# Options:
# -d 10 duration 10 seconds
# -c 100 100 connections
# -p 10 10 pipelining requests
# Compare before/after optimization
npx autocannon http://localhost:3000/api/users -d 30 -c 50 --json > before.json
# Make optimization
npx autocannon http://localhost:3000/api/users -d 30 -c 50 --json > after.json
Profile before optimizing - most performance issues are in 10% of the code.