Quantcast
Username/Email:  Password: 

Coverage Measurement and Profiling

“It is easier to optimize correct code, than correct optimized code.”—Yves Deville


Maybe you've always wondered what the gcov utility that comes with
GCC is used for, or maybe your new project at work has a
regulatory or customer requirement that your delivered software be
tested to a certain percentage of coverage, and you are looking for
how to accomplish that task. In this article, I introduce the general ideas
of coverage measurement and of performance profiling, along with the
standard GNU tools (gcov and gprof) used in these two techniques.

Coverage measurement is the recording of what paths were executed in
code. Coverage can be measured with different degrees of
granularity. The coarsest level is function coverage, measuring which
functions were called; then comes statement coverage, measuring which lines
of code were executed; and finally, branch coverage, measuring which
logic conditions in branch statements were satisfied. When
someone refers to coverage measurement usually statement or branch coverage is
being discussed.
gcov is the standard GNU coverage measurement tool, and it requires
GCC.

It's a sad fact, but slow software can outstrip Moore's Law and cheap
hardware. Even if you have the latest CPU and memory to spare,
it seems you always can find software to soak it
up. Furthermore, when discussing resource-constrained systems, like PDAs
and
embedded systems, it's often not possible to solve performance
problems simply by throwing more hardware at them.

Profiling allows you to measure which part of your code is
taking the most time to run. It gives you a window into the actual
runtime behavior of the program and lets you see what the hot
spots are for optimization. These spots often are not obvious from simple
code inspection or reasoning about how the program ought to
behave, so a profiler is a necessary tool here.

Profiling is a superset of coverage measurement, but its intent is
different. With coverage measurement, you hope to measure which parts
of your code didn't run. With profiling, you hope to measure
which parts of your code did run and which consumed the most
time.
A profiler measures the number of times a function has been
called, the number of times any particular line of code or branch of
logic has been executed, the call graph of which functions have called and the amount of time spent in each area of your program.
Gprof is the standard GNU profiler. It also requires GCC.

Coverage: Why Bother?
Although some industries, such as aerospace, require test coverage
measurement, it seems to be an underused technique outside of those
areas. This is because coverage
measurement is a bit more indirect than other debugging techniques, such as
memory leak detection and overwrite detections. Coverage
measurement in and of itself is only a measurement; it does not
automatically find bugs or improve the quality of your code.

What coverage management does do is provide information on how comprehensive your tests
are. If you don't test at all or don't test in some
systematic way, there's no point in measuring coverage. Furthermore,
if you don't have some sort of standard, automated test suite (DejaGNU, for
example), collecting coverage measurement data can be so
labor-intensive, ad hoc and error-prone that it might be difficult to
interpret your coverage measurements meaningfully.

Even test suites that seem comprehensive can leave large gaps. On
my first project ever using coverage measurement, we ran our
standard regression suite, which we were very proud of, and found the
percentage of lines of code exercised was 63%. If you've
never done this exercise before, you may be tempted to think “63%!
Your tests must not have been very good.” We actually were quite
happy to have a number this high; we knew our code had a large
amount of error-handling cases for system faults that our test suite
did not have the ability to trigger, such as out-of-memory
or out-of-file descriptors. We also had read that the industry average for
coverage on a newly instrumented test suite was close to 50%, so we
were glad to have done as well as we did.

Let that number sink in: 50%. An average comprehensive test
suite exercises only 50% of the code it is supposed to be
checking.
If you are not doing coverage measurement, you have no idea if you are
doing even this well—and you do not have a measurement telling you
how much better you could do. It's hard to optimize something that's
unmeasured.

Knowing how much of your code was exercised during
your test suite is the beginning, though, not the end. Once you know this,
you can look at the details of the coverage reports, find what code
was not exercised and start adding to your test suite.

Using gcov for Coverage Measurement
In order to use gcov, you must use GCC as your compiler. In addition,
you must pass the options -fprofile-arcs
-ftest-coverage during
compilation. This causes GCC to insert additional code into the
created object files to record the information needed by gcov and save
it to disk.

The gcov data is written in the same directory as your source
files, using a .da extension instead of .c. Nothing
is written until you run your program. You can run your
program multiple times to accumulate measurements, for example, trying
different command-line options or input data.

Per the GCC documentation, the object file contains the absolute
pathname of the .da file. So, it is not possible to move
your source files after building and continue to collect coverage
measurements. Doing so produces the error message arc
profiling: Can't open output file filename when attempting
to save the coverage data.
gcov also shows results for only a single file at a time, and it does not
provide a summarization method.

