Home | History | Annotate | Download | only in perf
      1 #! @PERL@
      2 ##--------------------------------------------------------------------##
      3 ##--- Valgrind performance testing script                  vg_perf ---##
      4 ##--------------------------------------------------------------------##
      5 
      6 #  This file is part of Valgrind, a dynamic binary instrumentation
      7 #  framework.
      8 #
      9 #  Copyright (C) 2005 Nicholas Nethercote
     10 #     njn (at] valgrind.org
     11 #
     12 #  This program is free software; you can redistribute it and/or
     13 #  modify it under the terms of the GNU General Public License as
     14 #  published by the Free Software Foundation; either version 2 of the
     15 #  License, or (at your option) any later version.
     16 #
     17 #  This program is distributed in the hope that it will be useful, but
     18 #  WITHOUT ANY WARRANTY; without even the implied warranty of
     19 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     20 #  General Public License for more details.
     21 #
     22 #  You should have received a copy of the GNU General Public License
     23 #  along with this program; if not, write to the Free Software
     24 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
     25 #  02111-1307, USA.
     26 #
     27 #  The GNU General Public License is contained in the file COPYING.
     28 
     29 #----------------------------------------------------------------------------
     30 # usage: see usage message.
     31 #
     32 # You can specify individual files to test, or whole directories, or both.
     33 # Directories are traversed recursively, except for ones named, for example, 
     34 # CVS/ or docs/.
     35 #
     36 # Each test is defined in a file <test>.vgperf, containing one or more of the
     37 # following lines, in any order:
     38 #   - prog:   <prog to run>                         (compulsory)
     39 #   - args:   <args for prog>                       (default: none)
     40 #   - vgopts: <Valgrind options>                    (default: none)
     41 #   - prereq: <prerequisite command>                (default: none)
     42 #   - cleanup: <post-test cleanup cmd to run>       (default: none)
     43 #
     44 # The prerequisite command, if present, must return 0 otherwise the test is
     45 # skipped.
     46 #----------------------------------------------------------------------------
     47 
     48 use warnings;
     49 use strict;
     50 
     51 #----------------------------------------------------------------------------
     52 # Global vars
     53 #----------------------------------------------------------------------------
     54 my $usage = <<END
     55 usage: vg_perf [options] [files or dirs]
     56 
     57   options for the user, with defaults in [ ], are:
     58     -h --help             show this message
     59     --reps=<n>            number of repeats for each program [1]
     60     --tools=<t1,t2,t3>    tools to run [Nulgrind and Memcheck]
     61     --vg                  Valgrind(s) to measure (can be specified multiple
     62                             times).  The "in-place" build is used.
     63                             [Valgrind in the current directory]
     64 
     65   Any tools named in --tools must be present in all directories specified
     66   with --vg.  (This is not checked.)
     67 END
     68 ;
     69 
     70 # Test variables
     71 my $vgopts;             # valgrind options
     72 my $prog;               # test prog
     73 my $args;               # test prog args
     74 my $prereq;             # prerequisite test to satisfy before running test
     75 my $cleanup;            # cleanup command to run
     76 
     77 # Command line options
     78 my $n_reps = 1;         # Run each test $n_reps times and choose the best one.
     79 my @vgdirs;             # Dirs of the various Valgrinds being measured.
     80 my @tools = ("none", "memcheck");   # tools being measured
     81 
     82 my $num_tests_done   = 0;
     83 my $num_timings_done = 0;
     84 
     85 # Starting directory
     86 chomp(my $tests_dir = `pwd`);
     87 
     88 #----------------------------------------------------------------------------
     89 # Process command line, setup
     90 #----------------------------------------------------------------------------
     91 
     92 # If $prog is a relative path, it prepends $dir to it.  Useful for two reasons:
     93 #
     94 # 1. Can prepend "." onto programs to avoid trouble with users who don't have
     95 #    "." in their path (by making $dir = ".")
     96 # 2. Can prepend the current dir to make the command absolute to avoid
     97 #    subsequent trouble when we change directories.
     98 #
     99 # Also checks the program exists and is executable.
    100 sub validate_program ($$$$) 
    101 {
    102     my ($dir, $prog, $must_exist, $must_be_executable) = @_;
    103 
    104     # If absolute path, leave it alone.  If relative, make it
    105     # absolute -- by prepending current dir -- so we can change
    106     # dirs and still use it.
    107     $prog = "$dir/$prog" if ($prog !~ /^\//);
    108     if ($must_exist) {
    109         (-f $prog) or die "vg_perf: '$prog' not found or not a file ($dir)\n";
    110     }
    111     if ($must_be_executable) { 
    112         (-x $prog) or die "vg_perf: '$prog' not executable ($dir)\n";
    113     }
    114 
    115     return $prog;
    116 }
    117 
    118 sub add_vgdir($)
    119 {
    120     my ($vgdir) = @_;
    121     if ($vgdir !~ /^\//) { $vgdir = "$tests_dir/$vgdir"; }
    122     validate_program($vgdir, "./coregrind/valgrind", 1, 1);
    123     push(@vgdirs, $vgdir);
    124 }
    125 
    126 sub process_command_line() 
    127 {
    128     my @fs;
    129     
    130     for my $arg (@ARGV) {
    131         if ($arg =~ /^-/) {
    132             if ($arg =~ /^--reps=(\d+)$/) {
    133                 $n_reps = $1;
    134                 if ($n_reps < 1) { die "bad --reps value: $n_reps\n"; }
    135             } elsif ($arg =~ /^--vg=(.+)$/) {
    136                 # Make dir absolute if not already
    137                 add_vgdir($1);
    138             } elsif ($arg =~ /^--tools=(.+)$/) {
    139                 @tools = split(/,/, $1);
    140             } else {
    141                 die $usage;
    142             }
    143         } else {
    144             push(@fs, $arg);
    145         }
    146     }
    147 
    148     # If no --vg options were specified, use the current tree.
    149     if (0 == @vgdirs) {
    150         add_vgdir($tests_dir);
    151     }
    152 
    153     (0 != @fs) or die "No test files or directories specified\n";
    154 
    155     return @fs;
    156 }
    157 
    158 #----------------------------------------------------------------------------
    159 # Read a .vgperf file
    160 #----------------------------------------------------------------------------
    161 sub read_vgperf_file($)
    162 {
    163     my ($f) = @_;
    164 
    165     # Defaults.
    166     ($vgopts, $prog, $args, $prereq, $cleanup)
    167       = ("", undef, "", undef, undef, undef, undef);
    168 
    169     open(INPUTFILE, "< $f") || die "File $f not openable\n";
    170 
    171     while (my $line = <INPUTFILE>) {
    172         if      ($line =~ /^\s*#/ || $line =~ /^\s*$/) {
    173 	    next;
    174 	} elsif ($line =~ /^\s*vgopts:\s*(.*)$/) {
    175             $vgopts = $1;
    176         } elsif ($line =~ /^\s*prog:\s*(.*)$/) {
    177             $prog = validate_program(".", $1, 1, 1);
    178         } elsif ($line =~ /^\s*args:\s*(.*)$/) {
    179             $args = $1;
    180         } elsif ($line =~ /^\s*prereq:\s*(.*)$/) {
    181             $prereq = $1;
    182         } elsif ($line =~ /^\s*cleanup:\s*(.*)$/) {
    183             $cleanup = $1;
    184         } else {
    185             die "Bad line in $f: $line\n";
    186         }
    187     }
    188     close(INPUTFILE);
    189 
    190     if (!defined $prog) {
    191         $prog = "";     # allow no prog for testing error and --help cases
    192     }
    193     if (0 == @tools) {
    194         die "vg_perf: missing 'tools' line in $f\n";
    195     }
    196 }
    197 
    198 #----------------------------------------------------------------------------
    199 # Do one test
    200 #----------------------------------------------------------------------------
    201 # Since most of the program time is spent in system() calls, need this to
    202 # propagate a Ctrl-C enabling us to quit.
    203 sub mysystem($) 
    204 {
    205     my ($cmd) = @_;
    206     my $retval = system($cmd);
    207     if ($retval == 2) { 
    208         exit 1; 
    209     } else {
    210         return $retval;
    211     }
    212 }
    213 
    214 # Run program N times, return the best user time.  Use the POSIX
    215 # -p flag on /usr/bin/time so as to get something parseable on AIX.
    216 sub time_prog($$)
    217 {
    218     my ($cmd, $n) = @_;
    219     my $tmin = 999999;
    220     for (my $i = 0; $i < $n; $i++) {
    221         mysystem("echo '$cmd' > perf.cmd");
    222         my $retval = mysystem("$cmd > perf.stdout 2> perf.stderr");
    223         (0 == $retval) or 
    224             die "\n*** Command returned non-zero ($retval)"
    225               . "\n*** See perf.{cmd,stdout,stderr} to determine what went wrong.\n";
    226         my $out = `cat perf.stderr`;
    227         ($out =~ /[Uu]ser +([\d\.]+)/) or 
    228             die "\n*** missing usertime in perf.stderr\n";
    229         $tmin = $1 if ($1 < $tmin);
    230     }
    231     # Avoid divisions by zero!
    232     return (0 == $tmin ? 0.01 : $tmin);
    233 }
    234 
    235 sub do_one_test($$) 
    236 {
    237     my ($dir, $vgperf) = @_;
    238     $vgperf =~ /^(.*)\.vgperf/;
    239     my $name = $1;
    240     my %first_tTool;    # For doing percentage speedups when comparing
    241                         # multiple Valgrinds
    242 
    243     read_vgperf_file($vgperf);
    244 
    245     if (defined $prereq) {
    246         if (system("$prereq") != 0) {
    247             printf("%-16s (skipping, prereq failed: $prereq)\n", "$name:");
    248             return;
    249         }
    250     }
    251 
    252     my $timecmd = "/usr/bin/time -p";
    253 
    254     # Do the native run(s).
    255     printf("-- $name --\n") if (@vgdirs > 1);
    256     my $cmd     = "$timecmd $prog $args";
    257     my $tNative = time_prog($cmd, $n_reps);
    258 
    259     foreach my $vgdir (@vgdirs) {
    260         # Benchmark name
    261         printf("%-8s ", $name);
    262 
    263         # Print the Valgrind version if we are measuring more than one.
    264         my $vgdirname = $vgdir;
    265         chomp($vgdirname = `basename $vgdir`);
    266         printf("%-10s:", $vgdirname);
    267         
    268         # Native execution time
    269         printf("%4.2fs", $tNative);
    270 
    271         foreach my $tool (@tools) {
    272             # First two chars of toolname for abbreviation
    273             my $tool_abbrev = $tool;
    274             $tool_abbrev =~ s/(..).*/$1/;
    275 
    276             # Do the tool run(s).  Set both VALGRIND_LIB and VALGRIND_LIB_INNER
    277             # in case this Valgrind was configured with --enable-inner.  And
    278             # also VALGRINDLIB, which was the old name for the variable, to
    279             # allow comparison against old Valgrind versions (eg. 2.4.X).
    280             printf("  %s:", $tool_abbrev);
    281             my $vgsetup = "VALGRINDLIB=$vgdir/.in_place "
    282                         . "VALGRIND_LIB=$vgdir/.in_place "
    283                         . "VALGRIND_LIB_INNER=$vgdir/.in_place ";
    284             my $vgcmd   = "$vgdir/coregrind/valgrind "
    285                         . "--command-line-only=yes --tool=$tool -q "
    286                         . "--memcheck:leak-check=no "
    287                         . "--trace-children=yes "
    288                         . "$vgopts ";
    289             my $cmd     = "$vgsetup $timecmd $vgcmd $prog $args";
    290             my $tTool   = time_prog($cmd, $n_reps);
    291             printf("%4.1fs (%4.1fx,", $tTool, $tTool/$tNative);
    292 
    293             # If it's the first timing for this tool on this benchmark,
    294             # record the time so we can get the percentage speedup of the
    295             # subsequent Valgrinds.  Otherwise, compute and print
    296             # the speedup.
    297             if (not defined $first_tTool{$tool}) {
    298                 $first_tTool{$tool} = $tTool;
    299                 print(" -----)");
    300             } else {
    301                 my $speedup = 100 - (100 * $tTool / $first_tTool{$tool});
    302                 printf("%5.1f%%)", $speedup);
    303             }
    304 
    305             $num_timings_done++;
    306 
    307             if (defined $cleanup) {
    308                 (system("$cleanup") == 0) or 
    309                     print("  ($name cleanup operation failed: $cleanup)\n");
    310             }
    311         }
    312         printf("\n");
    313     }
    314 
    315     $num_tests_done++;
    316 }
    317 
    318 #----------------------------------------------------------------------------
    319 # Test one directory (and any subdirs)
    320 #----------------------------------------------------------------------------
    321 sub test_one_dir($$);    # forward declaration
    322 
    323 sub test_one_dir($$) 
    324 {
    325     my ($dir, $prev_dirs) = @_;
    326     $dir =~ s/\/$//;    # trim a trailing '/'
    327 
    328     chomp(my $initial_dir = `pwd`);     # record where we started
    329 
    330     # Ignore dirs into which we should not recurse.
    331     if ($dir =~ /^(BitKeeper|CVS|SCCS|docs|doc)$/) { return; }
    332 
    333     chdir($dir) or die "Could not change into $dir\n";
    334 
    335     # Nb: Don't prepend a '/' to the base directory
    336     my $full_dir = $prev_dirs . ($prev_dirs eq "" ? "" : "/") . $dir;
    337     my $dashes = "-" x (50 - length $full_dir);
    338 
    339     my @fs = glob "*";
    340     my $found_tests = (0 != (grep { $_ =~ /\.vgperf$/ } @fs));
    341 
    342     if ($found_tests) {
    343         print "-- Running  tests in $full_dir $dashes\n";
    344     }
    345     foreach my $f (@fs) {
    346         if (-d $f) {
    347             test_one_dir($f, $full_dir);
    348         } elsif ($f =~ /\.vgperf$/) {
    349             do_one_test($full_dir, $f);
    350         }
    351     }
    352     if ($found_tests) {
    353         print "-- Finished tests in $full_dir $dashes\n";
    354     }
    355 
    356     chdir("$initial_dir");
    357 }
    358 
    359 #----------------------------------------------------------------------------
    360 # Summarise results
    361 #----------------------------------------------------------------------------
    362 sub summarise_results 
    363 {
    364     printf("\n== %d programs, %d timings =================\n\n", 
    365            $num_tests_done, $num_timings_done);
    366 }
    367 
    368 #----------------------------------------------------------------------------
    369 # main()
    370 #----------------------------------------------------------------------------
    371 
    372 # nuke VALGRIND_OPTS
    373 $ENV{"VALGRIND_OPTS"} = "";
    374 
    375 my @fs = process_command_line();
    376 foreach my $f (@fs) {
    377     if (-d $f) {
    378         test_one_dir($f, "");
    379     } else { 
    380         # Allow the .vgperf suffix to be given or omitted
    381         if ($f =~ /.vgperf$/ && -r $f) {
    382             # do nothing
    383         } elsif (-r "$f.vgperf") {
    384             $f = "$f.vgperf";
    385         } else {
    386             die "`$f' neither a directory nor a readable test file/name\n"
    387         }
    388         my $dir  = `dirname  $f`;   chomp $dir;
    389         my $file = `basename $f`;   chomp $file;
    390         chdir($dir) or die "Could not change into $dir\n";
    391         do_one_test($dir, $file);
    392         chdir($tests_dir);
    393     }
    394 }
    395 summarise_results();
    396 
    397 ##--------------------------------------------------------------------##
    398 ##--- end                                                          ---##
    399 ##--------------------------------------------------------------------##
    400