summaryrefslogtreecommitdiff
path: root/perf
diff options
context:
space:
mode:
authornjn <njn@a5019735-40e9-0310-863c-91ae7b9d1cf9>2005-12-10 23:11:28 +0000
committernjn <njn@a5019735-40e9-0310-863c-91ae7b9d1cf9>2005-12-10 23:11:28 +0000
commitec0c27a40ae0777cc5c5f96f219d1d72cbcaafd6 (patch)
tree114d468c48914cd3926c8fec24e3a9eadd70171e /perf
parent932d5ed2053e80768149a0baa220a51c60889e03 (diff)
First attempt at some performance tracking tools. Includes a script vg_perf
(use "make perf" to run) that executes test programs and times their slowdowns under various tools. It works a lot like the vg_regtest script. It's a bit rough around the edges -- eg. you can't currently directly compare two different versions of Valgrind, which would be useful -- but it is a good start. There are currently two test programs in perf/. More will be added as time goes on. This stuff will be built on so that performance changes can be tracked over time. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@5323 a5019735-40e9-0310-863c-91ae7b9d1cf9
Diffstat (limited to 'perf')
-rw-r--r--perf/Makefile.am17
-rw-r--r--perf/ffbench.c382
-rw-r--r--perf/ffbench.vgperf2
-rw-r--r--perf/sarp.c46
-rw-r--r--perf/sarp.vgperf2
-rw-r--r--perf/vg_perf.in368
6 files changed, 817 insertions, 0 deletions
diff --git a/perf/Makefile.am b/perf/Makefile.am
new file mode 100644
index 00000000..d703a679
--- /dev/null
+++ b/perf/Makefile.am
@@ -0,0 +1,17 @@
+
+noinst_SCRIPTS = vg_perf
+
+EXTRA_DIST = $(noinst_SCRIPTS) \
+ ffbench.vgperf \
+ sarp.vgperf
+
+check_PROGRAMS = \
+ ffbench sarp
+
+AM_CFLAGS = $(WERROR) -Winline -Wall -Wshadow -g -O
+AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CXXFLAGS = $(AM_CFLAGS)
+
+# Extra stuff
+ffbench_LDADD = -lm
+
diff --git a/perf/ffbench.c b/perf/ffbench.c
new file mode 100644
index 00000000..e7e35233
--- /dev/null
+++ b/perf/ffbench.c
@@ -0,0 +1,382 @@
+// This small program computes a Fast Fourier Transform. It tests
+// Valgrind's handling of FP operations. It is representative of all
+// programs that do a lot of FP operations.
+
+// This program was taken from http://www.fourmilab.ch/. The front page of
+// that site says:
+//
+// "Except for a few clearly-marked exceptions, all the material on this
+// site is in the public domain and may be used in any manner without
+// permission, restriction, attribution, or compensation."
+
+/*
+
+ Two-dimensional FFT benchmark
+
+ Designed and implemented by John Walker in April of 1989.
+
+ This benchmark executes a specified number of passes (default
+ 20) through a loop in which each iteration performs a fast
+ Fourier transform of a square matrix (default size 256x256) of
+ complex numbers (default precision double), followed by the
+ inverse transform. After all loop iterations are performed
+ the results are checked against known correct values.
+
+ This benchmark is intended for use on C implementations which
+ define "int" as 32 bits or longer and permit allocation and
+ direct addressing of arrays larger than one megabyte.
+
+ If CAPOUT is defined, the result after all iterations is
+ written as a CA Lab pattern file. This is intended for
+ debugging in case horribly wrong results are obtained on a
+ given machine.
+
+ Archival timings are run with the definitions below set as
+ follows: Float = double, Asize = 256, Passes = 20, CAPOUT not
+ defined.
+
+ Time (seconds) System
+
+ 2393.93 Sun 3/260, SunOS 3.4, C, "-f68881 -O".
+ (John Walker).
+
+ 1928 Macintosh IIx, MPW C 3.0, "-mc68020
+ -mc68881 -elems881 -m". (Hugh Hoover).
+
+ 1636.1 Sun 4/110, "cc -O3 -lm". (Michael McClary).
+ The suspicion is that this is software
+ floating point.
+
+ 1556.7 Macintosh II, A/UX, "cc -O -lm"
+ (Michael McClary).
+
+ 1388.8 Sun 386i/250, SunOS 4.0.1 C
+ "-O /usr/lib/trig.il". (James Carrington).
+
+ 1331.93 Sun 3/60, SunOS 4.0.1, C,
+ "-O4 -f68881 /usr/lib/libm.il"
+ (Bob Elman).
+
+ 1204.0 Apollo Domain DN4000, C, "-cpu 3000 -opt 4".
+ (Sam Crupi).
+
+ 1174.66 Compaq 386/25, SCO Xenix 386 C.
+ (Peter Shieh).
+
+ 1068 Compaq 386/25, SCO Xenix 386,
+ Metaware High C. (Robert Wenig).
+
+ 1064.0 Sun 3/80, SunOS 4.0.3 Beta C
+ "-O3 -f68881 /usr/lib/libm.il". (James Carrington).
+
+ 1061.4 Compaq 386/25, SCO Xenix, High C 1.4.
+ (James Carrington).
+
+ 1059.79 Compaq 386/25, 387/25, High C 1.4,
+ DOS|Extender 2.2, 387 inline code
+ generation. (Nathan Bender).
+
+ 777.14 Compaq 386/25, IIT 3C87-25 (387 Compatible),
+ High C 1.5, DOS|Extender 2.2, 387 inline
+ code generation. (Nathan Bender).
+
+ 751 Compaq DeskPro 386/33, High C 1.5 + DOS|Extender,
+ 387 code generation. (James Carrington).
+
+ 431.44 Compaq 386/25, Weitek 3167-25, DOS 3.31,
+ High C 1.4, DOS|Extender, Weitek code generation.
+ (Nathan Bender).
+
+ 344.9 Compaq 486/25, Metaware High C 1.6, Phar Lap
+ DOS|Extender, in-line floating point. (Nathan
+ Bender).
+
+ 324.2 Data General Motorola 88000, 16 Mhz, Gnu C.
+
+ 323.1 Sun 4/280, C, "-O4". (Eric Hill).
+
+ 254 Compaq SystemPro 486/33, High C 1.5 + DOS|Extender,
+ 387 code generation. (James Carrington).
+
+ 242.8 Silicon Graphics Personal IRIS, MIPS R2000A,
+ 12.5 Mhz, "-O3" (highest level optimisation).
+ (Mike Zentner).
+
+ 233.0 Sun SPARCStation 1, C, "-O4", SunOS 4.0.3.
+ (Nathan Bender).
+
+ 187.30 DEC PMAX 3100, MIPS 2000 chip.
+ (Robert Wenig).
+
+ 120.46 Sun SparcStation 2, C, "-O4", SunOS 4.1.1.
+ (John Walker).
+
+ 120.21 DEC 3MAX, MIPS 3000, "-O4".
+
+ 98.0 Intel i860 experimental environment,
+ OS/2, data caching disabled. (Kern
+ Sibbald).
+
+ 34.9 Silicon Graphics Indigo², MIPS R4400,
+ 175 Mhz, IRIX 5.2, "-O".
+
+ 32.4 Pentium 133, Windows NT, Microsoft Visual
+ C++ 4.0.
+
+ 17.25 Silicon Graphics Indigo², MIPS R4400,
+ 175 Mhz, IRIX 6.5, "-O3".
+
+ 14.10 Dell Dimension XPS R100, Pentium II 400 MHz,
+ Windows 98, Microsoft Visual C 5.0.
+
+ 10.7 Hewlett-Packard Kayak XU 450Mhz Pentium II,
+ Microsoft Visual C++ 6.0, Windows NT 4.0sp3. (Nathan Bender).
+
+ 5.09 Sun Ultra 2, UltraSPARC V9, 300 MHz, gcc -O3.
+
+ 0.846 Dell Inspiron 9100, Pentium 4, 3.4 GHz, gcc -O3.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+/* The program may be run with Float defined as either float or
+ double. With IEEE arithmetic, the same answers are generated for
+ either floating point mode. */
+
+#define Float double /* Floating point type used in FFT */
+
+#define Asize 256 /* Array edge size */
+#define Passes 20 /* Number of FFT/Inverse passes */
+
+#define max(a,b) ((a)>(b)?(a):(b))
+#define min(a,b) ((a)<=(b)?(a):(b))
+
+#ifndef unix
+#ifndef WIN32
+extern char *farmalloc(long s);
+#define malloc(x) farmalloc(x)
+#endif
+#define FWMODE "wb"
+#else
+#define FWMODE "w"
+#endif
+
+/*
+
+ Multi-dimensional fast Fourier transform
+
+ Adapted from Press et al., "Numerical Recipes in C".
+
+*/
+
+#define SWAP(a,b) tempr=(a); (a)=(b); (b)=tempr
+
+static void fourn(data, nn, ndim, isign)
+ Float data[];
+ int nn[], ndim, isign;
+{
+ register int i1, i2, i3;
+ int i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2;
+ int ibit, idim, k1, k2, n, nprev, nrem, ntot;
+ Float tempi, tempr;
+ double theta, wi, wpi, wpr, wr, wtemp;
+
+ ntot = 1;
+ for (idim = 1; idim <= ndim; idim++)
+ ntot *= nn[idim];
+ nprev = 1;
+ for (idim = ndim; idim >= 1; idim--) {
+ n = nn[idim];
+ nrem = ntot / (n * nprev);
+ ip1 = nprev << 1;
+ ip2 = ip1 * n;
+ ip3 = ip2 * nrem;
+ i2rev = 1;
+ for (i2 = 1; i2 <= ip2; i2 += ip1) {
+ if (i2 < i2rev) {
+ for (i1 = i2; i1 <= i2 + ip1 - 2; i1 += 2) {
+ for (i3 = i1; i3 <= ip3; i3 += ip2) {
+ i3rev = i2rev + i3 - i2;
+ SWAP(data[i3], data[i3rev]);
+ SWAP(data[i3 + 1], data[i3rev + 1]);
+ }
+ }
+ }
+ ibit = ip2 >> 1;
+ while (ibit >= ip1 && i2rev > ibit) {
+ i2rev -= ibit;
+ ibit >>= 1;
+ }
+ i2rev += ibit;
+ }
+ ifp1 = ip1;
+ while (ifp1 < ip2) {
+ ifp2 = ifp1 << 1;
+ theta = isign * 6.28318530717959 / (ifp2 / ip1);
+ wtemp = sin(0.5 * theta);
+ wpr = -2.0 * wtemp * wtemp;
+ wpi = sin(theta);
+ wr = 1.0;
+ wi = 0.0;
+ for (i3 = 1; i3 <= ifp1; i3 += ip1) {
+ for (i1 = i3; i1 <= i3 + ip1 - 2; i1 += 2) {
+ for (i2 = i1; i2 <= ip3; i2 += ifp2) {
+ k1 = i2;
+ k2 = k1 + ifp1;
+ tempr = wr * data[k2] - wi * data[k2 + 1];
+ tempi = wr * data[k2 + 1] + wi * data[k2];
+ data[k2] = data[k1] - tempr;
+ data[k2 + 1] = data[k1 + 1] - tempi;
+ data[k1] += tempr;
+ data[k1 + 1] += tempi;
+ }
+ }
+ wr = (wtemp = wr) * wpr - wi * wpi + wr;
+ wi = wi * wpr + wtemp * wpi + wi;
+ }
+ ifp1 = ifp2;
+ }
+ nprev *= n;
+ }
+}
+#undef SWAP
+
+int main()
+{
+ int i, j, k, l, m, npasses = Passes, faedge;
+ Float *fdata /* , *fd */ ;
+ static int nsize[] = {0, 0, 0};
+ long fanum, fasize;
+ double mapbase, mapscale, /* x, */ rmin, rmax, imin, imax;
+
+ faedge = Asize; /* FFT array edge size */
+ fanum = faedge * faedge; /* Elements in FFT array */
+ fasize = ((fanum + 1) * 2 * sizeof(Float)); /* FFT array size */
+ nsize[1] = nsize[2] = faedge;
+
+ fdata = (Float *) malloc(fasize);
+ if (fdata == NULL) {
+ fprintf(stdout, "Can't allocate data array.\n");
+ exit(1);
+ }
+
+ /* Generate data array to process. */
+
+#define Re(x,y) fdata[1 + (faedge * (x) + (y)) * 2]
+#define Im(x,y) fdata[2 + (faedge * (x) + (y)) * 2]
+
+ memset(fdata, 0, fasize);
+ for (i = 0; i < faedge; i++) {
+ for (j = 0; j < faedge; j++) {
+ if (((i & 15) == 8) || ((j & 15) == 8))
+ Re(i, j) = 128.0;
+ }
+ }
+
+ for (i = 0; i < npasses; i++) {
+/*printf("Pass %d\n", i);*/
+ /* Transform image to frequency domain. */
+ fourn(fdata, nsize, 2, 1);
+
+ /* Back-transform to image. */
+ fourn(fdata, nsize, 2, -1);
+ }
+
+ {
+ double r, ij, ar, ai;
+ rmin = 1e10; rmax = -1e10;
+ imin = 1e10; imax = -1e10;
+ ar = 0;
+ ai = 0;
+
+ for (i = 1; i <= fanum; i += 2) {
+ r = fdata[i];
+ ij = fdata[i + 1];
+ ar += r;
+ ai += ij;
+ rmin = min(r, rmin);
+ rmax = max(r, rmax);
+ imin = min(ij, imin);
+ imax = max(ij, imax);
+ }
+#ifdef DEBUG
+ printf("Real min %.4g, max %.4g. Imaginary min %.4g, max %.4g.\n",
+ rmin, rmax, imin, imax);
+ printf("Average real %.4g, imaginary %.4g.\n",
+ ar / fanum, ai / fanum);
+#endif
+ mapbase = rmin;
+ mapscale = 255 / (rmax - rmin);
+ }
+
+ /* See if we got the right answers. */
+
+ m = 0;
+ for (i = 0; i < faedge; i++) {
+ for (j = 0; j < faedge; j++) {
+ k = (Re(i, j) - mapbase) * mapscale;
+ l = (((i & 15) == 8) || ((j & 15) == 8)) ? 255 : 0;
+ if (k != l) {
+ m++;
+ fprintf(stdout,
+ "Wrong answer at (%d,%d)! Expected %d, got %d.\n",
+ i, j, l, k);
+ }
+ }
+ }
+ if (m == 0) {
+ fprintf(stdout, "%d passes. No errors in results.\n", npasses);
+ } else {
+ fprintf(stdout, "%d passes. %d errors in results.\n",
+ npasses, m);
+ }
+
+#ifdef CAPOUT
+
+ /* Output the result of the transform as a CA Lab pattern
+ file for debugging. */
+
+ {
+#define SCRX 322
+#define SCRY 200
+#define SCRN (SCRX * SCRY)
+ unsigned char patarr[SCRY][SCRX];
+ FILE *fp;
+
+/* Map user external state numbers to internal state index */
+
+#define UtoI(x) (((((x) >> 1) & 0x7F) | ((x) << 7)) & 0xFF)
+
+ /* Copy data from FFT buffer to map. */
+
+ memset(patarr, 0, sizeof patarr);
+ l = (SCRX - faedge) / 2;
+ m = (faedge > SCRY) ? 0 : ((SCRY - faedge) / 2);
+ for (i = 1; i < faedge; i++) {
+ for (j = 0; j < min(SCRY, faedge); j++) {
+ k = (Re(i, j) - mapbase) * mapscale;
+ patarr[j + m][i + l] = UtoI(k);
+ }
+ }
+
+ /* Dump pattern map to file. */
+
+ fp = fopen("fft.cap", "w");
+ if (fp == NULL) {
+ fprintf(stdout, "Cannot open output file.\n");
+ exit(0);
+ }
+ putc(':', fp);
+ putc(1, fp);
+ fwrite(patarr, SCRN, 1, fp);
+ putc(6, fp);
+ fclose(fp);
+ }
+#endif
+
+ return 0;
+}
diff --git a/perf/ffbench.vgperf b/perf/ffbench.vgperf
new file mode 100644
index 00000000..98dae55c
--- /dev/null
+++ b/perf/ffbench.vgperf
@@ -0,0 +1,2 @@
+prog: ffbench
+tools: none memcheck
diff --git a/perf/sarp.c b/perf/sarp.c
new file mode 100644
index 00000000..04518d61
--- /dev/null
+++ b/perf/sarp.c
@@ -0,0 +1,46 @@
+// This artificial program allocates and deallocates a lot of large objects
+// on the stack. It is a stress test for Memcheck's set_address_range_perms
+// (sarp) function. Pretty much all Valgrind versions up to 3.1.X do very
+// badly on it, ie. a slowdown of at least 100x.
+//
+// It is representative of tsim_arch, the simulator for the University of
+// Texas's TRIPS processor, whose performance under Valgrind is dominated by
+// the handling of one frequently-called function that allocates 8348 bytes
+// on the stack.
+
+#include <assert.h>
+#include <time.h>
+
+#define REPS 1000*1000
+
+int f(int i)
+{
+ // This nonsense is just to ensure that the compiler does not optimise
+ // away the stack allocation.
+ char big_array[8348];
+ big_array[0] = 12;
+ big_array[2333] = 34;
+ big_array[5678] = 56;
+ big_array[8347] = 78;
+ assert( 8000 == (&big_array[8100] - &big_array[100]) );
+ return big_array[i];
+}
+
+int main(void)
+{
+ int i, sum = 0;
+
+ struct timespec req;
+ req.tv_sec = 0;
+ req.tv_nsec = 100*1000*1000; // 0.1s
+
+ // Pause for a bit so that the native run-time is not 0.00, which leads
+ // to ridiculous slow-down figures.
+ nanosleep(&req, NULL);
+
+ for (i = 0; i < REPS; i++) {
+ sum += f(i & 0xff);
+ }
+ return sum % 256;
+}
+
diff --git a/perf/sarp.vgperf b/perf/sarp.vgperf
new file mode 100644
index 00000000..83c634a2
--- /dev/null
+++ b/perf/sarp.vgperf
@@ -0,0 +1,2 @@
+prog: sarp
+tools: none memcheck
diff --git a/perf/vg_perf.in b/perf/vg_perf.in
new file mode 100644
index 00000000..91991520
--- /dev/null
+++ b/perf/vg_perf.in
@@ -0,0 +1,368 @@
+#! @PERL@
+##--------------------------------------------------------------------##
+##--- Valgrind performance testing script vg_perf ---##
+##--------------------------------------------------------------------##
+
+# This file is part of Valgrind, a dynamic binary instrumentation
+# framework.
+#
+# Copyright (C) 2005 Nicholas Nethercote
+# njn@valgrind.org
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+# 02111-1307, USA.
+#
+# The GNU General Public License is contained in the file COPYING.
+
+#----------------------------------------------------------------------------
+# usage: vg_perf [options] <dirs | files>
+#
+# Options:
+# --all: run tests in all subdirs
+# --valgrind: valgrind to use (the directory it's in). Default is the one
+# in the current tree.
+#
+# The easiest way is to run all tests in valgrind/ with (assuming you installed
+# in $PREFIX):
+#
+# perl perf/vg_perf --all
+#
+# You can specify individual files to test, or whole directories, or both.
+# Directories are traversed recursively, except for ones named, for example,
+# CVS/ or docs/.
+#
+# Each test is defined in a file <test>.vgperf, containing one or more of the
+# following lines, in any order:
+# - prog: <prog to run> (compulsory)
+# - tools: <Valgrind tools> (compulsory)
+# - args: <args for prog> (default: none)
+# - vgopts: <Valgrind options> (default: none)
+# - prereq: <prerequisite command> (default: none)
+# - cleanup: <post-test cleanup cmd to run> (default: none)
+#
+# The prerequisite command, if present, must return 0 otherwise the test is
+# skipped.
+#----------------------------------------------------------------------------
+
+use warnings;
+use strict;
+
+#----------------------------------------------------------------------------
+# Global vars
+#----------------------------------------------------------------------------
+my $usage="vg_perf [--all, --valgrind]\n";
+
+my $tmp="vg_perf.tmp.$$";
+
+# Test variables
+my $vgopts; # valgrind options
+my $prog; # test prog
+my $args; # test prog args
+my $prereq; # prerequisite test to satisfy before running test
+my $cleanup; # cleanup command to run
+my @tools; # which tools are we measuring the program with
+
+# Abbreviations used in output
+my %toolnames = (
+ none => "nl",
+ memcheck => "mc",
+ cachegrind => "cg",
+ massif => "ms"
+);
+
+# We run each program this many times and choose the best time.
+my $n_runs = 3;
+
+my $num_tests_done = 0;
+my $num_timings_done = 0;
+
+# Starting directory
+chomp(my $tests_dir = `pwd`);
+
+# Directory of the Valgrind being measured. Default is the one in the
+# current tree.
+my $vg_dir = $tests_dir;
+
+#----------------------------------------------------------------------------
+# Process command line, setup
+#----------------------------------------------------------------------------
+
+# If $prog is a relative path, it prepends $dir to it. Useful for two reasons:
+#
+# 1. Can prepend "." onto programs to avoid trouble with users who don't have
+# "." in their path (by making $dir = ".")
+# 2. Can prepend the current dir to make the command absolute to avoid
+# subsequent trouble when we change directories.
+#
+# Also checks the program exists and is executable.
+sub validate_program ($$$$)
+{
+ my ($dir, $prog, $must_exist, $must_be_executable) = @_;
+
+ # If absolute path, leave it alone. If relative, make it
+ # absolute -- by prepending current dir -- so we can change
+ # dirs and still use it.
+ $prog = "$dir/$prog" if ($prog !~ /^\//);
+ if ($must_exist) {
+ (-f $prog) or die "vg_perf: '$prog' not found or not a file ($dir)\n";
+ }
+ if ($must_be_executable) {
+ (-x $prog) or die "vg_perf: '$prog' not executable ($dir)\n";
+ }
+
+ return $prog;
+}
+
+sub validate_tools($)
+{
+ # XXX: should check they exist!
+ my ($toolnames) = @_;
+ my @t = split(/\s+/, $toolnames);
+ return @t;
+}
+
+sub process_command_line()
+{
+ my $alldirs = 0;
+ my @fs;
+
+ for my $arg (@ARGV) {
+ if ($arg =~ /^-/) {
+ if ($arg =~ /^--all$/) {
+ $alldirs = 1;
+ } elsif ($arg =~ /^--valgrind=(.*)$/) {
+ $vg_dir = $1;
+ } else {
+ die $usage;
+ }
+ } else {
+ push(@fs, $arg);
+ }
+ }
+ # Make $vg_dir absolute if not already
+ if ($vg_dir !~ /^\//) { $vg_dir = "$tests_dir/$vg_dir"; }
+ validate_program($vg_dir, "./coregrind/valgrind", 1, 1);
+
+ if ($alldirs) {
+ @fs = ();
+ foreach my $f (glob "*") {
+ push(@fs, $f) if (-d $f);
+ }
+ }
+
+ (0 != @fs) or die "No test files or directories specified\n";
+
+ return @fs;
+}
+
+#----------------------------------------------------------------------------
+# Read a .vgperf file
+#----------------------------------------------------------------------------
+sub read_vgperf_file($)
+{
+ my ($f) = @_;
+
+ # Defaults.
+ ($vgopts, $prog, $args, $prereq, $cleanup)
+ = ("", undef, "", undef, undef, undef, undef);
+
+ open(INPUTFILE, "< $f") || die "File $f not openable\n";
+
+ while (my $line = <INPUTFILE>) {
+ if ($line =~ /^\s*#/ || $line =~ /^\s*$/) {
+ next;
+ } elsif ($line =~ /^\s*vgopts:\s*(.*)$/) {
+ $vgopts = $1;
+ } elsif ($line =~ /^\s*prog:\s*(.*)$/) {
+ $prog = validate_program(".", $1, 0, 0);
+ } elsif ($line =~ /^\s*tools:\s*(.*)$/) {
+ @tools = validate_tools($1);
+ } elsif ($line =~ /^\s*args:\s*(.*)$/) {
+ $args = $1;
+ } elsif ($line =~ /^\s*prereq:\s*(.*)$/) {
+ $prereq = $1;
+ } elsif ($line =~ /^\s*cleanup:\s*(.*)$/) {
+ $cleanup = $1;
+ } else {
+ die "Bad line in $f: $line\n";
+ }
+ }
+ close(INPUTFILE);
+
+ if (!defined $prog) {
+ $prog = ""; # allow no prog for testing error and --help cases
+ }
+ if (0 == @tools) {
+ die "vg_perf: missing 'tools' line in $f\n";
+ }
+}
+
+#----------------------------------------------------------------------------
+# Do one test
+#----------------------------------------------------------------------------
+# Since most of the program time is spent in system() calls, need this to
+# propagate a Ctrl-C enabling us to quit.
+sub mysystem($)
+{
+ (system($_[0]) != 2) or exit 1; # 2 is SIGINT
+}
+
+# Run program N times, return the best wall-clock time.
+sub time_prog($$)
+{
+ my ($cmd, $n) = @_;
+ my $tmin = 999999;
+ for (my $i = 0; $i < $n; $i++) {
+ my $out = `$cmd 2>&1 1>/dev/null`;
+ $out =~ /walltime: ([\d\.]+)s/;
+ $tmin = $1 if ($1 < $tmin);
+ }
+ return $tmin;
+}
+
+sub do_one_test($$)
+{
+ my ($dir, $vgperf) = @_;
+ $vgperf =~ /^(.*)\.vgperf/;
+ my $name = $1;
+
+ read_vgperf_file($vgperf);
+
+ if (defined $prereq) {
+ if (system("$prereq") != 0) {
+ printf("%-16s (skipping, prereq failed: $prereq)\n", "$name:");
+ return;
+ }
+ }
+
+ printf("%-12s", "$name:");
+
+ my $timecmd = "/usr/bin/time -f 'walltime: %es'";
+
+ # Do the native run(s).
+ printf("nt:");
+ my $cmd = "$timecmd $prog $args";
+ my $tNative = time_prog($cmd, $n_runs);
+ printf("%4.1fs ", $tNative);
+
+ foreach my $tool (@tools) {
+ (defined $toolnames{$tool}) or
+ die "unknown tool $tool, please add to %toolnames\n";
+
+ # Do the tool run(s). Set both VALGRIND_LIB and VALGRIND_LIB_INNER
+ # in case this Valgrind was configured with --enable-inner.
+ printf("%s:", $toolnames{$tool});
+ my $vgsetup = "VALGRIND_LIB=$vg_dir/.in_place "
+ . "VALGRIND_LIB_INNER=$vg_dir/.in_place ";
+ my $vgcmd = "$vg_dir/coregrind/valgrind "
+ . "--command-line-only=yes --tool=$tool -q "
+ . "--memcheck:leak-check=no --addrcheck:leak-check=no "
+ . "$vgopts ";
+ my $cmd = "$vgsetup $timecmd $vgcmd $prog $args";
+ my $tTool = time_prog($cmd, $n_runs);
+ printf("%4.1fs (%4.1fx) ", $tTool, $tTool/$tNative);
+
+ $num_timings_done++;
+ }
+ printf("\n");
+
+ if (defined $cleanup) {
+ (system("$cleanup") == 0) or
+ print(" ($name cleanup operation failed: $cleanup)\n");
+ }
+
+ $num_tests_done++;
+}
+
+#----------------------------------------------------------------------------
+# Test one directory (and any subdirs)
+#----------------------------------------------------------------------------
+sub test_one_dir($$); # forward declaration
+
+sub test_one_dir($$)
+{
+ my ($dir, $prev_dirs) = @_;
+ $dir =~ s/\/$//; # trim a trailing '/'
+
+ # Ignore dirs into which we should not recurse.
+ if ($dir =~ /^(BitKeeper|CVS|SCCS|docs|doc)$/) { return; }
+
+ chdir($dir) or die "Could not change into $dir\n";
+
+ # Nb: Don't prepend a '/' to the base directory
+ my $full_dir = $prev_dirs . ($prev_dirs eq "" ? "" : "/") . $dir;
+ my $dashes = "-" x (50 - length $full_dir);
+
+ my @fs = glob "*";
+ my $found_tests = (0 != (grep { $_ =~ /\.vgperf$/ } @fs));
+
+ if ($found_tests) {
+ print "-- Running tests in $full_dir $dashes\n";
+ }
+ foreach my $f (@fs) {
+ if (-d $f) {
+ test_one_dir($f, $full_dir);
+ } elsif ($f =~ /\.vgperf$/) {
+ do_one_test($full_dir, $f);
+ }
+ }
+ if ($found_tests) {
+ print "-- Finished tests in $full_dir $dashes\n";
+ }
+
+ chdir("..");
+}
+
+#----------------------------------------------------------------------------
+# Summarise results
+#----------------------------------------------------------------------------
+sub summarise_results
+{
+ printf("\n== %d programs, %d timings =================\n\n",
+ $num_tests_done, $num_timings_done);
+}
+
+#----------------------------------------------------------------------------
+# main()
+#----------------------------------------------------------------------------
+
+# nuke VALGRIND_OPTS
+$ENV{"VALGRIND_OPTS"} = "";
+
+my @fs = process_command_line();
+foreach my $f (@fs) {
+ if (-d $f) {
+ test_one_dir($f, "");
+ } else {
+ # Allow the .vgperf suffix to be given or omitted
+ if ($f =~ /.vgperf$/ && -r $f) {
+ # do nothing
+ } elsif (-r "$f.vgperf") {
+ $f = "$f.vgperf";
+ } else {
+ die "`$f' neither a directory nor a readable test file/name\n"
+ }
+ my $dir = `dirname $f`; chomp $dir;
+ my $file = `basename $f`; chomp $file;
+ chdir($dir) or die "Could not change into $dir\n";
+ do_one_test($dir, $file);
+ chdir($tests_dir);
+ }
+}
+summarise_results();
+
+##--------------------------------------------------------------------##
+##--- end ---##
+##--------------------------------------------------------------------##