11 views
perf_hooks.monitorEventLoopDelay tells you something blocked. It will not tell you that JSON.parse on a 12MB webhook or a sync pbkdf2 in a rare branch is the culprit. Perfetto / Chrome DevTools timelines (and Node’s trace events) give you wall-clock stacks across JS and native frames so you can see the stall, not just the symptom histogram.
⚡ TL;DR: Keep ELD histograms in prod; in staging (and during SEVs) capture Chrome-format CPU + timeline traces around the failing request; hunt long tasks >16–50ms on the main thread; fix by moving CPU to workers or streaming parsers. Companion pieces: Node.js Event Loop Lag: Catch P99 Stalls and Node Flamegraphs on ECS.
Event Loop Blockers: production guidance
typescript
When p99 ELD spikes without matching upstream latency, you need a timeline.
// src/eld.ts — always-on cheap signal
import { monitorEventLoopDelay } from "node:perf_hooks";
const h = monitorEventLoopDelay({ resolution: 10 });
h.enable();
setInterval(() => {
metrics.gauge("node.eld_p99_ms", h.percentile(99) / 1e6);
metrics.gauge("node.eld_max_ms", h.max / 1e6);
h.reset();
}, 10_000);
bash
# ✅ Staging capture (Chrome DevTools protocol via inspector)
node --inspect=0.0.0.0:9229 dist/server.js
# Then: chrome://inspect → Performance → record during repro
# Or export trace events:
node --trace-event-categories=v8,node,node.async_hooks dist/server.js
Classic blockers that metrics mis-attribute
typescript
// ❌ Sync parse on giant payloads in the request path
app.post("/hook", (req, res) => {
const body = JSON.parse(req.rawBody.toString("utf8")); // 12MB stalls
res.end("ok");
});
// ✅ Streaming / size-capped parse
import { once } from "node:events";
app.post("/hook", async (req, res) => {
const chunks: Buffer[] = [];
let n = 0;
for await (const c of req) {
n += c.length;
if (n > 1_000_000) return res.status(413).end();
chunks.push(c);
}
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
res.end("ok");
});
typescript
// ❌ crypto.pbkdf2Sync in login
const hash = crypto.pbkdf2Sync(password, salt, 310000, 32, "sha256");
// ✅ async API — returns to the loop between libuv turns
const hash = await pbkdf2(password, salt, 310000, 32, "sha256");
Annotate traces with user marks
typescript
Open the trace in ui.perfetto.dev / Chrome Performance — long yellow tasks that sit on the main thread between import { performance } from "node:perf_hooks";
export function withSpan<T>(name: string, fn: () => T): T {
performance.mark(`${name}:start`);
try {
return fn();
} finally {
performance.mark(`${name}:end`);
performance.measure(name, `${name}:start`, `${name}:end`);
}
}
http.incoming and http.outgoing are your backlog.
Staging workflow that sticks
- Reproduce with a captured payload.
- Record 5–15s of Performance timeline + simultaneous ELD gauges.
- Sort long tasks; identify JS vs native (zlib, bcrypt, sharp).
- Move native CPU to
worker_threadsor async APIs; stream JSON. - Re-measure ELD p99 under the same load profile.
Closing checklist
✅ Dos– ✅ Keep ELD histograms in production
– ✅ Capture Perfetto/Chrome traces in staging on ELD regressions
– ✅ Cap sync parse sizes; prefer async crypto
– ✅ Offload heavy CPU to worker threads
– ✅ Attach deploy id to trace artifacts ❌ Don’ts
– ❌ Don’t rely on average latency alone to find blockers
– ❌ Don’t
*Sync crypto/fs on request paths– ❌ Don’t JSON.parse unbounded webhook bodies
– ❌ Don’t leave
--inspect open on public interfaces in prod– ❌ Don’t confuse GC pauses with your own sync JS without traces
Related reading
- Node.js Event Loop Lag: Catch P99 Stalls
- Node Flamegraphs on ECS: Continuous Profiling That Survives Scale
- Node Worker Threads: SharedArrayBuffer Protocols Without Data Races
- OpenTelemetry for LLMs: Trace Prompt Latency Across Microservices
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Node libuv Threadpool Tuning: Size UV_THREADPOOL_SIZE With Benchmarks - CheatCoders