Creating gcov reports is straightforward. I use glib (the base
library for Gtk+ and GNOME, see www.gtk.org) as an example.
First, download glib from the Gtk+ site or a mirror. I used
glib-2.2.1, which was the latest at the time of this writing.

To turn on coverage measurement, set the environment variable
CFLAGS to -fprofile-arcs -ftest-coverage and run the
standard ./configure; make; make check. This should work
for any software that follows the GNU guidelines.

After make check has completed, it is possible to use
gcov to create coverage reports. The most basic report we can access is
a statement coverage report:


[zfrey@warthog glib]$ gcov garray.c
 68.37% of 215 source lines executed in file garray.c
Creating garray.c.gcov.

Besides printing out summary statistics, gcov creates an annotated
source listing. For each executable line, this listing shows either
the number of times it has been executed or a flag indicating it
never has been executed (Listing 1).

Listing 1. Excerpt of a gcov Annotated Source Listing


      GArray* g_array_sized_new
                  (gboolean zero_terminated,
                   gboolean clear,
                   guint    elt_size,
                   guint    reserved_size)
40    {
40      GRealArray *array;

40      G_LOCK (array_mem_chunk);
40      if (!array_mem_chunk)
14        array_mem_chunk = g_mem_chunk_new
                           ("array mem chunk",
                            sizeof (GRealArray),
                            1024, G_ALLOC_AND_FREE);

40      array = g_chunk_new (GRealArray,
                             array_mem_chunk);

To see the branch coverage, use the -b option:

[zfrey@warthog glib]$ gcov -b garray.c
 68.37% of 215 source lines executed in file garray.c
 59.02% of 122 branches executed in file garray.c
 46.72% of 122 branches taken at least once in file garray.c
 45.35% of 86 calls executed in file garray.c
Creating garray.c.gcov.

The annotated source file now contains the branch data
(Listing 2).

Listing 2. Excerpt from a Source File Annotated with Branch Data


        GArray* g_array_sized_new (
                         gboolean zero_terminated,
                         gboolean clear,
                         guint    elt_size,
                         guint    reserved_size)
  40    {
  40      GRealArray *array;

  40      G_LOCK (array_mem_chunk);
branch 0 taken = 40%
branch 1 taken = 58%
call 2 returns = 100%
branch 3 taken = 100%
call 4 returns = 100%
  40      if (!array_mem_chunk)
branch 0 taken = 65%
  14        array_mem_chunk = g_mem_chunk_new (
                            "array mem chunk",
call 0 returns = 100%
                             sizeof (GRealArray),
                             1024, G_ALLOC_AND_FREE);
                             1024, G_ALLOC_AND_FREE);

  40      array = g_chunk_new (GRealArray,
                               array_mem_chunk);
call 0 returns = 100%

To see coverage broken down by function within
the source file, use the -f option. This can
be combined with -b to get the branch
coverage data on a per-function basis as well.
Unfortunately, gcov does not provide a way to
summarize results over multiple source files.

GGCov: a Gtk+ GUI for gcov
GGCov is a Gtk+-based GUI front end for gcov written by Greg Banks and
available at www.alphalink.com.au/~gnb/ggcov. I was able to
download the latest version (0.1.1 at the time), compile and install
with it no problems. This was done using ./configure; make; make
install and allowing /usr/local as the prefix.

To use GGCov, cd to the directory with your source files and launch
GGCov. GGCov then looks for all the files in that directory with
coverage measurements and creates a summary (Figure 1).
This is something you cannot do with GNU gcov by itself.

Figure 1. A GGCov Summary Report

Selecting the Filename button activates a drop-down list of all
files in the directory that have coverage information. Selecting
a filename returns the statistics for that file.

Figure 2. A GGCov Report by File

Selecting the Function button activates a drop-down list of all
the functions from all the files in the directory that have coverage
information. Selecting a function name returns the statistics for that
function (Figure 3).

Figure 3. A GGCov Report by Function

As if that weren't enough power, the Range button allows the selection of
a file and a line number range and creates a summary for only the
functions within that range.
All views except the Overall summary have a View button, which
brings up a new window containing the annotated source display for the
selected file.

Overall, I found the basic functionality of GGCov easy to use and
helpful. GGCov also contains features for displaying call graphs;
unfortunately, on Red Hat 8.0 the glib example caused
GGCov to segfault. I contacted Greg Banks, who said the call
graph features were the most immature and that he hopes to address
these problems in the next few releases.

Using gprof for Profiling
Like gcov, gprof requires that your programs be compiled with
particular options so GCC can instrument your code properly. To
use gprof, you must use the -pg option. By default, this
profiles each function executed. To profile line by line, also use
the standard -g debugging option to insert debugging symbols
that gprof can use to create line-by-line reports.

When your program is run, information about function calls and time
spent is kept in memory. At program exit, this information is written
to a file called gmon.out. gprof uses
this file to create profiling reports. Unlike coverage monitoring, which saves
data in separate files for each source file, one data file is created
for the entire program. Instead of saving data in the same directory
as the source file, gmon.out is written to the correct
working directory at the time the program exits. So beware of
chdir() calls.

By default, the profiling data counts the number of times each
function is called, along with the function from which it was called.
Runtime data is collected by a sampling process, where the program is
examined every sample period, and the function currently
executing is recorded. gprof interprets this sampling to
report how much time was spent on each area of the code.

Because this is a statistical sampling, it is subject to statistical
error. See the gprof manual for a detailed discussion of the error
calculation. In short, the times reported by gprof may not be exact.
Furthermore, if a function is executed for a relatively small amount
of time, it may be missed during the sampling and
show 0.0 execution time. This is not a problem for performance
profiling. The information that a particular function or block
of code did not run for enough time to be sampled tells you that you
probably don't want to spend much time optimizing it.

Statistical errors are a problem, however, if you also want to use gprof to show
statement or branch coverage in addition to performance data. For
exact counts of how many times each line of code has been executed,
enable basic block counting by using the -a option at compile
time. This adds instrumentation to the code to count the execution of
each block of code. It also allows gprof to show exactly how many times
each line of code was executed, even if zero time is shown by way of
sampling.

It is possible to combine multiple gmon.out files in order to
build up measurements over many runs of a program. This is done with
the gprof -s command, which takes any number of
gmon.out files and creates a new gmon.sum with the
combined data. It is possible to add profiling data incrementally by
specifying gmon.sum as an input file.

I originally intended to reuse my glib example with gprof, but the
different behavior of gprof and gcov made this prospect difficult. The glib
test programs are run one after another by the make
check
command, so each test's gmon.out was overwritten by the next
test. Also, the test programs actually are shell scripts, which
set some reasonably complex environment variables to run the
real test programs, which are kept in a hidden subdirectory.
Moral
of the story: gcov is the right tool for doing coverage testing for
glib.

Instead, I created a simple program to calculate the Fibonacci numbers
from zero to 42. The Fibonacci numbers are a series of
numbers beginning with 0,1 where the next number in the series is
found by adding the previous two numbers (so, 0,1,1,2,3,5,8,13,21,...)
A good Web site on the Fibonacci numbers and related mathematical
concepts can be found at
www.mcs.surrey.ac.uk/Personal/R.Knott/Fibonacci/fib.html.
The number 42 was chosen, not merely for its literary
significance to Douglas Adams fans, but because the Fibonnacci numbers
begin overflowing signed 32-bit integer arithmetic at 47, so a lower
limit was useful.

The calculation of a
Fibonacci number can make a good example of a recursive function.
Because each Fibonacci number is the sum of the previous two, the
simplest approach is to create a function int fibonacci(int
n), which simply calls itself with n - 1 and with n - 2,
adds the results and returns the value (Listing 3).

Listing 3. fib.c


#include <stdio.h>

int fibonacci(int n);

int main (int argc, char **argv)
{
   int fib;
   int n;

   for (n = 0; n <= 42; n++) {
      fib = fibonacci(n);
      printf("fibonnaci(%d) = %d\n", n, fib);
   }

   return 0;
}

int fibonacci(int n)
{
   int fib;
   if (n <= 0) {
      fib = 0;
   }
   else if (n == 1) {
      fib = 1;
   }
   else {
      fib = fibonacci(n -1) + fibonacci(n - 2);
   }

   return fib;
}


Here is the output from running this simple Fibonacci program:

[zfrey@warthog prof]$ time ./fib
fibonnaci(0) = 0
fibonnaci(1) = 1
fibonnaci(2) = 1
fibonnaci(3) = 2
fibonnaci(4) = 3
...
fibonnaci(40) = 102334155
fibonnaci(41) = 165580141
fibonnaci(42) = 267914296

real    3m12.580s
user    3m10.566s
sys     0m0.127s
[zfrey@warthog prof]$

Now, we can look at the profiling data:


[zfrey@warthog prof]$ gprof -b ./fib gmon.out
Flat profile:

Each sample counts as 0.00195312 seconds.

 %   cumulative   self              self     total
time   seconds   seconds    calls  ms/call  ms/call  name
95.13     16.34    16.34       43   380.02   380.02  fibonacci
 4.87     17.18     0.84                             main

                        Call graph

index % time    self  children    called     name

[1]    100.0    0.84   16.34                 main [1]
               16.34    0.00      43/43      fibonacci [2]
-----------------------------------------------
                             2269806252      fibonacci [2]
               16.34    0.00      43/43      main [1]
[2]     95.1   16.34    0.00   43+2269806252 fibonacci [2]
                             2269806252      fibonacci [2]
-----------------------------------------------

Index by function name

   [2] fibonacci               [1] main

It's obvious where this program's bottleneck is—the fibonnaci()
function itself. That fact should be evident from reading the source.
What our profiling tells us that is not obvious from the
source is the number of recursion calls that our calculation
takes. Although fibonacci() is called directly from main() only 43
times, we are making approximately 2.3 billion calls as part of
the recursion.

Fortunately, there's another way. Checking our reference on Fibonacci
numbers, we find that there is a formula to calculate the nth
Fibonacci number directly from n:

F(n) = round(Phin / sqrt(5))

I am not going to explain the mathematics behind the
derivation of this formula, for the simple reason that I don't
understand them myself.
With this improved algorithm, however, once again I can rebuild, run and
profile (Listing 4).

Listing 4. New Version of the Fibonacci Function

#define PHI   1.6180339887498948

int fibonacci(int n)
{
   return (int) rint(pow(PHI, n) / sqrt(5));
}

Let's see what gprof tells us this time:

                        Call graph

granularity: each sample hit covers 4 byte(s) no time propagated

index % time    self  children    called     name
                0.00    0.00      43/43          main [8]
[1]      0.0    0.00    0.00      43         fibonacci [1]
-----------------------------------------------

Index by function name

   [1] fibonacci

Our program is now so much faster that the sampling process failed to
register any time spent. Although this is a good result in a way, it's
not so good if we want to see if any more performance
improvements can be made.

A standard technique from the gprof manual is to run the program many
times and summarize the results, like so:


[zfrey@warthog prof]$ mkdir runfib2; cd runfib2
[zfrey@warthog runfib2]$ for i in `seq 1 1000` ; \
do ../fib2 > /dev/null; mv gmon.out gmon.out.$i; done
[zfrey@warthog runfib2]$ gprof -s ../fib2 gmon.out.*

The results, though, still show the time spent as 0.00.
Clearly, a different approach is called for. I now create fib3.c, with
two changes. First, I enclose the 0–42 loop with an additional
0–1000 loop, to increase our runtime. Second, I add a local
variable to the fibonacci() function and break the calculation apart
to one operation per line for line-by-line profiling. This gives us
enough runtime that gprof has something to report:

Listing 5. fib3.c


#include <stdio.h>
#include <math.h>

int fibonacci(int n);

int main (int argc, char **argv)
{
   int fib;
   int n;
   int i;

   for (i = 0; i < 1000; i++) {
      for (n = 0; n <= 42; n++) {
         fib = fibonacci(n);
         printf("fibonnaci(%d) = %d\n", n, fib);
      }
   }

   return 0;
}

#define PHI   1.6180339887498948

int fibonacci(int n)
{
   double temp;

   temp = pow(PHI,n);
   temp = temp / sqrt(5);
   return (int) rint(temp);
}

Before doing a line-by-line report, though, I create
a multirun summary, as before.

Now, let's look at the line-by-line report (trimmed to fit):


[zfrey@warthog runfib3]$ gprof -b -l -p ../fib3 gmon.sum
Flat profile:

Each sample counts as 0.00195312 seconds.
 %   cumulative   self             self     total
time   seconds   seconds   calls  ps/call  ps/call  name
38.25      1.12     1.12                            fibonacci (fib3.c:31)
27.97      1.94     0.82                            fibonacci (fib3.c:30)
14.49      2.36     0.42                            fibonacci (fib3.c:29)
 5.91      2.53     0.17                            main (fib3.c:16)
 4.64      2.67     0.14                            main (fib3.c:15)
 3.14      2.76     0.09                            fibonacci (fib3.c:32)
 1.87      2.82     0.05                            main (fib3.c:14)
 1.54      2.86     0.04                            main (fib3.c:14)
 0.83      2.89     0.02 43000000   567.77   567.77 fibonacci (fib3.c:26)
 0.77      2.91     0.02                            main (fib3.c:21)
 0.53      2.92     0.02                            main (fib3.c:13)
 0.07      2.93     0.00                            main (fib3.c:20)


The highest percentage of time is spent on line 31, followed by line
30. There is nothing we can do about line 31, because the printf() call
and the function return are essential operations. However, on line 30,
notice that we are calculating the square root of 5 every time.
Because this result never changes, the next optimization should be to replace
it with a constant. This cuts the execution time from
136 milliseconds to 22 milliseconds.

KProf—A KDE GUI for gprof
KProf provides a KDE-based GUI for viewing gprof results. It is
available from kprof.sourceforge.net and can be built
with the
standard ./configure; make; make install process.

After launching KProf, go to Filerarr;Open to open either an
executable for which profiling measurements have been taken or a text
file that contains a gprof report. If you open an executable, KProf
assumes that the profiling data is contained in the current directory
in gmon.out.

Figure 4 shows a view of KProf displaying the results from the fib example. The circular arrow by the Fibonacci results
indicate the function is called recursively.

Figure 4. KProf Display

Zach Frey (zach@zfrey.com) works as an application engineer at
New Eagle, a division of Mototron, in Ann Arbor, Michigan.
He has more experience with software tests than he cares to admit, but
he still finds improving software quality to be an interesting topic.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Is there any coverage tool

Neeraj's picture

Is there any coverage tool available that only works on source files and not on executables. I mean I dont want to compile the source files even once still I want to find out the coverage in the code.
Is it possible?

Please help. I'll really appreciate your effort if you get me any information regarding the matter.

Thanks!

help needed in installing ggcov

savita's picture

We have tried installing ggcov

we had followed following steps.

./configure (it satisfies our pc configuration)

make
make install

It is not getting installed properly...
How can we check it is properly installed or not....?
Is there any other steps to covered...

please kindly help

Regards
savita

hai, i understood your

Raja's picture

hai,
i understood your problem.....
me also struck with that problem but what i did was,
i just installed ggcov-06 instead of ggcov-08.....
in ggcov ,it is asking someother file which we could not get fro internet...
so better to go to install ggcov-06
thanks and regards,
Rajasekhar...

ggcov

Anonymous's picture

when i try to run ggcov in SUSE LINUX 10.1 using the command line ggcov ,i do not get any error message.but the results are not displayed.
the installation had no problems.
If anybody who is familiar with ggcov can give me a detailed idea about how to use this tool,please reply.

ggcov

Anonymous's picture

anybody kindly post a reply regarding how to implement ggcov

I could run ggcov successfully on the command prompt but I can't view any GUI.So please tell me the syntax/procedures of using ggcov.My linux version is SUSE 9.0

error on running kprof

lalitha's picture

when i try to make the kprof i am getting this errors..
Kindly help me to correct and run the application

parseprofile_pose.h:30: error: expected `)' before â&â token
parseprofile_pose.cpp:29: error: prototype for âCParseProfile_pose::CParseProfile_pose(QTextStream&, QPtrVector&)â does not match any in class âCParseProfile_poseâ
parseprofile_pose.h:28: error: candidates are: CParseProfile_pose::CParseProfile_pose(const CParseProfile_pose&)
parseprofile_pose.h:36: error: CParseProfile_pose::CParseProfile_pose()

need help for GCOV

Anonymous's picture

I have a problem:
I'm not able to gcc -fprofile-arcs -ftest-coverage file.c
Could you let me know how I can set environemt CFLAGS variable?
I have problem when I gcc on glib files i.e garray.c

please hel--

valgrind coverage

John Pye's picture

I thought I'd add a comment to say that some people are using Valgrind to do coverage testing using the 'cachegrind' tool.

Great article, a nice clear introduction to the topic.

Thanks

Anonymous's picture

Thanks very much for this informative example. I was surprised that 50% is about average for code coverage!

Re: Coverage Measurement and Profiling

Anonymous's picture

Very nice article, I enjoyed it and learned new (for me) methods

thanks a lot
b.

Don't forget lcov!

Anonymous's picture

lcov is a nice addition to gcof; like ggcov, it shows summaries
for multiple files. It goes beyond ggcov, I think, in that it
handles entire directory hierarchies. It can handle the Linux
kernel and Wine, for instance. It outputs HTML rather than
presenting a GUI, but that's kind of a plus in my book.
It's at ltp.sf.net/coverage/lcov.php

Re: Don't forget lcov!

ZachFrey's picture

Lcov does look very nice. My intent wasn't to do a comprehensive survey of tools, but to introduce the basics of gcov and gprof and give an example of a representative "add-on" for each.

Post new comment

  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <pre> <ul> <ol> <li> <dl> <dt> <dd> <i> <b>
  • Lines and paragraphs break automatically.
  • Use to create page breaks.

More information about formatting options