Skip to main content
Flame graphs are a way of visualizing CPU time spent in functions. They can help you pin down where you spend too much time doing synchronous operations.

What a flame graph is useful for

A flame graph shows which functions are consuming CPU time and how they relate to one another in the call stack. The most saturated (orange) bars represent CPU-heavy functions. Clicking an element zooms in on that section. Flame graphs are generated from perf output — not a Node.js-specific tool. While it is the most powerful way to visualize CPU time spent, it may have issues with how JavaScript code is optimized in Node.js 8 and above. See perf output issues for details.

How to create a flame graph

Solaris VMs are no longer needed for flame graphs. Creating one for Node.js is straightforward on Linux.

Use a pre-packaged tool

For a single-step solution that produces a flame graph locally, use 0x:
npx 0x app.js
For diagnosing production deployments, see the 0x production servers notes.

Create a flame graph with system perf tools

This approach gives you full control over each step.
1

Install perf

Install perf — usually available through the linux-tools-common package:
sudo apt-get install linux-tools-common linux-tools-$(uname -r)
Try running perf. If it complains about missing kernel modules, install those too.
2

Run Node.js with perf enabled

See perf output issues for tips specific to your Node.js version.
perf record -e cycles:u -g -- node --perf-basic-prof --interpreted-frames-native-stack app.js
Disregard warnings unless they say you cannot run perf due to missing packages. Warnings about kernel module samples are expected and can be ignored.
3

Generate the data file

Export the perf data:
perf script > perfs.out
It is useful to filter out Node.js internal functions for a more readable graph.
4

Preview or generate the flame graph

Upload the generated perfs.out file to flamegraph.com to visualize the flame graph directly in your browser.
Once the flame graph is rendered, inspect the most saturated orange bars first. They are likely to represent CPU-heavy functions.

Sampling a running process

This is useful for recording flame graph data from an already-running process that you do not want to interrupt — for example, a production process with a hard-to-reproduce issue.
perf record -F99 -p `pgrep -n node` -g -- sleep 3
OptionExplanation
-F99Profiling frequency: 99 samples per second. Increase for more precision; lower values produce less output.
-pPID of the process to profile
-- sleep 3Keeps perf running for 3 seconds. perf runs for the life of the command passed to it.
After recording, proceed with generating the flame graph from step 3 above.

Filtering out Node.js internal functions

Usually, you want to focus on the performance of your own code. Filter out Node.js and V8 internal functions to make the graph easier to read:
sed -i -r \
  -e "/( __libc_start| LazyCompile | v8::internal::| Builtin:| Stub:| LoadIC:|\[unknown\]| LoadPolymorphicIC:)/d" \
  -e 's/ LazyCompile:[*~]?/ /' \
  perfs.out
If your flame graph looks odd or something seems missing in a key function, try generating it without the filters. You may have a rare case of an issue within Node.js itself.

Node.js profiling options

--perf-basic-prof and --perf-basic-prof-only-functions are the two flags useful for debugging your JavaScript code. Other options are used for profiling Node.js itself, which is outside the scope of this guide.
FlagEffect
--perf-basic-profAlways writes to /tmp/perf-PID.map. Lower overhead than full profiling, but can cause unbounded disk growth.
--perf-basic-prof-only-functionsProduces less output than --perf-basic-prof. The lowest-overhead option, suitable for production.
Without these options you will still get a flame graph, but most bars will be labeled v8::Function::Call instead of showing your actual function names.

perf output issues

Node.js 8.x V8 pipeline changes

Node.js 8.x and above ships with new optimizations to the JavaScript compilation pipeline in the V8 engine (Turbofan), which makes function names/references sometimes unreachable for perf. The result is that you might see ByteCodeHandler: where you would expect function names. 0x has mitigations for this built in. For details, see:

Node.js 10+

Node.js 10.x addresses the Turbofan issue with the --interpreted-frames-native-stack flag:
node --interpreted-frames-native-stack --perf-basic-prof-only-functions app.js
This gets function names in the flame graph regardless of which pipeline V8 used to compile your JavaScript.

Broken labels in the flame graph

If you see labels like this:
node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPNS0_8ZoneListIPNS0_9StatementEEE
It means the Linux perf you are using was not compiled with demangle support. See this Ubuntu bug for more information.

Examples

Practice capturing flame graphs yourself with the flame graph exercise.

Build docs developers (and LLMs) love