docs java javascript python c / c++ c# reading reports
How to profile C and C++
Native code has no runtime to ask, so profiling happens from the outside: the OS
interrupts the process a few hundred times a second and records the call stack. On
Linux that's perf, and it's the one to reach for — no
recompile, no instrumentation, and it works on a binary that's already running in
production. flamelens takes either the folded stacks every native
profiler can export, or raw perf script output saved straight to a
file.
Linux: perf, the short version
# sample the whole system at 99Hz with call graphs, for 60 seconds
sudo perf record -F 99 -a -g -- sleep 60
# ...or one process you already have running
sudo perf record -F 99 -p <pid> -g -- sleep 60
# ...or a command, start to finish
perf record -F 99 -g -- ./my_program
# then turn perf.data into text and upload THAT
perf script > profile.perf
Upload profile.perf as-is — we parse perf script output
directly, so you don't need FlameGraph's Perl scripts. If you already use them, folded
stacks work too and are a much smaller file:
perf script | stackcollapse-perf.pl > profile.folded
Why -F 99 rather than a round 100: sampling at a frequency that shares a
factor with your workload's own periodicity (timers, frame rates, tick handlers) makes
the profiler lock in step with it and over-count whatever runs on that beat. 99 is the
conventional choice for exactly that reason.
Symbols or it's useless. A profile full of hex addresses tells
nobody anything. Compile with -g (and keep it —
-g -O2 is fine and normal for profiling; optimized-with-symbols is
what you want, since profiling a -O0 build measures a program you
don't ship). Add -fno-omit-frame-pointer so the stack walker can
actually unwind; without it, call graphs on x86-64 collapse to a single frame. If
your distro strips binaries, install the matching
-dbg/-debuginfo packages, or use
perf record --call-graph dwarf (slower, bigger, but it doesn't need
frame pointers).
C++ specifically: the name mangling
perf script usually demangles for you. When it doesn't, you get
_ZN3foo3barEv instead of foo::bar() — pipe it through
c++filt before uploading, since a report that names mangled symbols is a
report you have to decode by hand:
perf script | c++filt > profile.perf
Template-heavy code produces enormous symbol names
(std::vector<std::pair<…>>::_M_realloc_insert). That's fine —
it's exactly the signal that tells you which instantiation is hot.
macOS: Instruments
- Launch Instruments (it ships with Xcode) and choose the
Time Profiler template — or from a terminal:
xctrace record --template 'Time Profiler' --launch -- ./my_program - Record while the slow thing happens, then stop.
- In the call tree, right-click and turn Invert Call Tree off, then File → Export the call tree as text.
perf doesn't exist on macOS (no perf_events), and Instruments'
.trace bundle is a directory, not a file we can take. The practical path
is to export a call tree and collapse it, or — simpler — use
samply, which works on
macOS and Linux and can write a profile flamelens reads.
In JetBrains CLion
- CLion has a built-in profiler on Linux (it drives
perf) and macOS (it drives DTrace): Run → Profile '<target>', or the profile button in the run widget. - First run on Linux, CLion will tell you if
kernel.perf_event_paranoidblocks it — set it as it suggests (sudo sysctl -w kernel.perf_event_paranoid=1), which is the same setting the command-line flow needs. - Exercise the slow path, stop, and read the flame graph in the profiler tool window.
- For a file to upload, run the
perf record/perf scriptpair from CLion's built-in terminal against the binary CLion just built (it's incmake-build-debug/or your configured output directory).
In VS Code
- There's no native profiler in the C/C++ extension, so profiling is a task. Add one
to
.vscode/tasks.jsonso it's one keystroke:{ "version": "2.0.0", "tasks": [{ "label": "profile with perf", "type": "shell", "command": "perf record -F 99 -g -- ${workspaceFolder}/build/my_program && perf script > profile.perf" }] } - Run it with Ctrl+Shift+P → Tasks: Run Task → profile with perf.
profile.perflands in your workspace folder.
Windows and Intel VTune
On Windows, use Windows Performance Recorder
(wpr -start CPU -filemode, then wpr -stop trace.etl) and
collapse the ETL with FlameGraph's stackcollapse-wpa.pl. With
Intel VTune on either platform,
vtune -collect hotspots -r result -- ./my_program then
vtune -report top-down -format csv gives you something collapsible. Either
way, folded stacks are the destination — it's the format everything can reach.
Now analyze it. Take profile.perf or
profile.folded to the flamelens terminal —
auth <email> → verify <code> →
scan init. The same format covers Rust and Go binaries, since by the
time perf sees them they're all just symbols.
Why there's no “scan live” for native code
Java exposes JMX and Node exposes the inspector, so flamelens can attach to those over
the network. A compiled binary exposes nothing — there's no runtime listening, and
perf works by asking the kernel on that machine for sample
interrupts. There is no remote equivalent and no port to open, so the honest answer is
the same one Python gets: run an agent next to the process.
Continuous sampling with an agent ENTERPRISE
The agent runs on the host, connects out over HTTPS, and samples on a cadence — so you get a profile every N minutes, each diffed against the last:
agent new prod-encoder-1
# → prints a token, ONCE. store it like a password.
schedule new encoder 1234 native 30 60
# → sample pid 1234 for 60s, every 30 minutes
Like Python, the target is a pid (or whatever your script resolves to
one), since that's what perf takes. The recorder step is two lines:
perf record -F 99 -p "$TARGET" -g -o /tmp/perf.data -- sleep "$secs"
perf script -i /tmp/perf.data | c++filt > "$out"
The agent needs permission to sample: kernel.perf_event_paranoid at 1 or
lower, or CAP_PERFMON (CAP_SYS_ADMIN on kernels before 5.8).
In Kubernetes that's securityContext.capabilities.add: ["PERFMON"] plus
shareProcessNamespace: true for a sidecar to see the app container's pids.
The full polling loop is on the JavaScript
page — only the recorder line differs per runtime.
What makes a good profile
- Frame pointers on, symbols kept. The single most common reason a
native profile is useless is a stripped, frame-pointer-omitted release build. Ship
a build with
-g -fno-omit-frame-pointer; the cost is negligible next to being able to see anything. - 60 seconds at 99Hz is plenty. ~6000 samples resolves anything worth fixing. Longer recordings mostly grow the file.
- Sample the process, not the machine —
-p <pid>rather than-a— unless you're specifically chasing something that crosses process boundaries. - Kernel frames are signal. Time in
copy_user, futex waits, or page-fault handlers usually points at an I/O, locking, or allocation problem in your code rather than a kernel problem.