Read and Interpret perf.data with perf report
perf report opens a profile perf record already made, without rerunning the workload. By the end you can spot the hottest symbols, narrow the view to one process or library, and read the call chains behind a slow function.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 10 minutes for a familiar profile, longer if you need to chase missing symbols. You need the perf command and a readable perf data file. This guide follows the perf-report(1) shipped in linux-tools-common version 6.8.0-142.142 on the reference machine. The local wrapper currently warns that a matching kernel-specific perf package for kernel 6.8.0-139 is not installed, so check your own command before trusting version-specific output.
1. Find the profile
Run the report from the directory holding the data file. With no input option, perf report reads perf.data; if standard input happens to be a FIFO the default changes, so use an explicit file whenever a script or pipeline is involved:
cd /path/to/profile-directory
test -r ./perf.data
perf report -i ./perf.data
The interactive interface normally opens in your terminal. For output suitable for a log or a review, ask for the stdio interface directly:
perf report --stdio -i ./perf.data
A report is sampled event data, not a complete execution trace. A symbol showing a high percentage got many samples, that is all: it is not automatically the proportion of wall-clock time, because the event, sampling frequency, workload and collection permissions all shape what the samples actually mean.
Checkpoint
If the command says the file cannot be opened, check the path and permissions. If it says the data was recorded with an incompatible or unavailable feature, use the same or a compatible perf version where you can, and do not delete the original profile while you are diagnosing it.
2. Read the histogram
The default sort keys are command, shared object or module, and symbol, equivalent to --sort comm,dso,symbol. Exact columns shift with the recorded event and the options you pass. Start with whichever symbol or library carries the largest relevant overhead, then ask whether it lives in your code, a dependency, the kernel, or somewhere unresolved.
For a stable text report, ask for the fields you need and add sample counts when percentages alone are misleading:
perf report --stdio --show-nr-samples \
--sort comm,dso,symbol \
-i ./perf.data
--show-nr-samples prints the sample count behind each symbol, which is how you tell a 0.2 percent entry backed by thousands of samples from a tiny entry that only exists because the profile itself is small. It does not make the profile any more statistically precise, just more honest about what backs each number.
To focus on one executable or shared object, use comma-separated filters. These change the denominator behind the overhead column, so a filtered 40 percent is not directly comparable with an unfiltered 40 percent:
perf report --stdio \
--comms=worker \
--dsos=libexample.so \
-i ./perf.data
Swap worker and libexample.so for names that actually show up in your report. For a process or thread ID, use --pid=PID or --tid=TID. For a symbol name, use --symbol-filter=TEXT; the match is partial, so pick text specific enough that it will not pull in something you did not mean.
3. Inspect call chains
Call chains show how execution reached a sampled function, but only if perf record actually stored them. perf report cannot reconstruct a call stack that was never captured:
perf report --stdio \
--call-graph=graph,0.5 \
-i ./perf.data
graph is the default call-chain display, and the second value is the minimum percentage threshold, so this example hides branches under 0.5 percent. Lowering it surfaces more detail, but it also makes the report longer and harder to scan in one sitting.
With call chains present, the report can show Children and Self overhead. Self counts samples attributed directly to a function; Children rolls in everything it called. A parent can carry high Children overhead with little or no Self overhead of its own, and the Children column can sum to more than 100 percent across rows. That is expected accumulation, not duplicate samples hiding in the data file.
To go back to direct samples only, turn off the default accumulation:
perf report --stdio --no-children -i ./perf.data
The same behaviour can be set persistently with report.children = false in the perf configuration, but a command-line flag is easier to spot and safer for a one-off look.
Useful focused views
Switch sort keys when the default view hides the question you are actually asking. These only make sense when the profile carries the required information:
--sort cpuseparates samples by processor number.--sort srclinegroups by source file and line, and needs DWARF debugging information.--sort symbolgroups by function name and enables IPC and IPC Coverage columns when the data supports them.--sort time,overheadbuckets samples into time quanta before ordering by overhead. The default quantum is 100ms; change it with--time-quantum, for example--time-quantum=1s.
--hide-unresolved gives you a clean list of resolved symbols, but do not mistake a shorter list for better data. Unresolved samples can be exactly the part that needs fixing: missing debug information, stripped binaries, unavailable kernel symbols, or a mismatched build can all thin out symbolisation.
Common traps and safe boundaries
Do not treat an empty or tiny histogram as proof the program was idle. The recording event might not have been available, the workload might have finished before sampling started, or permissions might have limited collection. Check the recording command and its statistics before drawing a conclusion from a thin report.
Tip
Do not add sudo out of habit. Reading a profile only needs read access to the file; elevated privileges may have been needed when recording system-wide events, but that was a collection concern, not a requirement of perf report. If you do run it elevated, keep the input path explicit and be careful opening untrusted data with tools that load external symbol or trace metadata.
perf report does not alter perf.data during normal use. If you are experimenting with filters, keep the original file and redirect text output to a new path instead:
perf report --stdio -i ./perf.data > ./perf-report.txt
test -s ./perf-report.txt
Done means
- Right file opened: you used
-ior confirmed the defaultperf.datawas the one you meant. - Self vs Children understood: you know which kind of overhead the leading percentage represents.
- Filters used with intent: any focused filter or sort was chosen with its denominator and data requirements in mind.
- Unresolved symbols checked: you looked at symbol and call-chain availability before blaming a specific function.
- Original profile preserved: any text report was saved separately, leaving the source data untouched.