Course contentsShow
← Node.js
Lesson 1909 of 1,98017. Performance Tuning and ProfilingFree lesson

Correlating Flame Graphs with Load Tests

You will perform load testing while profiling to see how execution paths change under pressure.

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":

  1. 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.
  2. Promise/Async Depth: Under load, the stack depth of asynchronous continuations often grows. If your flame graph shows a deep, narrow tower of processTicksAndRejections or promise resolutions, it suggests that your event loop is buried in job queue management rather than business logic.
  3. 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.parse or 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.