sprof reads the profile the dynamic linker collected for one shared object and shows it as a flat profile, a call graph, or a list of call pairs. Everything happens in a throwaway working directory; your source tree stays untouched. Twenty minutes if you already have a small library to profile, longer if you need to build one first.
This guide follows the installed sprof(1) manual from the Debian manpages package, version 6.7-2, identifying Linux man-pages 6.7. The executable itself is not installed in this shell, so the command output below shows the documented shape rather than a claim that it ran here. Check your own binary and package before you put this into automation.
Start with read-only checks that need no elevated privileges:
$ command -v sprof
$ sprof --version
$ sprof --help
The manual documents --version and --help, short forms -V and -?. If command -v comes back empty, install the glibc utilities through your distribution's normal process before continuing: the presence of the manpage tells you nothing about whether the executable itself is there.
Checkpoint: only continue once sprof --version identifies a usable program. Its exact version matters, because the output format and profiling implementation both come from the installed glibc toolchain.
sprof consumes data the dynamic linker generates for a named shared object. That soname is what you put in LD_PROFILE and what shows up in the profile filename, so pick it deliberately. This small example has two exported functions and two worker functions:
$ work=$(mktemp -d)
$ cd "$work"
$ cat > libdemo.c <<'EOF'
#include <unistd.h>
static void consume_cpu_1(unsigned int limit)
{
for (unsigned int j = 0; j < limit; j++)
(void)getppid();
}
static void consume_cpu_2(unsigned int limit)
{
for (unsigned int j = 0; j < limit; j++)
(void)getppid();
}
void x1(void) { for (unsigned int j = 0; j < 100; j++) consume_cpu_1(200000); }
void x2(void) { for (unsigned int j = 0; j < 1000; j++) consume_cpu_2(10000); }
EOF
$ cat > prog.c <<'EOF'
#include <stdlib.h>
void x1(void);
void x2(void);
int main(void) { x1(); x2(); return EXIT_SUCCESS; }
EOF
$ cc -g -fPIC -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 libdemo.c
$ ln -s libdemo.so.1.0.1 libdemo.so.1
$ ln -s libdemo.so.1 libdemo.so
$ cc -g -o prog prog.c -L. -ldemo
The temporary directory is ordinary user-owned state, so no sudo is needed. The three links make the real filename, soname and linker name agree with the command-line examples. Already have a suitable library in your own project? Use its real path and soname instead of compiling this one.
Create a separate output directory, then set the two environment variables for the run. LD_PROFILE takes the library soname, not the source filename; LD_PROFILE_OUTPUT chooses where the linker writes the result:
$ mkdir prof_data
$ export LD_PROFILE=libdemo.so.1
$ export LD_PROFILE_OUTPUT="$work/prof_data"
$ LD_LIBRARY_PATH=. ./prog
$ ls -l "$LD_PROFILE_OUTPUT"
libdemo.so.1.profile
The profile file gets appended to when it already exists, so repeated test runs quietly accumulate counts. That is often surprising when comparing two builds. The safe reset is a fresh temporary directory and a fresh run. If you specifically need to clear this one generated file, check the path first:
$ printf 'profile output: %s\n' "$LD_PROFILE_OUTPUT/$LD_PROFILE.profile"
$ rm -f -- "$LD_PROFILE_OUTPUT/$LD_PROFILE.profile"
$ LD_LIBRARY_PATH=. ./prog
Warning: that rm is irreversible for the profile file. It leaves the library alone, but only run it after confirming the printed path. Unset the profiling variables once you are done, so later programs do not add data by accident:
$ unset LD_PROFILE LD_PROFILE_OUTPUT
Pass the shared object first, the profile data second. -p (also --flat-profile) asks for per-function counts and timing:
$ sprof -p ./libdemo.so.1 "$work/prof_data/libdemo.so.1.profile"
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
The exact rows and numbers depend on workload, CPU and sampling. In this demo, the worker functions should account for the measured work while x1 and x2 represent their callers. Treat it as sampled evidence of performance, not proof that every invocation was measured precisely.
Omit the profile-data path and sprof will try to infer it from the soname, looking for <soname>.profile in the current directory. Supplying the full path is clearer, and avoids a common failure caused by running from the wrong directory.
Use -q or --graph for a call graph:
$ sprof -q ./libdemo.so.1 "$work/prof_data/libdemo.so.1.profile"
index % time self children called name
The report includes self and child time, call counts and an index for each entry. Names outside the profiled object show up as <UNKNOWN>. That is not necessarily a broken library: the manual uses the label for anything calling in from outside the profiled object, such as the main program.
Use -c or --call-pairs when the real question is which exported interface led to which other interface:
$ sprof -c ./libdemo.so.1 "$work/prof_data/libdemo.so.1.profile"
<UNKNOWN> x1 1
x1 consume_cpu_1 100
Names and counts depend on what symbols the library exposes and what the program actually calls. Pass none of -p, -q or -c, and the documented default gives you both a flat profile and a call graph.
A missing profile file usually means the monitored program did not load the soname named in LD_PROFILE, the output directory was wrong, or the program failed to run cleanly. Check dependency resolution and the generated filename:
$ ldd ./prog | grep libdemo
$ find "$work/prof_data" -maxdepth 1 -type f -print
If the file exists but sprof rejects it, use the exact shared-object path and soname active during collection. Never mix a profile from one incompatible library build with a different build and trust the resulting report.
sprof --version and sprof --help both run on the target host.LD_PROFILE_OUTPUT.-p, -q or -c matched the question you needed answered.<UNKNOWN> callers were all considered.