2
votes

According to the gperftools documentation, the profiler can be started using any of the following methods:

  1. Setting the CPUPROFILE environment variable to the filename the profile information will be saved to
  2. Doing the above, and also setting CPUPROFILESIGNAL and sending the appropriate signal to start or stop the sampling.
  3. Calling ProfilerStart(filename) and ProfileStop() directly from your code

All 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.

1
link to documentation is brokenofloveandhate
@ofloveandhate Fixed. Thanks for bringing that to my attention.Ray

1 Answers

6
votes

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