Profile a Program's Allocations with memusage
A program that quietly eats more memory every hour needs more than a hunch, and memusage gives you a repeatable allocation profile. This guide follows the installed Linux man-pages 6.7 interface and the glibc 2.39 runtime on this machine. Allow about fifteen minutes, plus whatever time the target workload itself takes.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a shell, the memusage script and the matching glibc support files. The target program must be safe to run under LD_PRELOAD, because memusage injects libmemusage.so into it. Do not profile a set-user-ID or otherwise security-sensitive program casually: the preload changes the process environment and can affect how it runs.
1. Check the local tool before profiling
Start with read-only checks. None of this needs elevated privileges:
$ command -v memusage
$ memusage --version
$ command -v memusagestat
The first command should print the path to the script, the second its version information, and the third tells you whether the optional graph converter is even installed. On the reference machine, libmemusage.so belongs to glibc 2.39, but the executable scripts are not on PATH at all. That's a useful failure to hit early: having the manpage or the library alone doesn't mean the workflow is actually available.
Checkpoint
If command -v memusage prints nothing, stop and install or enable the package that supplies the tool through your normal system-management process. Don't substitute an unrelated memory monitor. Everything below uses the syntax documented by memusage(1), and none of it works until the script is actually there.
2. Profile a harmless, bounded command
Use something small first, so a tool problem doesn't get tangled up with a long application run:
$ memusage --data=/tmp/memusage-date.dat /usr/bin/true
<summary and allocation table are printed here>
The exact summary depends on the workload. Look for the line beginning Memory usage summary:, followed by the allocation table and a histogram of block sizes. memusage runs the named program and reports allocations intercepted from malloc, calloc, free and realloc.
The data file is a binary trace, not something to open in an editor. The destination under /tmp is ordinary user-writable state; if you use a shared directory instead, choose permissions and retention deliberately, because an allocation trace can reveal workload details.
Checkpoint
Check both the status and the file:
$ printf 'program status: %s\n' "$?"
$ test -s /tmp/memusage-date.dat && echo 'data file exists'
memusage returns the profiled program's own exit status. Zero means true returned zero; it does not mean a real application passed its own tests.
3. Run the real workload and preserve the output
Swap in your actual executable and its arguments, keeping the path and each argument as separate shell words:
$ memusage --data=/tmp/my-program.memusage.dat \
/path/to/PROGRAM --input /path/to/INPUT
$ status=$?
$ printf 'program status: %s\n' "$status"
$ test "$status" -eq 0
The line continuation above is just formatting. Quote a path that contains whitespace, for example "/path/to/my program". The program's own output and error messages still belong to that program: memusage's report is extra diagnostic output on top, so capture standard output and standard error separately if the target's own output matters:
$ memusage --data=/tmp/my-program.memusage.dat \
/path/to/PROGRAM --input /path/to/INPUT \
> /tmp/my-program.stdout 2> /tmp/my-program.stderr
Don't reach for sudo just because the program is being profiled. Use the same unprivileged account and environment that normally runs it: elevated privileges change the test and make the preload boundary harder to reason about.
4. Read heap totals without confusing them
The first report line has three numbers that answer three different questions:
heap totalis the accumulated size of allocations, including increases recorded throughreallocand, when enabled,mmap.heap peakis the largest single allocation size the monitored calls saw, not the process's maximum resident set.stack peakis calculated from stack-pointer movement, and is not a complete measurement of every thread's stack.
That distinction is the main trap in this tool. A workload that repeatedly allocates and frees a small block can show a large heap total but a modest heap peak. Conversely, several live allocations at once can leave a process using more memory than the largest single allocation the report shows. Use memusage to understand allocation activity, not as a stand-in for a resident-memory or whole-process measurement.
For realloc and mremap, the table adds extra fields such as nomove and dec. A shrinking reallocation can make the total-memory cells look inconsistent with the free cell: the manual is explicit that the realloc total doesn't represent every reduction in size. Don't add those columns together as if they were a live-byte balance.
5. Include mappings when the workload uses them
By default the trace only covers the malloc family. Add --mmap when mapped regions are part of the question:
$ memusage --mmap --data=/tmp/my-program-mmap.dat \
/path/to/PROGRAM --input /path/to/INPUT
This also intercepts mmap, mremap and munmap. It's a broader allocation trace, but still not a complete accounting of every kernel mapping or resident page. Compare two runs with the same input and environment, changing one option at a time.
The default output is buffered. Add --unbuffered when you need records written promptly and are willing to pay the extra I/O cost:
$ memusage --unbuffered --data=/tmp/long-run.dat \
/path/to/PROGRAM --input /path/to/INPUT
For a very busy target, --buffer=SIZE changes how many entries collect before a write; keep SIZE a deliberate, documented choice in scripts. --no-timer disables timer-based SIGPROF sampling of the stack pointer, for when that signal would interfere with the workload, at the cost of less stack sampling.
6. Turn saved data into a graph
If memusagestat is installed, convert the binary data once the profiled process has exited:
$ memusagestat /tmp/my-program.memusage.dat /tmp/my-program.png
$ file /tmp/my-program.png
The PNG is a separate artefact. Add graph-only options to memusage itself if you want to shape the generated graph, for example:
$ memusage --data=/tmp/my-program.memusage.dat \
--png=/tmp/my-program.png --time-based --total \
--title='my program allocation run' \
/path/to/PROGRAM --input /path/to/INPUT
--time-based plots time instead of call count on the horizontal scale, and --total adds a total-memory line. The title and dimensions affect presentation, not the measurements. A visually smooth graph is not proof two runs are equivalent; keep the command, input and environment details alongside the PNG.
7. Clean up temporary traces safely
These examples all write new files under /tmp. Inspect them before removing anything. Deleting a trace is irreversible, so don't paste the cleanup command into an automated first run:
$ ls -lh /tmp/my-program*.dat /tmp/my-program*.png
$ rm -- /tmp/my-program.memusage.dat /tmp/my-program.png
If a run fails before producing a complete file, leave the original input and program untouched, remove only the incomplete output you created, and rerun with a fresh filename. None of the profiling examples touch service configuration or other persistent system state.
Done means
- Tool checked first.
memusageand, for graphs,memusagestatwere confirmed before the workload ran. - Right account. The target ran under its intended unprivileged account and returned the status you expected.
- Heap numbers understood. Heap total, heap peak and stack peak were read as three different measurements, not one figure.
- Options justified.
--mmap, buffering and timer options were only added for a stated diagnostic reason. - Output handled deliberately. The binary trace and PNG were kept or removed on purpose, without overwriting useful results.