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-2017 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 # Sometimes it is useful to run all the tests at a high sanity check
     47 # level or with arbitrary other flags.  To make this simple, extra 
     48 # options, applied to all tests run, are read from $EXTRA_REGTEST_OPTS,
     49 # and handed to valgrind prior to any other flags specified by the 
     50 # .vgperf file. Note: the env var is the same as vg_regtest.
     51 #----------------------------------------------------------------------------
     52 
     53 use warnings;
     54 use strict;
     55 
     56 #----------------------------------------------------------------------------
     57 # Global vars
     58 #----------------------------------------------------------------------------
     59 my $usage = <<END
     60 usage: vg_perf [options] [files or dirs]
     61 
     62   options for the user, with defaults in [ ], are:
     63     -h --help             show this message
     64     --reps=<n>            number of repeats for each program [1]
     65     --tools=<t1,t2,t3>    tools to run [Nulgrind and Memcheck]
     66     --vg=<dir>            top-level directory containing Valgrind to measure
     67                           [Valgrind in the current directory, i.e. --vg=.]
     68                           Can be specified multiple times.
     69                           The "in-place" build is used.
     70 
     71     --outer-valgrind: run these Valgrind(s) under the given outer valgrind.
     72       These Valgrind(s) must be configured with --enable-inner.
     73     --outer-tool: tool to use by the outer valgrind (default cachegrind).
     74     --outer-args: use this as outer tool args. If the outer args are starting
     75       with +, the given outer args are appended to the outer args predefined
     76       by vg_perf.
     77 
     78   Any tools named in --tools must be present in all directories specified
     79   with --vg.  (This is not checked.)
     80   Use EXTRA_REGTEST_OPTS to supply extra args for all tests
     81 END
     82 ;
     83 
     84 # Test variables
     85 my $vgopts;             # valgrind options
     86 my $prog;               # test prog
     87 my $args;               # test prog args
     88 my $prereq;             # prerequisite test to satisfy before running test
     89 my $cleanup;            # cleanup command to run
     90 
     91 # Command line options
     92 my $n_reps = 1;         # Run each test $n_reps times and choose the best one.
     93 my @vgdirs;             # Dirs of the various Valgrinds being measured.
     94 my @tools = ("none", "memcheck");   # tools being measured
     95 
     96 # Outer valgrind to use, and args to use for it.
     97 # If this is set, --valgrind should be set to the installed inner valgrind,
     98 # and --valgrind-lib will be ignore
     99 my $outer_valgrind;
    100 my $outer_tool = "cachegrind";
    101 my $outer_args;
    102 
    103 
    104 my $num_tests_done   = 0;
    105 my $num_timings_done = 0;
    106 
    107 # Starting directory
    108 chomp(my $tests_dir = `pwd`);
    109 
    110 #----------------------------------------------------------------------------
    111 # Process command line, setup
    112 #----------------------------------------------------------------------------
    113 
    114 # If $prog is a relative path, it prepends $dir to it.  Useful for two reasons:
    115 #
    116 # 1. Can prepend "." onto programs to avoid trouble with users who don't have
    117 #    "." in their path (by making $dir = ".")
    118 # 2. Can prepend the current dir to make the command absolute to avoid
    119 #    subsequent trouble when we change directories.
    120 #
    121 # Also checks the program exists and is executable.
    122 sub validate_program ($$$$) 
    123 {
    124     my ($dir, $prog, $must_exist, $must_be_executable) = @_;
    125 
    126     # If absolute path, leave it alone.  If relative, make it
    127     # absolute -- by prepending current dir -- so we can change
    128     # dirs and still use it.
    129     $prog = "$dir/$prog" if ($prog !~ /^\//);
    130     if ($must_exist) {
    131         (-f $prog) or die "vg_perf: '$prog' not found or not a file ($dir)\n";
    132     }
    133     if ($must_be_executable) { 
    134         (-x $prog) or die "vg_perf: '$prog' not executable ($dir)\n";
    135     }
    136 
    137     return $prog;
    138 }
    139 
    140 sub add_vgdir($)
    141 {
    142     my ($vgdir) = @_;
    143     if ($vgdir !~ /^\//) { $vgdir = "$tests_dir/$vgdir"; }
    144     push(@vgdirs, $vgdir);
    145 }
    146 
    147 sub process_command_line() 
    148 {
    149     my @fs;
    150     
    151     for my $arg (@ARGV) {
    152         if ($arg =~ /^-/) {
    153             if ($arg =~ /^--reps=(\d+)$/) {
    154                 $n_reps = $1;
    155                 if ($n_reps < 1) { die "bad --reps value: $n_reps\n"; }
    156             } elsif ($arg =~ /^--vg=(.+)$/) {
    157                 # Make dir absolute if not already
    158                 add_vgdir($1);
    159             } elsif ($arg =~ /^--tools=(.+)$/) {
    160                 @tools = split(/,/, $1);
    161             } elsif ($arg =~ /^--outer-valgrind=(.*)$/) {
    162                 $outer_valgrind = $1;
    163             } elsif ($arg =~ /^--outer-tool=(.*)$/) {
    164                 $outer_tool = $1;
    165             } elsif ($arg =~ /^--outer-args=(.*)$/) {
    166                 $outer_args = $1;
    167             } else {
    168                 die $usage;
    169             }
    170         } else {
    171             push(@fs, $arg);
    172         }
    173     }
    174 
    175     # If no --vg options were specified, use the current tree.
    176     if (0 == @vgdirs) {
    177         add_vgdir($tests_dir);
    178     }
    179 
    180     (0 != @fs) or die "No test files or directories specified\n";
    181 
    182     return @fs;
    183 }
    184 
    185 #----------------------------------------------------------------------------
    186 # Read a .vgperf file
    187 #----------------------------------------------------------------------------
    188 sub read_vgperf_file($)
    189 {
    190     my ($f) = @_;
    191 
    192     # Defaults.
    193     ($vgopts, $prog, $args, $prereq, $cleanup)
    194       = ("", undef, "", undef, undef, undef, undef);
    195 
    196     open(INPUTFILE, "< $f") || die "File $f not openable\n";
    197 
    198     while (my $line = <INPUTFILE>) {
    199         if      ($line =~ /^\s*#/ || $line =~ /^\s*$/) {
    200 	    next;
    201 	} elsif ($line =~ /^\s*vgopts:\s*(.*)$/) {
    202             $vgopts = $1;
    203         } elsif ($line =~ /^\s*prog:\s*(.*)$/) {
    204             $prog = validate_program(".", $1, 1, 1);
    205         } elsif ($line =~ /^\s*args:\s*(.*)$/) {
    206             $args = $1;
    207         } elsif ($line =~ /^\s*prereq:\s*(.*)$/) {
    208             $prereq = $1;
    209         } elsif ($line =~ /^\s*cleanup:\s*(.*)$/) {
    210             $cleanup = $1;
    211         } else {
    212             die "Bad line in $f: $line\n";
    213         }
    214     }
    215     close(INPUTFILE);
    216 
    217     if (!defined $prog) {
    218         $prog = "";     # allow no prog for testing error and --help cases
    219     }
    220     if (0 == @tools) {
    221         die "vg_perf: missing 'tools' line in $f\n";
    222     }
    223 }
    224 
    225 #----------------------------------------------------------------------------
    226 # Do one test
    227 #----------------------------------------------------------------------------
    228 # Since most of the program time is spent in system() calls, need this to
    229 # propagate a Ctrl-C enabling us to quit.
    230 sub mysystem($) 
    231 {
    232     my ($cmd) = @_;
    233     my $retval = system($cmd);
    234     if ($retval == 2) { 
    235         exit 1; 
    236     } else {
    237         return $retval;
    238     }
    239 }
    240 
    241 # Run program N times, return the best user time.  Use the POSIX
    242 # -p flag on /usr/bin/time so as to get something parseable on AIX.
    243 sub time_prog($$)
    244 {
    245     my ($cmd, $n) = @_;
    246     my $tmin = 999999;
    247     for (my $i = 0; $i < $n; $i++) {
    248         mysystem("echo '$cmd' > perf.cmd");
    249         my $retval = mysystem("$cmd > perf.stdout 2> perf.stderr");
    250         (0 == $retval) or 
    251             die "\n*** Command returned non-zero ($retval)"
    252               . "\n*** See perf.{cmd,stdout,stderr} to determine what went wrong.\n";
    253         my $out = `cat perf.stderr`;
    254         ($out =~ /[Uu]ser +([\d\.]+)/) or 
    255             die "\n*** missing usertime in perf.stderr\n";
    256         $tmin = $1 if ($1 < $tmin);
    257     }
    258 
    259     # Successful run; cleanup
    260     unlink("perf.cmd");
    261     unlink("perf.stderr");
    262     unlink("perf.stdout");
    263 
    264     # Avoid divisions by zero!
    265     return (0 == $tmin ? 0.01 : $tmin);
    266 }
    267 
    268 sub do_one_test($$) 
    269 {
    270     my ($dir, $vgperf) = @_;
    271     $vgperf =~ /^(.*)\.vgperf/;
    272     my $name = $1;
    273     my %first_tTool;    # For doing percentage speedups when comparing
    274                         # multiple Valgrinds
    275 
    276     read_vgperf_file($vgperf);
    277 
    278     if (defined $prereq) {
    279         if (system("$prereq") != 0) {
    280             printf("%-16s (skipping, prereq failed: $prereq)\n", "$name:");
    281             return;
    282         }
    283     }
    284 
    285     my $timecmd = "/usr/bin/time -p";
    286 
    287     # Do the native run(s).
    288     printf("-- $name --\n") if (@vgdirs > 1);
    289     my $cmd     = "$timecmd $prog $args";
    290     my $tNative = time_prog($cmd, $n_reps);
    291 
    292     if (defined $outer_valgrind) {
    293         $outer_valgrind = validate_program($tests_dir, $outer_valgrind, 1, 1);
    294         foreach my $vgdir (@vgdirs) {
    295             validate_program($vgdir, "./coregrind/valgrind", 1, 1);
    296         }
    297     } else {
    298         foreach my $vgdir (@vgdirs) {
    299             validate_program($vgdir, "./coregrind/valgrind", 1, 1);
    300         }
    301     }
    302 
    303     # Pull any extra options (for example, --sanity-level=4)
    304     # from $EXTRA_REGTEST_OPTS.
    305     my $maybe_extraopts = $ENV{"EXTRA_REGTEST_OPTS"};
    306     my $extraopts = $maybe_extraopts ?  $maybe_extraopts  : "";
    307 
    308     foreach my $vgdir (@vgdirs) {
    309         # Benchmark name
    310         printf("%-8s ", $name);
    311 
    312         # Print the Valgrind version if we are measuring more than one.
    313         my $vgdirname = $vgdir;
    314         chomp($vgdirname = `basename $vgdir`);
    315         printf("%-10s:", $vgdirname);
    316         
    317         # Native execution time
    318         printf("%4.2fs", $tNative);
    319 
    320         foreach my $tool (@tools) {
    321             # First two chars of toolname for abbreviation
    322             my $tool_abbrev = $tool;
    323             $tool_abbrev =~ s/(..).*/$1/;
    324             printf("  %s:", $tool_abbrev);
    325             my $run_outer_args = "";
    326             if ((not defined $outer_args) || ($outer_args =~ /^\+/)) {
    327                 $run_outer_args = 
    328                       " -v --command-line-only=yes"
    329                     . " --run-libc-freeres=no --sim-hints=enable-outer"
    330                     . " --smc-check=all-non-file"
    331                     . " --vgdb=no --trace-children=yes --read-var-info=no"
    332                     . " --suppressions=../tests/outer_inner.supp"
    333                     . " --memcheck:leak-check=full --memcheck:show-reachable=no"
    334                     . " --cachegrind:cache-sim=yes --cachegrind:branch-sim=yes"
    335                     . " --cachegrind:cachegrind-out-file=cachegrind.out.$vgdirname.$tool_abbrev.$name.%p"
    336                     . " --callgrind:cache-sim=yes --callgrind:branch-sim=yes"
    337                     . " --callgrind:dump-instr=yes --callgrind:collect-jumps=yes"
    338                     . " --callgrind:callgrind-out-file=callgrind.out.$vgdirname.$tool_abbrev.$name.%p"
    339                     . " ";
    340                  if (defined $outer_args) {
    341                     $outer_args =~ s/^\+(.*)/$1/;
    342                     $run_outer_args = $run_outer_args . $outer_args;
    343                  }
    344             } else {
    345                 $run_outer_args = $outer_args;
    346             }
    347 
    348             my $vgsetup = "";
    349             my $vgcmd   = "$vgdir/coregrind/valgrind "
    350                         . "--command-line-only=yes --tool=$tool  $extraopts -q "
    351                         . "--memcheck:leak-check=no "
    352                         . "--trace-children=yes "
    353                         . "$vgopts ";
    354             # Do the tool run(s).
    355             if (defined $outer_valgrind ) {
    356                 # in an outer-inner setup, only set VALGRIND_LIB_INNER
    357                 $vgsetup = "VALGRIND_LIB_INNER=$vgdir/.in_place ";
    358                 $vgcmd   = "$outer_valgrind "
    359                          . "--tool=" . $outer_tool . " "
    360                          . "--log-file=" . "$outer_tool.outer.log.$vgdirname.$tool_abbrev.$name.%p "
    361                          . "$run_outer_args "
    362                          . $vgcmd;
    363             } else {
    364                 # Set both VALGRIND_LIB and VALGRIND_LIB_INNER
    365                 # in case this Valgrind was configured with --enable-inner.  And
    366                 # also VALGRINDLIB, which was the old name for the variable, to
    367                 # allow comparison against old Valgrind versions (eg. 2.4.X).
    368                 $vgsetup = "VALGRINDLIB=$vgdir/.in_place "
    369                          . "VALGRIND_LIB=$vgdir/.in_place "
    370                          . "VALGRIND_LIB_INNER=$vgdir/.in_place ";
    371             }
    372             my $cmd     = "$vgsetup $timecmd $vgcmd $prog $args";
    373             my $tTool   = time_prog($cmd, $n_reps);
    374             printf("%4.1fs (%4.1fx,", $tTool, $tTool/$tNative);
    375 
    376             # If it's the first timing for this tool on this benchmark,
    377             # record the time so we can get the percentage speedup of the
    378             # subsequent Valgrinds.  Otherwise, compute and print
    379             # the speedup.
    380             if (not defined $first_tTool{$tool}) {
    381                 $first_tTool{$tool} = $tTool;
    382                 print(" -----)");
    383             } else {
    384                 my $speedup = 100 - (100 * $tTool / $first_tTool{$tool});
    385                 printf("%5.1f%%)", $speedup);
    386             }
    387 
    388             $num_timings_done++;
    389 
    390             if (defined $cleanup) {
    391                 (system("$cleanup") == 0) or 
    392                     print("  ($name cleanup operation failed: $cleanup)\n");
    393             }
    394         }
    395         printf("\n");
    396     }
    397 
    398     $num_tests_done++;
    399 }
    400 
    401 #----------------------------------------------------------------------------
    402 # Test one directory (and any subdirs)
    403 #----------------------------------------------------------------------------
    404 sub test_one_dir($$);    # forward declaration
    405 
    406 sub test_one_dir($$) 
    407 {
    408     my ($dir, $prev_dirs) = @_;
    409     $dir =~ s/\/$//;    # trim a trailing '/'
    410 
    411     chomp(my $initial_dir = `pwd`);     # record where we started
    412 
    413     # Ignore dirs into which we should not recurse.
    414     if ($dir =~ /^(BitKeeper|CVS|SCCS|docs|doc)$/) { return; }
    415 
    416     chdir($dir) or die "Could not change into $dir\n";
    417 
    418     # Nb: Don't prepend a '/' to the base directory
    419     my $full_dir = $prev_dirs . ($prev_dirs eq "" ? "" : "/") . $dir;
    420     my $dashes = "-" x (50 - length $full_dir);
    421 
    422     my @fs = glob "*";
    423     my $found_tests = (0 != (grep { $_ =~ /\.vgperf$/ } @fs));
    424 
    425     if ($found_tests) {
    426         print "-- Running  tests in $full_dir $dashes\n";
    427     }
    428     foreach my $f (@fs) {
    429         if (-d $f) {
    430             test_one_dir($f, $full_dir);
    431         } elsif ($f =~ /\.vgperf$/) {
    432             do_one_test($full_dir, $f);
    433         }
    434     }
    435     if ($found_tests) {
    436         print "-- Finished tests in $full_dir $dashes\n";
    437     }
    438 
    439     chdir("$initial_dir");
    440 }
    441 
    442 #----------------------------------------------------------------------------
    443 # Summarise results
    444 #----------------------------------------------------------------------------
    445 sub summarise_results 
    446 {
    447     printf("\n== %d programs, %d timings =================\n\n", 
    448            $num_tests_done, $num_timings_done);
    449 }
    450 
    451 #----------------------------------------------------------------------------
    452 # main()
    453 #----------------------------------------------------------------------------
    454 sub warn_about_EXTRA_REGTEST_OPTS()
    455 {
    456     print "WARNING: \$EXTRA_REGTEST_OPTS is set.  You probably don't want\n";
    457     print "to run the perf tests with it set, unless you are doing some\n";
    458     print "strange experiment, and/or you really know what you are doing.\n";
    459     print "\n";
    460 }
    461 
    462 # nuke VALGRIND_OPTS
    463 $ENV{"VALGRIND_OPTS"} = "";
    464 
    465 if ($ENV{"EXTRA_REGTEST_OPTS"}) {
    466     print "\n";
    467     warn_about_EXTRA_REGTEST_OPTS();
    468 }
    469 
    470 my @fs = process_command_line();
    471 foreach my $f (@fs) {
    472     if (-d $f) {
    473         test_one_dir($f, "");
    474     } else { 
    475         # Allow the .vgperf suffix to be given or omitted
    476         if ($f =~ /.vgperf$/ && -r $f) {
    477             # do nothing
    478         } elsif (-r "$f.vgperf") {
    479             $f = "$f.vgperf";
    480         } else {
    481             die "`$f' neither a directory nor a readable test file/name\n"
    482         }
    483         my $dir  = `dirname  $f`;   chomp $dir;
    484         my $file = `basename $f`;   chomp $file;
    485         chdir($dir) or die "Could not change into $dir\n";
    486         do_one_test($dir, $file);
    487         chdir($tests_dir);
    488     }
    489 }
    490 summarise_results();
    491 
    492 if ($ENV{"EXTRA_REGTEST_OPTS"}) {
    493     warn_about_EXTRA_REGTEST_OPTS();
    494 }
    495 
    496 ##--------------------------------------------------------------------##
    497 ##--- end                                                          ---##
    498 ##--------------------------------------------------------------------##
    499