According to the gperftools documentation, the profiler can be started using any of the following methods:
CPUPROFILE
environment variable to the filename the profile information will be saved toCPUPROFILESIGNAL
and sending the appropriate signal to start or stop the sampling.ProfilerStart(filename)
and ProfileStop()
directly from your codeAll three methods require that libprofiler.so
be linked as well.
When I tried this, the third method worked, but when I merely set CPUPROFILE
, no profiling information was generated.
Does not work:
$ cat foo.c
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
}
$ gcc foo.c -std=c99 -lprofiler -g && CPUPROFILE=foo.prof ./a.out
Hello, world!
$ ls foo.prof
ls: cannot access foo.prof: No such file or directory
Does work:
$ cat bar.c
#include <stdio.h>
#include <gperftools/profiler.h>
int main(void) {
ProfilerStart("bogus_filename");
printf("Hello, world!\n");
ProfilerStop();
}
$ gcc -std=c99 bar.c -lprofiler -g && CPUPROFILE=foo.prof ./a.out
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64
$ ls foo.prof
foo.prof
$ ls bogus_filename
ls: cannot access bogus_filename: No such file or directory
$ ./a.out
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64
$ ls bogus_filename
bogus_filename
Note that CPUPROFILE
is being read, since its value overrides the filename passed to ProfileStart()
if set.
All the information needed to solve this is scattered across Stack Overflow, but it would have been useful to have it in one place, so now it is. I've included references to the answers I found useful when solving this in case anyone is looking for further information.
In gperftools, the constructor for CpuProfiler
checks for CPUPROFILE
and calls ProfilerStart(getenv("CPUPROFILE"))
if it's set (plus or minus a few other conditions). A CpuProfiler
is declared in profiler.cc
to ensure that the function is called. [1] Naturally, this will only happen if the libprofiler.so
is linked.
The following code reveals the issue:
$ cat baz.c
#include <stdlib.h>
#include <stdio.h>
#include <gperftools/profiler.h>
int main(void) {
volatile int i = 0;
if (i) ProfilerStop();
printf("Hello, world!\n");
return 0;
}
$ gcc -std=c99 baz.c -lprofiler -g && CPUPROFILE=foo.prof ./a.out
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64
ProfileStop()
can never actually be called, but since i
is volatile, the compiler can't optimize it away, and thus the linker needs to bring libprofiler in for the definition. By default, -lprofiler
was only bringing in the symbols that actually appeared in the program, which in the original case was none of them, so it didn't link the library at all, and CpuProfiler()
never got called.
The fix is to pass the --no-as-needed
flag to ld
before linking libprofiler.so
. [2] This causes it to link the library whether or not it anything from it is used in the program (the ld man page seems to suggest that this should be the default behavior, but it wasn't working that way for me). The --as-needed
flag is then passed to turn it back off once we've loaded what we need. (As an aside, --whole-archive
appears to be the equivalent option for static libraries [3])
The compilation command to make the profiling work for the original file:
$ gcc -std=c99 foo.c -Wl,--no-as-needed,-lprofiler,--as-needed -g && CPUPROFILE=foo.prof ./a.out
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64