clinic.js Suite
Profile Node.js services with NearForm's clinic.js tools - Doctor for holistic health, Flame for CPU hotspots, and Bubbleprof for async I/O delays.
Search across all documentation pages
Profile Node.js services with NearForm's clinic.js tools - Doctor for holistic health, Flame for CPU hotspots, and Bubbleprof for async I/O delays.
Quick-reference recipe card - copy-paste ready.
npm install -g clinic
clinic doctor -- node dist/server.js
# In another terminal, generate load:
npx autocannon -c 50 -d 30 http://localhost:3000/health
# Press Ctrl+C on the server - Doctor opens an HTML reportWhen to reach for this:
await calls adding latency.# 1. Doctor - overall health (loop delay, CPU, memory)
clinic doctor -- node --import tsx src/server.ts
# 2. Flame - CPU flame graph (run under load, then stop)
clinic flame -- node --import tsx src/server.ts
# 3. Bubbleprof - async delay visualization
clinic bubbleprof -- node --import tsx src/server.ts// src/server.ts - minimal app to profile
import Fastify from "fastify";
const app = Fastify({ logger: false });
app.get("/health", async () => ({ ok: true }));
app.get("/users/:id", async (req) => {
// Simulate sequential I/O - Bubbleprof will highlight this
const profile = await fakeDb("profiles", req.params.id);
const orders = await fakeDb("orders", req.params.id);
return { profile, orders };
});
async function fakeDb(table: string, id: string) {
await new Promise((r) => setTimeout(r, 20));
return { table, id };
}
await app.listen({ port: 3000, host: "0.0.0.0" });What this demonstrates:
await chains that Promise.all could parallelize.--import tsx avoids a separate build step during investigation.| Tool | Best for | Not for |
|---|---|---|
| Doctor | First pass - "is the loop blocked?" | Pinpointing exact function names |
| Flame | CPU-bound hot paths, sync JSON, tight loops | Network-only latency (no CPU burn) |
| Bubbleprof | Slow downstream HTTP/DB, serial awaits | Pure CPU math without I/O |
# Terminal 1
clinic flame -- node dist/server.js
# Terminal 2 - sustain concurrency
npx autocannon -c 100 -d 60 http://localhost:3000/users/abc-c) and payload sizes.clinic doctor -- node --import tsx src/main.ts
# or compile first for closer-to-prod symbols:
npm run build && clinic flame -- node dist/main.jspino-pretty transport.LOG_LEVEL=warn.| Alternative | Use When | Don't Use When |
|---|---|---|
| clinic.js | Node HTTP services, event loop issues | Non-Node runtimes |
| 0x / flamegraph | Quick one-off CPU samples | Need async waterfall view |
| OpenTelemetry traces | Continuous production sampling | Deep V8-level CPU attribution |
--inspect + Chrome DevTools | Interactive debugging one request | Sustained load under concurrency |
No. Run in staging with anonymized data. Overhead is low but not zero; stop traffic to the profiled instance.
Run Flame under the same load. Search for wide bars in sync code: JSON.parse, bcrypt, *Sync fs calls.
Yes. Point clinic at your compiled main.js or node --import tsx src/main.ts. Disable Swagger and verbose logging during capture.
clinic.js is for deep, time-boxed investigations. OTel is for continuous traces and metrics in production. Use both.
Either load is too low or the bottleneck is outside Node (DB, network). Check Bubbleprof and DB EXPLAIN.
Yes. Install the latest clinic globally. Pin Node 24.18.0 to match production.
Possible but heavy. Prefer k6 thresholds for regression gates; reserve clinic for manual deep dives on failures.
Doctor shows rising heap over time. Pair with writeHeapSnapshot from Memory & GC Tuning.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026