In this lesson, you will learn how to synchronize CPU profiling with active load testing to identify how your application’s "hot paths" shift when under heavy concurrency.
Up until now, you have analyzed flame graphs captured during idle or low-traffic states. However, an application’s performance profile often changes drastically under pressure. When the event loop becomes saturated, micro-tasks, garbage collection cycles, and lock contention in native modules can appear in the flame graph that were previously invisible.
The Correlation Workflow
To gain actionable insights, you must capture a profile during the peak of a load test, rather than before or after. Use a tool like autocannon to simulate traffic while simultaneously triggering the V8 CPU profiler.
# Start a load test for 30 seconds
autocannon -d 30 http://localhost:3000
# Meanwhile, trigger a 20-second CPU profile capture
node --inspect
# Use Chrome DevTools or node-inspect to record the profile during the test
Analyzing Shifts in Execution
When comparing a flame graph captured at 10 requests per second (RPS) versus 1,000 RPS, look for these specific "pressure markers":
- Increased Garbage Collection Width: You may see the
(garbage collector)bar widen significantly, indicating that the allocator is struggling with the high object churn generated by your request handlers. - Promise/Async Depth: Under load, the stack depth of asynchronous continuations often grows. If your flame graph shows a deep, narrow tower of
processTicksAndRejectionsor promise resolutions, it suggests that your event loop is buried in job queue management rather than business logic. - Hot Path Bottlenecks: A function that looks efficient at low load might suddenly dominate the width of the graph under stress. This usually reveals a synchronization bottleneck—perhaps a synchronous
JSON.parseor a heavy regex operation that, when queued across hundreds of concurrent requests, creates a cumulative latency spike that doesn't show up in isolated unit tests.
By overlaying these profiles, you move from "guessing where it's slow" to "observing where the CPU is spent when it actually matters."
Key Takeaway: A flame graph is a snapshot; correlate it with a sustained load test to distinguish between minor background work and the actual performance bottlenecks that emerge under production-scale pressure.