Advanced CPU Profiling in Node - Best Practices and Pitfalls
Table of Contents

CPU profiling is a powerful but intricate part of optimizing Node.js application performance. While it is easy to get started with the basics, using profiling effectively requires a deeper understanding of the available tools, how they work, and what the results mean. This article introduces advanced CPU profiling techniques using Node.js’s built-in tools. You will learn how to create CPU profiles, troubleshoot common issues, manage and interpret profiling data, and adjust profiling arguments for different scenarios. With practical examples and detailed explanations throughout, you will gain the confidence you need to analyze and improve your application's performance.
How to create a CPU Profile
To create a CPU profile, you can use the --cpu-prof
.cpuprofile
Official docs: Node.js CPU Profiling
You can start CPU profiling in different ways
Direct command line flags
Using NODE_OPTIONS (Node.js v23.x+)
☝️ Note
The order of CPU profiling arguments is critical!
❌ Bad:
✅ Good:
Troubleshooting
What, of the many profiles, is the main process?
Based on the file naming conventions and the nature of process ID assignment, the main process always has the smallest Process ID (PID), and a Thread ID (TID) is 0 and appears at the end of an alphabetically sorted folder. The main process is the last file in the folder.
My profile files are appearing in different places in the file system based on the cwd of every process
This is because of the way Node.js handles the --cpu-prof
--cpu-prof-dir
Now all of them are in one place:
Error: --cpu-prof is not allowed in NODE_OPTIONS
--cpu-prof is not allowed in NODE_OPTIONS
Node.js maintains a security whitelist for NODE_OPTIONS
Version Support
≤ v22.x: All CPU profiling flags blocked in
NODE_OPTIONS
≥ v23.x: All CPU profiling flags allowed (PR #57018)
Test your Node.js version support
Workarounds for v22.x and earlier
CPU Profile Filename
Date and Time
The date and time in the filename represent the wall-clock write time, which is when the profile was flushed. This timestamp generally corresponds to the process creation time, not the moment the file was written to the file system.
Process and Thread IDs
The CPU profile filename includes both a Process ID (PID) and a Thread ID (TID). Understanding how these IDs are generated is crucial for interpreting profile files, especially in applications involving multiple processes or worker threads.
A Node.js application starts as a single process. This main process can then spawn child processes or create worker threads. Each new process and worker thread is assigned a distinct ID, which is reflected in the CPU profile filenames. Node.js provides built-in mechanisms for accessing these identifiers. For instance, process.pid
worker_threads.threadId
threadId
The following script demonstrates how to retrieve these values:
Output:
What Determines the Process ID (PID)?
The PID represents the OS-level process ID.
A new PID is generated each time a new process is created. Common ways to create new processes in Node.js include:
child_process.fork()
child_process.spawn()
child_process.exec()
Directly running the
executable (e.g., starting a new script).node
Consider the following script that spawns two child processes:
Output (order of spawn children may vary slightly due to asynchronous nature):
☝️ Note: The process ID
is assigned incrementally. The initial process is always the smallest PID and TID is 0. process.pid
If --cpu-prof
node --cpu-prof script.js
(Parent process)CPU.<timestamp>.51430.0.001.cpuprofile
(First child process)CPU.<timestamp>.51431.0.002.cpuprofile
(Second child process)CPU.<timestamp>.51432.0.003.cpuprofile
What Determines the Thread ID (TID)?
The TID represents V8's internal thread identifier.
By default, Node.js applications run in a single main thread, which typically has a TID of 0
Worker
node:worker_threads
The following script creates two worker threads:
Output (order of worker messages may vary):
If --cpu-prof
(Main thread)CPU.<timestamp>.51430.0.001.cpuprofile
(Worker 1)CPU.<timestamp>.51430.1.002.cpuprofile
(Worker 2)CPU.<timestamp>.51430.2.003.cpuprofile
Sequence number
The sequence number (.001
.002
CPU Profiling Arguments
☝️ Note: The "Added in" versions indicate when these flags were introduced. Some flags were initially experimental. Always consult the official Node.js documentation for the most current information on their status and behavior in your Node.js version.
Flag | Added in | Default | Description |
| v12.0.0 (Exp) | off | Starts the V8 CPU profiler on startup and writes a .cpuprofile on exit. |
| v12.0.0 (Exp) | Current working directory. If | Directory where |
| v12.0.0 (Exp) |
| Filename to use for the CPU profile. |
| v12.2.0 (Exp) |
| Sampling interval in microseconds for the CPU profiler. |
--cpu-prof
--cpu-prof
Purpose: Starts the V8 CPU profiler on application startup. When the Node.js process exits, a
file containing the profiling data is written..cpuprofile
Default:
off
Usage Example:
This command will profile and generate a profile file (e.g., CPU.<timestamp>.<pid>.<tid>.<sequence>.cpuprofile
--cpu-prof-dir
--cpu-prof-dir
Purpose: Specifies the directory where CPU profile files generated by
will be saved.--cpu-prof
Default: The current working directory. If
is set, it defaults to that directory instead.--diagnostic-dir
Usage Example:
This will save the CPU profile into the ./profiles
Notes:
If
specifies an absolute path,--cpu-prof-name
is ignored.--cpu-prof-dir
Ensure the specified directory is writable by the Node.js process.
--cpu-prof-name
--cpu-prof-name
Purpose: Specifies a custom filename for the generated CPU profile.
Default:
(see CPU Profile Filename for details on the pattern).CPU.<yyyymmdd>.<hhmmss>.<pid>.<tid>.<seq>.cpuprofile
Usage Example:
This will generate a profile file named my-custom-profile.cpuprofile
--cpu-prof-dir
Notes:
You CANNOT use placeholders for filenames (e.g.,
,${pid}
) in your custom name.${timestamp}
If an absolute path is provided (e.g.,
), the file WILL NOT be saved there./tmp/my-custom-profile.cpuprofile
--cpu-prof-interval
--cpu-prof-interval
Purpose: Sets the sampling interval for the CPU profiler in microseconds (µs). This determines how frequently the profiler captures the state of the JavaScript call stack.
Default:
(microseconds/µs), which is 1 millisecond.1000
Usage Example:
This command profiles an inline script (console.log('CPU')
How to use intervals?
The --cpu-prof-interval
A smaller interval (e.g., 50 µs, 100 µs) collects more samples, providing a highly detailed profile. This is useful for short-running processes or identifying fine-grained bottlenecks. However, it results in larger
files and can add slightly more overhead to the profiled application..cpuprofile
A larger interval (e.g., 1000 µs, 5000 µs) collects fewer samples, resulting in smaller profile files and reduced overhead. This is suitable for longer-running processes or when a high-level overview of CPU usage is sufficient.
Start with the default (1000 µs) and adjust based on the granularity of data you need and the acceptable size of the generated profile files. The following section includes a table that illustrates the impact of different intervals on a sample application.
Illustrative Example: Impact of Interval Choice
This example demonstrates how different --cpu-prof-interval
Step 1: Generate Profiles with Different Intervals
Run the following command to create profiles with intervals ranging from 1μs to 10,000μs:
Step 2: Analyze the Generated Profiles
Use this command to compare the profiles and display their characteristics:
Step 3: Results Analysis
The table below shows how the sampling interval affects profile characteristics:
Profile File | Sampling Interval | Duration (Script) | Total Nodes | Total Samples | File Size | Visual Example |
---|---|---|---|---|---|---|
| 1 µs | 58.5 ms | 472 | 10,514 | 162.0 KB | |
| 10 µs | 17.5 ms | 338 | 931 | 73.3 KB | |
| 50 µs | 16.0 ms | 245 | 312 | 45.2 KB | |
| 100 µs | 15.0 ms | 153 | 100 | 28.9 KB | |
| 1000 µs (1 ms) | 13.9 ms | 49 | 10 | 8.6 KB | |
| 2000 µs (2 ms) | 14.2 ms | 32 | 7 | 5.8 KB | |
| 4000 µs (4 ms) | 13.5 ms | 18 | 4 | 3.2 KB | |
| 5000 µs (5 ms) | 14.8 ms | 15 | 3 | 2.7 KB | |
| 7000 µs (7 ms) | 13.2 ms | 9 | 2 | 1.8 KB | |
| 10000 µs (10 ms) | 14.0 ms | 3 | 2 | 0.5 KB |
Examples of Combining Arguments
1. Experiment Comparison
Organized profiling for A/B testing or feature comparison experiments.
2. Production Monitoring
Low-overhead profiling with reduced sampling frequency.
3. Performance Benchmarking
High-detail profiling with frequent sampling for bottleneck analysis.
4. CI/CD Performance Testing
Automated profiling in build pipelines using NODE_OPTIONS
This works for spawning both child processes and worker threads.
All profiles end up in the same directory due to the absolute path.
The sampling interval is very high to enable long-running processes to be profiled.
5. Handling options with .env
Conclusion
Advanced CPU profiling is essential for keeping Node.js applications running smoothly as they grow. By applying these techniques, you can find and fix performance issues before they escalate into larger problems that affect users. As you gain experience with these tools, you will find that even the most complex optimization challenges become more approachable.