1#! /usr/bin/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@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 53use warnings; 54use strict; 55 56#---------------------------------------------------------------------------- 57# Global vars 58#---------------------------------------------------------------------------- 59my $usage = <<END 60usage: 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 81END 82; 83 84# Test variables 85my $vgopts; # valgrind options 86my $prog; # test prog 87my $args; # test prog args 88my $prereq; # prerequisite test to satisfy before running test 89my $cleanup; # cleanup command to run 90 91# Command line options 92my $n_reps = 1; # Run each test $n_reps times and choose the best one. 93my @vgdirs; # Dirs of the various Valgrinds being measured. 94my @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 99my $outer_valgrind; 100my $outer_tool = "cachegrind"; 101my $outer_args; 102 103 104my $num_tests_done = 0; 105my $num_timings_done = 0; 106 107# Starting directory 108chomp(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. 122sub 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 140sub add_vgdir($) 141{ 142 my ($vgdir) = @_; 143 if ($vgdir !~ /^\//) { $vgdir = "$tests_dir/$vgdir"; } 144 push(@vgdirs, $vgdir); 145} 146 147sub 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#---------------------------------------------------------------------------- 188sub 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. 230sub 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. 243sub 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 268sub 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#---------------------------------------------------------------------------- 404sub test_one_dir($$); # forward declaration 405 406sub 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#---------------------------------------------------------------------------- 445sub 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#---------------------------------------------------------------------------- 454sub 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 465if ($ENV{"EXTRA_REGTEST_OPTS"}) { 466 print "\n"; 467 warn_about_EXTRA_REGTEST_OPTS(); 468} 469 470my @fs = process_command_line(); 471foreach 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} 490summarise_results(); 491 492if ($ENV{"EXTRA_REGTEST_OPTS"}) { 493 warn_about_EXTRA_REGTEST_OPTS(); 494} 495 496##--------------------------------------------------------------------## 497##--- end ---## 498##--------------------------------------------------------------------## 499