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

Visualizing CPU Usage of Event Loop Phases

You will map CPU activity to specific event loop lifecycle states.

In this lesson, you will learn how to identify which phase of the Node.js event loop is consuming the most CPU by correlating flame graph patterns with specific event loop lifecycle states.

Mapping Event Loop Activity

When analyzing a CPU profile, a flame graph doesn't explicitly label "Timers" or "Poll" phases. Instead, you must map the execution stacks you see to the event loop's internal structure.

The event loop processes tasks in a specific order: Timers, Pending Callbacks, Idle/Prepare, Poll, Check, and Close Callbacks. By inspecting the "roots" of your flame graph stacks, you can deduce which phase is dominating your CPU time:

  • Timers Phase: Look for setTimeout or setInterval callbacks at the base of your stacks. If you see high "Self Time" within functions initiated by timer handles, your loop is spending excessive time processing scheduled tasks.
  • Poll Phase: This is where I/O callbacks live. If your flame graph shows stacks originating from net.js or fs.js (e.g., onread, emit), your CPU usage is dominated by handling incoming requests or reading data.
  • Check Phase: This is the home of setImmediate. If your graph is filled with stacks originating from processImmediate, you are likely over-utilizing the check phase, perhaps to defer expensive work.

Practical Identification

Consider this snippet:

// High CPU load in the Poll Phase
http.createServer((req, res) => {
  // Heavy synchronous processing here
  doExpensiveWork(); 
  res.end();
}).listen(8080);

If you profile this under load, your flame graph will show a wide "plateau" starting from the HTTP parser’s request handling. Because the request handler is part of the Poll phase, the wide bars represent the CPU time spent in that specific state. If you were instead using setImmediate to process chunks of work, those stacks would appear under the Check phase root, appearing as separate, discrete blocks of CPU activity later in the loop cycle.

By identifying whether your "hot" stacks begin with timer callbacks, I/O events, or immediate queues, you can pinpoint exactly which part of your application’s architecture is causing the Event Loop to block.

Key Takeaway: Correlating the "roots" of flame graph stacks to known Node.js lifecycle entry points allows you to treat the event loop as a predictable series of CPU-bound phases.