heaptrack
Use when tracking heap allocations on Linux with heaptrack: allocation hotspots, peak heap, temporary allocations, leaks, or a diff of two runs via heaptrack_print. Not for heap errors: use valgrind.
SKILL.mdplugins/odin-native/skills/heaptrack/SKILL.md
GitHubheaptrack
heaptrack (1.5.0 on the grounding host) intercepts malloc, free, realloc, and the C++ allocation operators through LD_PRELOAD or injection, records a backtrace per allocation, and compresses the trace to one file. heaptrack_print reads that file into four rankings: most calls, peak consumption, most temporary allocations, and leaks. It needs no rebuild and runs far faster than Valgrind's Massif.
Contract
| Field | Bound contract |
|---|---|
| Trigger | A program uses too much memory, allocates too often, or leaks, and the user wants the allocating call stacks; or two builds must be compared for allocation behavior. |
| Authority | Reversible local: writes only heaptrack data files (heaptrack.<prog>.<pid>.zst or the -o target), heaptrack_print output files, and the test binary in the working directory; rollback is deleting them. No remote mutation. |
| Side effect | Data files on disk. The traced program runs slower while every allocation is unwound and logged. |
| Done | The report names the top call stacks by peak bytes, by call count, and by temporary allocations, lists the leak records with their stacks, and, for a comparison, the per-stack delta between the two runs. |
Inputs
- Target binary with debug info (
-g, or aRelWithDebInfoCMake build) so backtraces carry function names and source lines. Optimization level stays as in production. - A representative workload; heaptrack records allocation counts, so the workload must exercise the suspected path.
- For Rust: the binary must use the system allocator (
#[global_allocator] static A: std::alloc::System = System;) so the interposedmallocsees every allocation. - For a comparison: two recorded data files.
Procedure
Record.
heaptrack ./myapp argswritesheaptrack.myapp.<pid>.zstin the current directory and prints the analyze command on exit;heaptrack -o run1 ./myappnames the filerun1.zst;heaptrack -p <pid>attaches to a running process by injection;--record-onlyskips the automatic analysis launch. Done when: the.zstfile exists and heaptrack printed its summary lines (calls to allocation functions,temporary allocations).Print the summary and rankings.
heaptrack_print -f run1.zstprints, in order,MOST CALLS TO ALLOCATION FUNCTIONS,PEAK MEMORY CONSUMERS,MOST TEMPORARY ALLOCATIONS, and a summary block:total runtime: 2.34s. calls to allocation functions: 145234 (62064/s) temporary allocations: 89123 (38087/s) peak heap memory consumption: 45.23M peak RSS (including heaptrack overhead): 78.45M total memory leaked: 2.34MEach ranking entry reads
N calls to allocation functions with X peak consumption fromfollowed by the stack, leaf first. Toggle sections with-p 0(peaks),-a 0(allocation counts),-T 0(temporary), and-l 1(leaks, off by default);-n 20widens the number of reported peaks from the default 10,-slimits sub-peak backtraces.-t 1shortens template identifiers and-m 1merges backtraces (both on by default). Done when: the four rankings and the summary are captured.Read each summary line against the code.
calls to allocation functionshigh relative to work done: many small allocations, so pool or reserve.temporary allocations(freed before the next allocation) as a large share of calls: churn in a hot loop, so hoist the buffer.peak heap memory consumption: the live-heap maximum to compare with the memory budget.peak RSSincludes heaptrack's own overhead and non-heap mappings, so compare peak heap between runs, not RSS.total memory leaked: bytes never freed at exit; the-l 1ranking names the stacks. Done when: each non-trivial line is tied to a call stack.Draw the allocation flamegraph.
heaptrack_print -f run1.zst -F stacks.folded --flamegraph-cost-type peakwrites folded stacks (cost types:allocations,temporary,leaked,peak); thenflamegraph.pl --countname bytes --title "Peak heap" stacks.folded > heap.svgfromflamegraphs. Done when: the SVG opens and the widest frame matches the top ranking entry.Compare two runs with the built-in diff:
heaptrack_print -f after.zst -d before.zstprints every ranking as the delta ofafterminusbefore, so a positive peak entry is a regression and a negative one an improvement. Done when: the top positive and top negative deltas are named with their stacks.Export to Massif format when a Massif-reading tool is already in the workflow:
heaptrack_print -f run1.zst -M run1.massif, thenms_print run1.massif. Done when:ms_printrenders the timeline.Suppress known leaks from third-party code: write a suppression file with one
leak:<function pattern>line per pattern and pass--suppressions leaks.supp;--print-suppressions 1lists the matches and the suppressed byte total;--disable-builtin-suppressionsreveals what heaptrack hides by default. Done when: the leak ranking holds only owned code.
Failure and recovery
| Failure | Cause | Fix |
|---|---|---|
| Stacks show only addresses | No debug info | Rebuild with -g; for Rust add debug = true to the profile |
| Rust program records almost nothing | Custom global allocator bypasses malloc |
Switch to std::alloc::System for the profiling build |
total memory leaked is large but the program frees on exit paths |
Exit through _exit or quick_exit skipped destructors |
Read the leak stacks; a still-referenced global at exit is not a bug, and a suppression records that decision |
| Data file is huge | Allocation-heavy workload | Shorten the workload; record only the phase under suspicion by attaching with -p after startup |
heaptrack -p fails to attach |
ptrace restricted | Run as the process owner or propose CAP_SYS_PTRACE; otherwise start the program under heaptrack |
| Comparison shows every stack changed | Different workloads or non-deterministic addresses in symbols | Use identical inputs and -m 1; compare peak heap totals first |
Massif reaches the same heap questions on platforms without heaptrack and at a much higher slowdown; valgrind covers it. Leak detection without profiling cost is LeakSanitizer through sanitizers.
Output
An allocation report with the summary block, the top stacks by peak, by call count, and by temporary allocations, the leak records, the flamegraph path when drawn, the per-stack deltas when two runs were compared, and the data file paths.