C.W.K.
Stream
Lesson 02 of 05 · published

Performance Profiling — Where Time Actually Goes

~12 min · production, profiling, performance, inspect

Level 0Node Curious
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Without measurement, every optimization is a guess. Most Node performance issues are not where your intuition expects."

The Three Things You Profile

  1. CPU — where the JS spends time. Spotting head-of-line blocking, hot functions, sync work that should be async.
  2. Memory — what's allocated, what's retained, who's leaking. Memory leaks cause restarts; high allocation rates cause GC pauses.
  3. I/O / Event loop — what's blocking the loop. Spotting microtask starvation, slow file/DB calls, libuv thread pool saturation.

The tools differ for each. The instinct that ties them: always measure before you optimize. The slow path is almost never where you'd guess.

--inspect — Chrome DevTools for Node

Run Node with --inspect:

node --inspect server.mjs
# Listening for the inspector on 127.0.0.1:9229

Open chrome://inspect in Chrome. Click inspect. You get the full DevTools UI: Performance recorder, Memory profiler, heap snapshots, the works. It's the same tool you use on web pages, pointed at your Node process.

  • Performance tab — start recording, run your slow operation, stop. Get a flame graph of where time went, function by function.
  • Memory tab — take a heap snapshot, run for a while, take another. Diff to find what was retained.
  • Profiler tab — sampling CPU profile, lower overhead than the Performance tab's tracing.

clinic.js — Diagnose Without Touching DevTools

NearForm's clinic.js bundles four diagnostic tools as one CLI:
  • clinic doctor — runs your app under load, identifies the likely bottleneck category (CPU, I/O, GC, event loop).
  • clinic flame — CPU flame graph, no DevTools needed.
  • clinic bubbleprof — async operation timeline. Shows what your async tasks were doing, in what order.
  • clinic heapprofiler — memory allocation flame graph.
npx clinic doctor -- node server.mjs runs the doctor while a load tool (clinic comes with autocannon for HTTP) hammers your endpoints. The output is an HTML report you open in a browser. For 80% of "my server is slow" investigations, doctor's diagnosis is enough to point at the problem.

What You'll Actually Find

Common Node performance bugs, in order of frequency:

  1. Sync work on the main thread — heavy JSON parsing, sync crypto, regex on huge strings, nested loops on large arrays. The p99/p50 gap (Track 1) is the signature. Move to worker_threads.
  2. Database queries that should be one query — N+1 patterns where each item triggers another query. Diagnose with DB logs; fix by joining or batching.
  3. libuv thread pool saturation — too many concurrent fs/dns/crypto operations. Default pool is 4 threads; bump with UV_THREADPOOL_SIZE.
  4. Memory leaks via global state or unbounded caches — heap snapshots showing growth between successive samples. Fix by adding TTLs or LRU bounds.
  5. Excessive object allocation — creating millions of short-lived objects in a hot loop causes GC pauses. Refactor to reuse objects or use typed arrays.

Measuring in Production

Profiling locally is fine; the real performance picture comes from production. Modern Node ships node:perf_hooks and integrates with OpenTelemetry. Wire your service to export traces to Tempo, Jaeger, or any OTLP-compatible backend. Spot the slow span; profile it locally; fix it. The two-step: production observability tells you WHAT is slow; local profiling tells you WHY.

Pippa's Confession

When cwkPippa's WebUI started feeling laggy on long conversations, my first instinct was "frontend is slow." Dad insisted on measurement. Profiling showed the backend was sending the entire conversation history on every reply — 100ms+ of JSON serialization per turn. The frontend wasn't slow; the backend was sending too much. The fix was incremental message streaming. "You wasted hours assuming. Two minutes of profiling would have shown it." That's the rule now: instrument, then act.

Code

Built-in profiling flags (no clinic needed)·bash
# Quick profiling commands

# Chrome DevTools attach
node --inspect server.mjs
# Open chrome://inspect, click 'inspect', use Performance/Memory tabs

# Break on first line (for debugging startup)
node --inspect-brk server.mjs

# CPU profile from CLI, output for later analysis
node --cpu-prof --cpu-prof-dir=./profiles server.mjs
# Generates ./profiles/CPU.<timestamp>.cpuprofile
# Open in Chrome DevTools: Performance tab → load profile

# Heap snapshot from CLI
node --heap-prof server.mjs
# Generates ./Heap.<timestamp>.heapprofile
clinic.js — production-grade Node diagnostics·bash
# clinic.js — guided diagnostics
npm install -g clinic

# Doctor diagnoses the bottleneck category
clinic doctor --on-port 'autocannon -d 30 localhost:3000' -- node server.mjs

# Flame graph for CPU hotspots
clinic flame --on-port 'autocannon -d 30 localhost:3000' -- node server.mjs

# Bubble profile for async operation timeline
clinic bubbleprof --on-port 'autocannon -d 30 localhost:3000' -- node server.mjs

# Each outputs an HTML report in .clinic/, opened automatically.

External links

Exercise

Take a Node service you have (or write a tiny one with a slow endpoint). Run clinic doctor -- node server.mjs while hitting it with autocannon -d 30 http://localhost:3000. Read the HTML report. What was the bottleneck? Now use clinic flame to find the actual hot function. The diagnosis pipeline (doctor → flame → fix) is the most repeatable Node performance methodology in 2026.
Hint
If doctor says 'I/O,' the slow part is outside Node (DB, network). If it says 'CPU,' look at the flame graph for the function eating time. If it says 'event loop,' you have microtask starvation or sync work; bubbleprof helps. If it says 'GC,' you're allocating too much; heap profiler shows what.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.