mirror of
https://github.com/kdlucas/byte-unixbench.git
synced 2024-12-11 23:30:07 +08:00
1837 lines
56 KiB
Perl
Executable File
1837 lines
56 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
|
|
use POSIX qw(strftime);
|
|
use Time::HiRes;
|
|
use IO::Handle;
|
|
|
|
|
|
############################################################################
|
|
# UnixBench - Release 5.1.3, based on:
|
|
# The BYTE UNIX Benchmarks - Release 3
|
|
# Module: Run SID: 3.11 5/15/91 19:30:14
|
|
# Original Byte benchmarks written by:
|
|
# Ben Smith, Tom Yager at BYTE Magazine
|
|
# ben@bytepb.byte.com tyager@bytepb.byte.com
|
|
# BIX: bensmith tyager
|
|
#
|
|
#######################################################################
|
|
# General Purpose Benchmark
|
|
# based on the work by Ken McDonell, Computer Science, Monash University
|
|
#
|
|
# You will need ...
|
|
# perl Time::HiRes IO::Handlecat cc chmod comm cp date dc df echo
|
|
# kill ls make mkdir rm sed test time touch tty umask who
|
|
###############################################################################
|
|
# Modification Log:
|
|
# $Header: run,v 5.2 88/01/12 06:23:43 kenj Exp $
|
|
# Ken McDonell, Computer Science, Monash University
|
|
# August 1, 1983
|
|
# 3/89 - Ben Smith - BYTE: globalized many variables, modernized syntax
|
|
# 5/89 - commented and modernized. Removed workload items till they
|
|
# have been modernized. Added database server test.
|
|
# 11/14/89 - Made modifications to reflect new version of fstime
|
|
# and elimination of mem tests.
|
|
# 10/22/90 - Many tests have been flipped so that they run for
|
|
# a specified length of time and loops are counted.
|
|
# 4/3/91 - Cleaned up and debugged several test parameters - Ben
|
|
# 4/9/91 - Added structure for creating index and determing flavor of UNIX
|
|
# 4/26/91 - Made changes and corrections suggested by Tin Le of Sony
|
|
# 5/15/91 - Removed db from distribution
|
|
# 4/4/92 Jon Tombs <jon@robots.ox.ac.uk> fixed for GNU time to look like
|
|
# BSD (don't know the format of sysV!)
|
|
# 12/95 - Massive changes for portability, speed, and more meaningful index
|
|
# DCN David C Niemi <niemi@tux.org>
|
|
# 1997.06.20 DCN Fixed overflow condition in fstime.c on fast machines
|
|
# 1997.08.24 DCN Modified "system", replaced double with
|
|
# whetstone-double in "index"
|
|
# 1997.09.10 DCN Added perlbench as an Exhibition benchmark
|
|
# 1997.09.23 DCN Added rgooch's select as an Exhibition benchmark
|
|
# 1999.07.28 DCN "select" not compiled or run by default, because it
|
|
# does not compile on many platforms. PerlBench also
|
|
# not run by default.
|
|
# 2007.09.26 IS Huge rewrite -- see release notes in README.
|
|
# 2007.10.12 IS Added graphics tests, categories feature.
|
|
# 2007.10.14 IS Set and report LANG. Added "grep" and "sysexec".
|
|
# 2007.12.22 IS Tiny fixes; see README.
|
|
# 2011.01.13 KDL Fix for parallel compilation.
|
|
|
|
|
|
############################################################################
|
|
# CONFIGURATION
|
|
############################################################################
|
|
|
|
# Version number of the script.
|
|
my $version = "5.1.3";
|
|
|
|
# The setting of LANG makes a huge difference to some of the scores,
|
|
# particularly depending on whether UTF-8 is used. So we always set
|
|
# it to the same value, which is configured here.
|
|
#
|
|
# If you want your results to be meaningful when compared to other peoples'
|
|
# results, you should not change this. Change it if you want to measure the
|
|
# effect of different languages.
|
|
my $language = "en_US.utf8";
|
|
|
|
# The number of iterations per test.
|
|
my $longIterCount = 10;
|
|
my $shortIterCount = 3;
|
|
|
|
# C compiler to use in compilation tests.
|
|
my $cCompiler = 'gcc';
|
|
|
|
# Establish full paths to directories. These need to be full pathnames
|
|
# (or do they, any more?). They can be set in env.
|
|
# variables whose names are the first parameter to getDir() below.
|
|
my $BASEDIR = `pwd`;
|
|
chomp($BASEDIR);
|
|
|
|
# Directory where the test programs live.
|
|
my $BINDIR = getDir('UB_BINDIR', $BASEDIR . "/pgms");
|
|
|
|
# Temp directory, for temp files.
|
|
my $TMPDIR = getDir('UB_TMPDIR', $BASEDIR . "/tmp");
|
|
|
|
# Directory to put results in.
|
|
my $RESULTDIR = getDir('UB_RESULTDIR', $BASEDIR . "/results");
|
|
|
|
# Directory where the tests are executed.
|
|
my $TESTDIR = getDir('UB_TESTDIR', $BASEDIR . "/testdir");
|
|
|
|
|
|
############################################################################
|
|
# TEST SPECIFICATIONS
|
|
############################################################################
|
|
|
|
# Configure the categories to which tests can belong.
|
|
my $testCats = {
|
|
'system' => { 'name' => "System Benchmarks", 'maxCopies' => 16 },
|
|
'2d' => { 'name' => "2D Graphics Benchmarks", 'maxCopies' => 1 },
|
|
'3d' => { 'name' => "3D Graphics Benchmarks", 'maxCopies' => 1 },
|
|
'misc' => { 'name' => "Non-Index Benchmarks", 'maxCopies' => 16 },
|
|
};
|
|
|
|
|
|
my $arithmetic = [
|
|
"arithoh", "short", "int", "long", "float", "double", "whetstone-double"
|
|
];
|
|
|
|
my $fs = [
|
|
"fstime-w", "fstime-r", "fstime",
|
|
"fsbuffer-w", "fsbuffer-r", "fsbuffer",
|
|
"fsdisk-w", "fsdisk-r", "fsdisk"
|
|
];
|
|
|
|
my $oldsystem = [
|
|
"execl", "fstime", "fsbuffer", "fsdisk", "pipe", "context1", "spawn",
|
|
"syscall"
|
|
];
|
|
|
|
my $system = [
|
|
@$oldsystem, "shell1", "shell8", "shell16"
|
|
];
|
|
|
|
my $index = [
|
|
"dhry2reg", "whetstone-double", @$oldsystem, "shell1", "shell8"
|
|
];
|
|
|
|
my $graphics = [
|
|
"2d-rects", "2d-ellipse", "2d-aashapes", "2d-text", "2d-blit",
|
|
"2d-window", "ubgears"
|
|
];
|
|
|
|
|
|
# List of all supported test names.
|
|
my $testList = {
|
|
# Individual tests.
|
|
"dhry2reg" => undef,
|
|
"whetstone-double" => undef,
|
|
"syscall" => undef,
|
|
"pipe" => undef,
|
|
"context1" => undef,
|
|
"spawn" => undef,
|
|
"execl" => undef,
|
|
"fstime-w" => undef,
|
|
"fstime-r" => undef,
|
|
"fstime" => undef,
|
|
"fsbuffer-w" => undef,
|
|
"fsbuffer-r" => undef,
|
|
"fsbuffer" => undef,
|
|
"fsdisk-w" => undef,
|
|
"fsdisk-r" => undef,
|
|
"fsdisk" => undef,
|
|
"shell1" => undef,
|
|
"shell8" => undef,
|
|
"shell16" => undef,
|
|
"short" => undef,
|
|
"int" => undef,
|
|
"long" => undef,
|
|
"float" => undef,
|
|
"double" => undef,
|
|
"arithoh" => undef,
|
|
"C" => undef,
|
|
"dc" => undef,
|
|
"hanoi" => undef,
|
|
"grep" => undef,
|
|
"sysexec" => undef,
|
|
|
|
"2d-rects" => undef,
|
|
"2d-lines" => undef,
|
|
"2d-circle" => undef,
|
|
"2d-ellipse" => undef,
|
|
"2d-shapes" => undef,
|
|
"2d-aashapes" => undef,
|
|
"2d-polys" => undef,
|
|
"2d-text" => undef,
|
|
"2d-blit" => undef,
|
|
"2d-window" => undef,
|
|
|
|
"ubgears" => undef,
|
|
|
|
# Named combos and shorthands.
|
|
"arithmetic" => $arithmetic,
|
|
"dhry" => [ "dhry2reg" ],
|
|
"dhrystone" => [ "dhry2reg" ],
|
|
"whets" => [ "whetstone-double" ],
|
|
"whetstone" => [ "whetstone-double" ],
|
|
"load" => [ "shell" ],
|
|
"misc" => [ "C", "dc", "hanoi" ],
|
|
"speed" => [ @$arithmetic, @$system ],
|
|
"oldsystem" => $oldsystem,
|
|
"system" => $system,
|
|
"fs" => $fs,
|
|
"shell" => [ "shell1", "shell8", "shell16" ],
|
|
"graphics" => $graphics,
|
|
|
|
# The tests which constitute the official index.
|
|
"index" => $index,
|
|
|
|
# The tests which constitute the official index plus the graphics
|
|
# index.
|
|
"gindex" => [ @$index, @$graphics ],
|
|
};
|
|
|
|
|
|
# Default parameters for benchmarks. Note that if "prog" is used,
|
|
# it must contain just the program name, as it will be quoted (this
|
|
# is necessary if BINDIR contains spaces). Put any options in "options".
|
|
my $baseParams = {
|
|
"prog" => undef,
|
|
"options" => "",
|
|
"repeat" => 'short',
|
|
"stdout" => 1, # Non-0 to keep stdout.
|
|
"stdin" => "",
|
|
"logmsg" => "",
|
|
};
|
|
|
|
|
|
# Individual parameters for all benchmarks.
|
|
my $testParams = {
|
|
|
|
##########################
|
|
## System Benchmarks ##
|
|
##########################
|
|
|
|
"dhry2reg" => {
|
|
"logmsg" => "Dhrystone 2 using register variables",
|
|
"cat" => 'system',
|
|
"options" => "10",
|
|
"repeat" => 'long',
|
|
},
|
|
"whetstone-double" => {
|
|
"logmsg" => "Double-Precision Whetstone",
|
|
"cat" => 'system',
|
|
"repeat" => 'long',
|
|
},
|
|
"syscall" => {
|
|
"logmsg" => "System Call Overhead",
|
|
"cat" => 'system',
|
|
"repeat" => 'long',
|
|
"options" => "10",
|
|
},
|
|
"context1" => {
|
|
"logmsg" => "Pipe-based Context Switching",
|
|
"cat" => 'system',
|
|
"repeat" => 'long',
|
|
"options" => "10",
|
|
},
|
|
"pipe" => {
|
|
"logmsg" => "Pipe Throughput",
|
|
"cat" => 'system',
|
|
"repeat" => 'long',
|
|
"options" => "10",
|
|
},
|
|
"spawn" => {
|
|
"logmsg" => "Process Creation",
|
|
"cat" => 'system',
|
|
"options" => "30",
|
|
},
|
|
"execl" => {
|
|
"logmsg" => "Execl Throughput",
|
|
"cat" => 'system',
|
|
"options" => "30",
|
|
},
|
|
"fstime-w" => {
|
|
"logmsg" => "File Write 1024 bufsize 2000 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-w -t 30 -d \"${TMPDIR}\" -b 1024 -m 2000",
|
|
},
|
|
"fstime-r" => {
|
|
"logmsg" => "File Read 1024 bufsize 2000 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-r -t 30 -d \"${TMPDIR}\" -b 1024 -m 2000",
|
|
},
|
|
"fstime" => {
|
|
"logmsg" => "File Copy 1024 bufsize 2000 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-c -t 30 -d \"${TMPDIR}\" -b 1024 -m 2000",
|
|
},
|
|
"fsbuffer-w" => {
|
|
"logmsg" => "File Write 256 bufsize 500 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-w -t 30 -d \"${TMPDIR}\" -b 256 -m 500",
|
|
},
|
|
"fsbuffer-r" => {
|
|
"logmsg" => "File Read 256 bufsize 500 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-r -t 30 -d \"${TMPDIR}\" -b 256 -m 500",
|
|
},
|
|
"fsbuffer" => {
|
|
"logmsg" => "File Copy 256 bufsize 500 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-c -t 30 -d \"${TMPDIR}\" -b 256 -m 500",
|
|
},
|
|
"fsdisk-w" => {
|
|
"logmsg" => "File Write 4096 bufsize 8000 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-w -t 30 -d \"${TMPDIR}\" -b 4096 -m 8000",
|
|
},
|
|
"fsdisk-r" => {
|
|
"logmsg" => "File Read 4096 bufsize 8000 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-r -t 30 -d \"${TMPDIR}\" -b 4096 -m 8000",
|
|
},
|
|
"fsdisk" => {
|
|
"logmsg" => "File Copy 4096 bufsize 8000 maxblocks",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/fstime",
|
|
"options" => "-c -t 30 -d \"${TMPDIR}\" -b 4096 -m 8000",
|
|
},
|
|
"shell1" => {
|
|
"logmsg" => "Shell Scripts (1 concurrent)",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/looper",
|
|
"options" => "60 \"${BINDIR}/multi.sh\" 1",
|
|
},
|
|
"shell8" => {
|
|
"logmsg" => "Shell Scripts (8 concurrent)",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/looper",
|
|
"options" => "60 \"${BINDIR}/multi.sh\" 8",
|
|
},
|
|
"shell16" => {
|
|
"logmsg" => "Shell Scripts (16 concurrent)",
|
|
"cat" => 'system',
|
|
"prog" => "${BINDIR}/looper",
|
|
"options" => "60 \"${BINDIR}/multi.sh\" 16",
|
|
},
|
|
|
|
##########################
|
|
## Graphics Benchmarks ##
|
|
##########################
|
|
|
|
"2d-rects" => {
|
|
"logmsg" => "2D graphics: rectangles",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "rects 3 2",
|
|
},
|
|
|
|
"2d-lines" => {
|
|
"logmsg" => "2D graphics: lines",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "lines 3 2",
|
|
},
|
|
|
|
"2d-circle" => {
|
|
"logmsg" => "2D graphics: circles",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "circle 3 2",
|
|
},
|
|
|
|
"2d-ellipse" => {
|
|
"logmsg" => "2D graphics: ellipses",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "ellipse 3 2",
|
|
},
|
|
|
|
"2d-shapes" => {
|
|
"logmsg" => "2D graphics: polygons",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "shapes 3 2",
|
|
},
|
|
|
|
"2d-aashapes" => {
|
|
"logmsg" => "2D graphics: aa polygons",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "aashapes 3 2",
|
|
},
|
|
|
|
"2d-polys" => {
|
|
"logmsg" => "2D graphics: complex polygons",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "polys 3 2",
|
|
},
|
|
|
|
"2d-text" => {
|
|
"logmsg" => "2D graphics: text",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "text 3 2",
|
|
},
|
|
|
|
"2d-blit" => {
|
|
"logmsg" => "2D graphics: images and blits",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "blit 3 2",
|
|
},
|
|
|
|
"2d-window" => {
|
|
"logmsg" => "2D graphics: windows",
|
|
"cat" => '2d',
|
|
"prog" => "${BINDIR}/gfx-x11",
|
|
"options" => "window 3 2",
|
|
},
|
|
|
|
"ubgears" => {
|
|
"logmsg" => "3D graphics: gears",
|
|
"cat" => '3d',
|
|
"options" => "-time 20 -v",
|
|
},
|
|
|
|
|
|
##########################
|
|
## Non-Index Benchmarks ##
|
|
##########################
|
|
|
|
"C" => {
|
|
"logmsg" => "C Compiler Throughput ($cCompiler)",
|
|
"cat" => 'misc',
|
|
"prog" => "${BINDIR}/looper",
|
|
"options" => "60 $cCompiler cctest.c",
|
|
},
|
|
"arithoh" => {
|
|
"logmsg" => "Arithoh",
|
|
"cat" => 'misc',
|
|
"options" => "10",
|
|
},
|
|
"short" => {
|
|
"logmsg" => "Arithmetic Test (short)",
|
|
"cat" => 'misc',
|
|
"options" => "10",
|
|
},
|
|
"int" => {
|
|
"logmsg" => "Arithmetic Test (int)",
|
|
"cat" => 'misc',
|
|
"options" => "10",
|
|
},
|
|
"long" => {
|
|
"logmsg" => "Arithmetic Test (long)",
|
|
"cat" => 'misc',
|
|
"options" => "10",
|
|
},
|
|
"float" => {
|
|
"logmsg" => "Arithmetic Test (float)",
|
|
"cat" => 'misc',
|
|
"options" => "10",
|
|
},
|
|
"double" => {
|
|
"logmsg" => "Arithmetic Test (double)",
|
|
"cat" => 'misc',
|
|
"options" => "10",
|
|
},
|
|
"dc" => {
|
|
"logmsg" => "Dc: sqrt(2) to 99 decimal places",
|
|
"cat" => 'misc',
|
|
"prog" => "${BINDIR}/looper",
|
|
"options" => "30 dc",
|
|
"stdin" => "dc.dat",
|
|
},
|
|
"hanoi" => {
|
|
"logmsg" => "Recursion Test -- Tower of Hanoi",
|
|
"cat" => 'misc',
|
|
"options" => "20",
|
|
},
|
|
"grep" => {
|
|
"logmsg" => "Grep a large file (system's grep)",
|
|
"cat" => 'misc',
|
|
"prog" => "${BINDIR}/looper",
|
|
"options" => "30 grep -c gimp large.txt",
|
|
},
|
|
"sysexec" => {
|
|
"logmsg" => "Exec System Call Overhead",
|
|
"cat" => 'misc',
|
|
"repeat" => 'long',
|
|
"prog" => "${BINDIR}/syscall",
|
|
"options" => "10 exec",
|
|
},
|
|
};
|
|
|
|
|
|
# CPU flags of interest.
|
|
my $x86CpuFlags = {
|
|
'pae' => "Physical Address Ext",
|
|
'sep' => "SYSENTER/SYSEXIT",
|
|
'syscall' => "SYSCALL/SYSRET",
|
|
'mmx' => "MMX",
|
|
'mmxext' => "AMD MMX",
|
|
'cxmmx' => "Cyrix MMX",
|
|
'xmm' => "Streaming SIMD",
|
|
'xmm2' => "Streaming SIMD-2",
|
|
'xmm3' => "Streaming SIMD-3",
|
|
'ht' => "Hyper-Threading",
|
|
'ia64' => "IA-64 processor",
|
|
'lm' => "x86-64",
|
|
'vmx' => "Intel virtualization",
|
|
'svm' => "AMD virtualization",
|
|
};
|
|
|
|
|
|
############################################################################
|
|
# UTILITIES
|
|
############################################################################
|
|
|
|
# Exec the given command, and catch its standard output.
|
|
# We return an array containing the PID and the filehandle on the
|
|
# process' standard output. It's up to the caller to wait for the command
|
|
# to terminate.
|
|
sub command {
|
|
my ( $cmd ) = @_;
|
|
|
|
my $pid = open(my $childFd, "-|");
|
|
if (!defined($pid)) {
|
|
die("Run: fork() failed (undef)\n");
|
|
} elsif ($pid == 0) {
|
|
exec($cmd);
|
|
die("Run: exec() failed (returned)\n");
|
|
}
|
|
|
|
return ( $pid, $childFd );
|
|
}
|
|
|
|
|
|
# Get data from running a system command. Used for things like getting
|
|
# the host OS from `uname -o` etc.
|
|
#
|
|
# Ignores initial blank lines from the command and returns the first
|
|
# non-blank line, with white space trimmed off. Returns a blank string
|
|
# if there is no output; undef if the command fails.
|
|
sub getCmdOutput {
|
|
my ( $cmd ) = @_;
|
|
|
|
my ( $pid, $fd ) = command($cmd . " 2>/dev/null");
|
|
my $result = "";
|
|
while (<$fd>) {
|
|
chomp;
|
|
next if /^[ \t]*$/;
|
|
|
|
$result = $_;
|
|
$result =~ s/^[ \t]+//;
|
|
$result =~ s/[ \t]+$//;
|
|
last;
|
|
}
|
|
|
|
# Close the command and wait for it to die.
|
|
waitpid($pid, 0);
|
|
my $status = $?;
|
|
|
|
return $status == 0 ? $result : undef;
|
|
}
|
|
|
|
|
|
# Get a directory pathname from an environment variable, or the given
|
|
# default. Canonicalise and return the value.
|
|
sub getDir {
|
|
my ( $var, $def ) = @_;
|
|
|
|
my $val = $ENV{$var} || $def;
|
|
|
|
# Canonicalise the value.
|
|
my $wd;
|
|
chomp($wd = `pwd`);
|
|
chdir($val);
|
|
chomp($val = `pwd`);
|
|
chdir($wd);
|
|
$ENV{$var} = $val;
|
|
|
|
$val;
|
|
}
|
|
|
|
|
|
# Get the name of the file we're going to log to. The name uses the hostname
|
|
# and date, plus a sequence number to make it unique.
|
|
sub logFile {
|
|
my ( $sysInfo ) = @_;
|
|
|
|
my $count = 1;
|
|
|
|
# Use the date in the base file name.
|
|
my $ymd = strftime "%Y-%m-%d", localtime;
|
|
|
|
while (1) {
|
|
my $log = sprintf "%s/%s-%s-%02d",
|
|
${RESULTDIR}, $sysInfo->{'name'}, $ymd, $count;
|
|
return $log if (! -e $log);
|
|
++$count;
|
|
}
|
|
}
|
|
|
|
|
|
# Print a message to the named log file. We use this method rather than
|
|
# keeping the FD open because we use shell redirection to send command
|
|
# output to the same file.
|
|
sub printLog {
|
|
my ( $logFile, @args ) = @_;
|
|
|
|
open(my $fd, ">>", $logFile) || abortRun("can't append to $logFile");
|
|
printf $fd @args;
|
|
close($fd);
|
|
}
|
|
|
|
|
|
# Display a number of something, auto-selecting the plural form
|
|
# if appropriate. We are given the number, the singular, and the
|
|
# plural; if the plural is omitted, it defaults to singular + "s".
|
|
sub number {
|
|
my ( $n, $what, $plural ) = @_;
|
|
|
|
$plural = $what . "s" if !defined($plural);
|
|
|
|
if (!defined($n)) {
|
|
return sprintf "unknown %s", $plural;
|
|
} else {
|
|
return sprintf "%d %s", $n, $n == 1 ? $what : $plural;
|
|
}
|
|
}
|
|
|
|
|
|
# Merge two sets of test parameters -- defaults and actual parameters.
|
|
# Return the merged parameter hash.
|
|
sub mergeParams {
|
|
my ( $def, $vals ) = @_;
|
|
|
|
my $params = { };
|
|
foreach my $k (keys(%$def)) {
|
|
$params->{$k} = $def->{$k};
|
|
}
|
|
foreach my $k (keys(%$vals)) {
|
|
$params->{$k} = $vals->{$k};
|
|
}
|
|
|
|
$params;
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# SYSTEM ANALYSIS
|
|
############################################################################
|
|
|
|
# Extract interesting flags from the given processor flags string and
|
|
# convert them to descriptive names.
|
|
sub processCpuFlags {
|
|
my ( $flagStr ) = @_;
|
|
|
|
my @names;
|
|
foreach my $f (sort split(/\s+/, $flagStr)) {
|
|
my $name = $x86CpuFlags->{$f};
|
|
push(@names, $name) if $name;
|
|
}
|
|
|
|
join(", ", @names);
|
|
}
|
|
|
|
|
|
# Get information on the CPUs in the system. Returns a reference to an
|
|
# array of N entries, one per CPU, where each entry is a hash containing
|
|
# these fields:
|
|
# describing the model etc. Returns undef if the information can't be got.
|
|
sub getCpuInfo {
|
|
open(my $fd, "<", "/proc/cpuinfo") || return undef;
|
|
|
|
my $cpus = [ ];
|
|
my $cpu = 0;
|
|
while (<$fd>) {
|
|
chomp;
|
|
my ( $field, $val ) = split(/[ \t]*:[ \t]*/);
|
|
next if (!$field || !$val);
|
|
if ($field eq "processor") {
|
|
$cpu = $val;
|
|
} elsif ($field eq "model name") {
|
|
my $model = $val;
|
|
$model =~ s/ +/ /g;
|
|
$cpus->[$cpu]{'model'} = $model;
|
|
} elsif ($field eq "bogomips") {
|
|
$cpus->[$cpu]{'bogo'} = $val;
|
|
} elsif ($field eq "flags") {
|
|
$cpus->[$cpu]{'flags'} = processCpuFlags($val);
|
|
}
|
|
}
|
|
|
|
close($fd);
|
|
|
|
$cpus;
|
|
}
|
|
|
|
|
|
# Get information on the host system. Returns a reference to a hash
|
|
# with the following fields:
|
|
# name Host name
|
|
# os Host OS name
|
|
# osRel Host OS release
|
|
# osVer Host OS version
|
|
# mach Host machine name (eg. "SparcStation 20", but on
|
|
# PC/Linux usually "i686" etc.)
|
|
# platform Hardware platform; on Linux, the base CPU type?
|
|
# system System name (eg. hostname and Linux distro, like
|
|
# "hostname: openSUSE 10.2 (i586)").
|
|
# cpus Value returned by getCpuInfo(), undef if not avail.
|
|
# numCpus Number of CPUs if known, else undef.
|
|
# load System load message as per "uptime".
|
|
# numUsers Number of users and/or open shell sessions.
|
|
sub getSystemInfo {
|
|
my $info = { };
|
|
|
|
# Get host system data.
|
|
$info->{'name'} = getCmdOutput("hostname");
|
|
$info->{'os'} = getCmdOutput("uname -o") || getCmdOutput("uname -s");
|
|
$info->{'osRel'} = getCmdOutput("uname -r");
|
|
$info->{'osVer'} = getCmdOutput("uname -v");
|
|
$info->{'mach'} = getCmdOutput("uname -m");
|
|
$info->{'platform'} = getCmdOutput("uname -i");
|
|
|
|
# Get the system name (SUSE, Red Hat, etc.) if possible.
|
|
$info->{'system'} = $info->{'os'};
|
|
if ( -r "/etc/SuSE-release" ) {
|
|
$info->{'system'} = getCmdOutput("cat /etc/SuSE-release");
|
|
} elsif ( -r "/etc/release" ) {
|
|
$info->{'system'} = getCmdOutput("cat /etc/release");
|
|
}
|
|
|
|
# Get the language info.
|
|
my $lang = getCmdOutput("printenv LANG");
|
|
my $map = getCmdOutput("locale -k LC_CTYPE | grep charmap");
|
|
$map =~ s/.*=//;
|
|
my $coll = getCmdOutput("locale -k LC_COLLATE | grep collate-codeset");
|
|
$coll =~ s/.*=//;
|
|
$info->{'language'} = sprintf "%s (charmap=%s, collate=%s)",
|
|
$lang, $map, $coll;
|
|
|
|
# Get details on the CPUs, if possible.
|
|
my $cpus = getCpuInfo();
|
|
if (defined($cpus)) {
|
|
$info->{'cpus'} = $cpus;
|
|
$info->{'numCpus'} = scalar(@$cpus);
|
|
}
|
|
|
|
# Get graphics hardware info.
|
|
$info->{'graphics'} = getCmdOutput("3dinfo | cut -f1 -d\'(\'");
|
|
|
|
# Get system run state, load and usage info.
|
|
$info->{'runlevel'} = getCmdOutput("runlevel | cut -f2 -d\" \"");
|
|
$info->{'load'} = getCmdOutput("uptime");
|
|
$info->{'numUsers'} = getCmdOutput("who | wc -l");
|
|
|
|
$info;
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# ERROR HANDLING
|
|
############################################################################
|
|
|
|
# Abort the benchmarking run with an error message.
|
|
sub abortRun {
|
|
my ( $err ) = @_;
|
|
|
|
printf STDERR "\n**********************************************\n";
|
|
printf STDERR "Run: %s; aborting\n", $err;
|
|
exit(1);
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# TEST SETUP
|
|
############################################################################
|
|
|
|
# Do checks that everything's ready for testing.
|
|
sub preChecks {
|
|
# Set the language.
|
|
$ENV{'LANG'} = $language;
|
|
|
|
# Check that the required files are in the proper places.
|
|
system("make check");
|
|
if ($? != 0) {
|
|
system("make all");
|
|
if ($? != 0) {
|
|
abortRun("\"make all\" failed");
|
|
}
|
|
}
|
|
|
|
# Create a script to kill this run.
|
|
system("echo \"kill -9 $$\" > \"${TMPDIR}/kill_run\"");
|
|
chmod(0755, $TMPDIR . "/kill_run");
|
|
}
|
|
|
|
|
|
# Parse the command arguments.
|
|
sub parseArgs {
|
|
my @words = @_;
|
|
|
|
# The accumulator for the bench units to be run.
|
|
my $tests = [ ];
|
|
my $params = { 'tests' => $tests };
|
|
|
|
# Generate the requested list of bench programs.
|
|
my $opt;
|
|
my $word;
|
|
while ($word = shift(@words)) {
|
|
if ($word !~ m/^-/) { # A test name.
|
|
if ($word eq "all") {
|
|
foreach my $t (keys(%$testList)) {
|
|
push(@$tests, $t) if (!defined($testList->{$t}));
|
|
}
|
|
} elsif (exists($testList->{$word})) {
|
|
my $val = $testList->{$word} || [ $word ];
|
|
push(@$tests, @$val);
|
|
} else {
|
|
die("Run: unknown test \"$word\"\n");
|
|
}
|
|
} elsif ($word eq "-q") {
|
|
$params->{'verbose'} = 0;
|
|
} elsif ($word eq "-v") {
|
|
$params->{'verbose'} = 2;
|
|
} elsif ($word eq "-i") {
|
|
$params->{'iterations'} = shift(@words);
|
|
} elsif ($word eq "-c") {
|
|
if (!defined($params->{'copies'})) {
|
|
$params->{'copies'} = [ ];
|
|
}
|
|
push(@{$params->{'copies'}}, shift(@words));
|
|
} else {
|
|
die("Run: unknown option $word\n");
|
|
}
|
|
}
|
|
|
|
$params;
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# RESULTS INPUT / OUTPUT
|
|
############################################################################
|
|
|
|
# Read a set of benchmarking results from the given file.
|
|
# Returns results in the form returned by runTests(), but without the
|
|
# individual pass results.
|
|
sub readResultsFromFile {
|
|
my ( $file ) = @_;
|
|
|
|
# Attempt to get the baseline data file; if we can't, just return undef.
|
|
open(my $fd, "<", $file) || return undef;
|
|
|
|
my $results = { };
|
|
while (<$fd>) {
|
|
chomp;
|
|
|
|
# Dump comments, ignore blank lines.
|
|
s/#.*//;
|
|
next if /^\s*$/;
|
|
|
|
my ( $name, $time, $slab, $sum, $score, $iters ) = split(/\|/);
|
|
my $bresult = { };
|
|
$bresult->{'score'} = $score;
|
|
$bresult->{'scorelabel'} = $slab;
|
|
$bresult->{'time'} = $time;
|
|
$bresult->{'iterations'} = $iters;
|
|
|
|
$results->{$name} = $bresult;
|
|
}
|
|
|
|
close($fd);
|
|
|
|
$results;
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# RESULTS PROCESSING
|
|
############################################################################
|
|
|
|
# Process a set of results from a single test by averaging the individal
|
|
# pass results into a single final value.
|
|
# First, though, dump the worst 1/3 of the scores. The logic is that a
|
|
# glitch in the system (background process waking up, for example) may
|
|
# make one or two runs go slow, so let's discard those.
|
|
#
|
|
# $bresult is a hashed array representing the results of a single test;
|
|
# $bresult->{'passes'} is an array of the output from the individual
|
|
# passes.
|
|
sub combinePassResults {
|
|
my ( $bench, $tdata, $bresult, $logFile ) = @_;
|
|
|
|
$bresult->{'cat'} = $tdata->{'cat'};
|
|
|
|
# Computed results.
|
|
my $iterations = 0;
|
|
my $totalTime = 0;
|
|
my $sum = 0;
|
|
my $product = 0;
|
|
my $label;
|
|
|
|
my $pres = $bresult->{'passes'};
|
|
|
|
# We're going to throw away the worst 1/3 of the pass results.
|
|
# Figure out how many to keep.
|
|
my $npasses = scalar(@$pres);
|
|
my $ndump = int($npasses / 3);
|
|
|
|
foreach my $presult (sort { $a->{'COUNT0'} <=> $b->{'COUNT0'} } @$pres) {
|
|
my $count = $presult->{'COUNT0'};
|
|
my $timebase = $presult->{'COUNT1'};
|
|
$label = $presult->{'COUNT2'};
|
|
my $time = $presult->{'TIME'} || $presult->{'elapsed'};
|
|
|
|
# Skip this result if it's one of the worst ones.
|
|
if ($ndump > 0) {
|
|
printLog($logFile, "*Dump score: %12.1f\n", $count);
|
|
--$ndump;
|
|
next;
|
|
}
|
|
|
|
# Count this result.
|
|
++$iterations;
|
|
printLog($logFile, "Count score: %12.1f\n", $count);
|
|
|
|
# If $timebase is 0 the figure is a rate; else compute
|
|
# counts per $timebase. $time is always seconds.
|
|
if ($timebase > 0) {
|
|
$sum += $count / ($time / $timebase);
|
|
$product += log($count) - log($time / $timebase);
|
|
} else {
|
|
$sum += $count;
|
|
$product += log($count);
|
|
}
|
|
$totalTime += $time;
|
|
}
|
|
|
|
# Save the results for the benchmark.
|
|
if ($iterations > 0) {
|
|
$bresult->{'score'} = exp($product / $iterations);
|
|
$bresult->{'scorelabel'} = $label;
|
|
$bresult->{'time'} = $totalTime / $iterations;
|
|
$bresult->{'iterations'} = $iterations;
|
|
} else {
|
|
$bresult->{'error'} = "No measured results";
|
|
}
|
|
}
|
|
|
|
|
|
# Index the given full benchmark results against the baseline results.
|
|
# $results is a hashed array of test names to test results.
|
|
#
|
|
# Adds the following fields to each benchmark result:
|
|
# iscore The baseline score for this test
|
|
# index The index of this test against the baseline
|
|
# Adds the following fields to $results:
|
|
# indexed The number of tests for which index values were
|
|
# generated
|
|
# fullindex Non-0 if all the index tests were indexed
|
|
# index The computed overall index for the run
|
|
# Note that the index values are computed as
|
|
# result / baseline * 10
|
|
# so an index of 523 indicates that a test ran 52.3 times faster than
|
|
# the baseline.
|
|
sub indexResults {
|
|
my ( $results ) = @_;
|
|
|
|
# Read in the baseline result data. If we can't get it, just return
|
|
# without making indexed results.
|
|
my $index = readResultsFromFile($BINDIR . "/index.base");
|
|
if (!defined($index)) {
|
|
return;
|
|
}
|
|
|
|
# Count the number of results we have (indexed or not) in
|
|
# each category.
|
|
my $numCat = { };
|
|
foreach my $bench (@{$results->{'list'}}) {
|
|
my $bresult = $results->{$bench};
|
|
++$numCat->{$bresult->{'cat'}};
|
|
}
|
|
$results->{'numCat'} = $numCat;
|
|
|
|
my $numIndex = { };
|
|
my $indexed = { };
|
|
my $sum = { };
|
|
foreach my $bench (sort(keys(%$index))) {
|
|
# Get the test data for this benchmark.
|
|
my $tdata = $testParams->{$bench};
|
|
if (!defined($tdata)) {
|
|
abortRun("unknown benchmark \"$bench\" in $BINDIR/index.base");
|
|
}
|
|
|
|
# Get the test category. Count the total tests in this cat.
|
|
my $cat = $tdata->{'cat'};
|
|
++$numIndex->{$cat};
|
|
|
|
# If we don't have a result for this test, skip.
|
|
next if (!defined($results->{$bench}));
|
|
|
|
# Get the index and actual results. Calcluate the score.
|
|
my $iresult = $index->{$bench};
|
|
my $bresult = $results->{$bench};
|
|
my $ratio = $bresult->{'score'} / $iresult->{'score'};
|
|
|
|
# Save the indexed score.
|
|
$bresult->{'iscore'} = $iresult->{'score'};
|
|
$bresult->{'index'} = $ratio * 10;
|
|
|
|
# Sun the scores, and count this test for this category.
|
|
$sum->{$cat} += log($ratio);
|
|
++$indexed->{$cat};
|
|
}
|
|
|
|
# Calculate the index scores per category.
|
|
$results->{'indexed'} = $indexed;
|
|
$results->{'numIndex'} = $numIndex;
|
|
foreach my $c (keys(%$indexed)) {
|
|
if ($indexed->{$c} > 0) {
|
|
$results->{'index'}{$c} = exp($sum->{$c} / $indexed->{$c}) * 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# TEST EXECUTION
|
|
############################################################################
|
|
|
|
# Exec the given command in a sub-process.
|
|
#
|
|
# In the child process, we run the command and store its standard output.
|
|
# We also time its execution, and catch its exit status. We then write
|
|
# the command's output, plus lines containing the execution time and status,
|
|
# to a pipe.
|
|
#
|
|
# In the parent process, we immediately return an array containing the
|
|
# child PID and the filehandle to the pipe. This allows the caller to
|
|
# kick off multiple commands in parallel, then gather their output.
|
|
sub commandBuffered {
|
|
my ( $cmd ) = @_;
|
|
|
|
# Create a pipe for parent-child communication.
|
|
my $childReader;
|
|
my $parentWriter;
|
|
pipe($childReader, $parentWriter) || abortRun("pipe() failed");
|
|
$parentWriter->autoflush(1);
|
|
|
|
# Fork off the child process.
|
|
my $pid = fork();
|
|
if (!defined($pid)) {
|
|
abortRun("fork() failed (undef)");
|
|
} elsif ($pid == 0) {
|
|
# Close the other end of the pipe.
|
|
close $childReader;
|
|
|
|
# Start the clock and spawn the command.
|
|
my $benchStart = Time::HiRes::time();
|
|
my ( $cmdPid, $cmdFd ) = command($cmd);
|
|
|
|
# Read and buffer all the command's output.
|
|
my $output = [ ];
|
|
while (<$cmdFd>) {
|
|
push(@$output, $_);
|
|
}
|
|
|
|
# Stop the clock and save the time.
|
|
my $elTime = Time::HiRes::time() - $benchStart;
|
|
push(@$output, sprintf "elapsed|%f\n", $elTime);
|
|
|
|
# Wait for the child to die so we can get its status.
|
|
# close($cmdFd); Doesn't work???
|
|
waitpid($cmdPid, 0);
|
|
my $status = $?;
|
|
push(@$output, sprintf "status|%d\n", $status);
|
|
|
|
# Now that we've got the time, play back all the output to the pipe.
|
|
# The parent can read this at its leisure.
|
|
foreach my $line (@$output) {
|
|
print $parentWriter $line;
|
|
}
|
|
|
|
# Terminate this child.
|
|
close $parentWriter;
|
|
exit(0);
|
|
}
|
|
|
|
# Close the other end of the pipe.
|
|
close $parentWriter;
|
|
|
|
return ( $pid, $childReader );
|
|
}
|
|
|
|
|
|
# Read the results of a benchmark execution from a child process, given
|
|
# its process ID and its filehandle. Create a results hash structure
|
|
# containing the fields returned by the child, plus:
|
|
# pid The child's process ID
|
|
# status The child's exit status
|
|
# ERROR Any stderr output from the child that isn't result data
|
|
# Note that ay result fields with ultiple values are split; so eg.
|
|
# COUNT|x|y|x
|
|
# becomes
|
|
# COUNT0 = x
|
|
# COUNT1 = y
|
|
# COUNT2 = z
|
|
sub readResults {
|
|
my ( $pid, $fd ) = @_;
|
|
|
|
my $presult = { 'pid' => $pid };
|
|
|
|
# Read all the result lines from the child.
|
|
while (<$fd>) {
|
|
chomp;
|
|
|
|
my ( $field, @params ) = split(/\|/);
|
|
if (scalar(@params) == 0) { # Error message.
|
|
$presult->{'ERROR'} .= "\n" if ($presult->{'ERROR'});
|
|
$presult->{'ERROR'} .= $field;
|
|
} elsif (scalar(@params) == 1) { # Simple data.
|
|
$presult->{$field} = $params[0];
|
|
} else { # Compound data.
|
|
# Store the values in separate fields, named "FIELD$i".
|
|
for (my $x = 0; $x < scalar(@params); ++$x) {
|
|
$presult->{$field . $x} = $params[$x];
|
|
}
|
|
}
|
|
}
|
|
|
|
# If the command had an error, make an appropriate message if we
|
|
# don't have one.
|
|
if ($presult->{'status'} != 0 && !defined($presult->{'ERROR'})) {
|
|
$presult->{'ERROR'} = "command returned status " . $presult->{'status'};
|
|
}
|
|
|
|
# Wait for the child to die.
|
|
close($fd);
|
|
waitpid($pid, 0);
|
|
|
|
$presult;
|
|
}
|
|
|
|
|
|
# Execute a benchmark command. We set off a given number of copies in
|
|
# parallel to exercise multiple CPUs.
|
|
#
|
|
# We return an array of results hashes, one per copy; each one is as
|
|
# returned by readResults().
|
|
sub executeBenchmark {
|
|
my ( $command, $copies ) = @_;
|
|
|
|
# Array of contexts for all the copies we're running.
|
|
my $ctxt = [ ];
|
|
|
|
# Kick off all the commands at once.
|
|
for (my $i = 0; $i < $copies; ++$i) {
|
|
my ( $cmdPid, $cmdFd ) = commandBuffered($command);
|
|
$ctxt->[$i] = {
|
|
'pid' => $cmdPid,
|
|
'fd' => $cmdFd,
|
|
};
|
|
}
|
|
|
|
# Now, we can simply read back the command results in order. Because
|
|
# the child processes read and buffer the results and time the commands,
|
|
# there's no need to use select() to read the results as they appear.
|
|
my $pres = [ ];
|
|
for (my $i = 0; $i < $copies; ++$i) {
|
|
my $presult = readResults($ctxt->[$i]{'pid'}, $ctxt->[$i]{'fd'});
|
|
push(@$pres, $presult);
|
|
}
|
|
|
|
$pres;
|
|
}
|
|
|
|
|
|
# Run one iteration of a benchmark, as specified by the given
|
|
# benchmark parameters. We run multiple parallel copies as
|
|
# specified by $copies.
|
|
sub runOnePass {
|
|
my ( $params, $verbose, $logFile, $copies ) = @_;
|
|
|
|
# Get the command to run.
|
|
my $command = $params->{'command'};
|
|
if ($verbose > 1) {
|
|
printf "\n";
|
|
printf "COMMAND: \"%s\"\n", $command;
|
|
printf "COPIES: \"%d\"\n", $copies;
|
|
}
|
|
|
|
# Remember where we are, and move to the test directory.
|
|
my $pwd = `pwd`;
|
|
chdir($TESTDIR);
|
|
|
|
# Execute N copies of the benchmark in parallel.
|
|
my $copyResults = executeBenchmark($command, $copies);
|
|
printLog($logFile, "\n");
|
|
|
|
# Move back home.
|
|
chdir($pwd);
|
|
|
|
# Sum up the scores of the copies.
|
|
my $count = 0;
|
|
my $time = 0;
|
|
my $elap = 0;
|
|
foreach my $res (@$copyResults) {
|
|
# Log the result data for each copy.
|
|
foreach my $k (sort(keys(%$res))) {
|
|
printLog($logFile, "# %s: %s\n", $k, $res->{$k});
|
|
}
|
|
printLog($logFile, "\n");
|
|
|
|
# If it failed, bomb out.
|
|
if (defined($res->{'ERROR'})) {
|
|
my $name = $params->{'logmsg'};
|
|
abortRun("\"$name\": " . $res->{'ERROR'});
|
|
}
|
|
|
|
# Count up the score.
|
|
$count += $res->{'COUNT0'};
|
|
$time += $res->{'TIME'} || $res->{'elapsed'};
|
|
$elap += $res->{'elapsed'};
|
|
}
|
|
|
|
# Make up a combined result.
|
|
my $passResult = $copyResults->[0];
|
|
$passResult->{'COUNT0'} = $count;
|
|
$passResult->{'TIME'} = $time / $copies;
|
|
$passResult->{'elapsed'} = $elap / $copies;
|
|
|
|
$passResult;
|
|
}
|
|
|
|
|
|
sub runBenchmark {
|
|
my ( $bench, $tparams, $verbose, $logFile, $copies ) = @_;
|
|
|
|
# Make up the actual benchmark parameters.
|
|
my $params = mergeParams($baseParams, $tparams);
|
|
|
|
# Make up the command string based on the parameters.
|
|
my $prog = $params->{'prog'} || $BINDIR . "/" . $bench;
|
|
my $command = sprintf "\"%s\" %s", $prog, $params->{'options'};
|
|
$command .= " < \"" . $params->{'stdin'} . "\"" if ($params->{'stdin'});
|
|
$command .= " 2>&1";
|
|
$command .= $params->{'stdout'} ? (" >> \"" . $logFile . "\"") : " > /dev/null";
|
|
$params->{'command'} = $command;
|
|
|
|
# Set up the benchmark results structure.
|
|
my $bresult = { 'name' => $bench, 'msg' => $params->{'logmsg'} };
|
|
|
|
if ($verbose > 0) {
|
|
printf "\n%d x %s ", $copies, $params->{'logmsg'};
|
|
}
|
|
|
|
printLog($logFile,
|
|
"\n########################################################\n");
|
|
printLog($logFile, "%s -- %s\n",
|
|
$params->{'logmsg'}, number($copies, "copy", "copies"));
|
|
printLog($logFile, "==> %s\n\n", $command);
|
|
|
|
# Run the test iterations, as given by the "repeat" parameter.
|
|
my $repeats = $shortIterCount;
|
|
$repeats = $longIterCount if $params->{'repeat'} eq 'long';
|
|
$repeats = 1 if $params->{'repeat'} eq 'single';
|
|
my $pres = [ ];
|
|
for (my $i = 1; $i <= $repeats; ++$i) {
|
|
printLog($logFile, "#### Pass %d\n\n", $i);
|
|
|
|
# make an attempt to flush buffers
|
|
system("sync; sleep 1; sync; sleep 2");
|
|
# display heartbeat
|
|
if ($verbose > 0) {
|
|
printf " %d", $i;
|
|
}
|
|
|
|
# Execute one pass of the benchmark.
|
|
my $presult = runOnePass($params, $verbose, $logFile, $copies);
|
|
push(@$pres, $presult);
|
|
}
|
|
$bresult->{'passes'} = $pres;
|
|
|
|
# Calculate the averaged results for this benchmark.
|
|
combinePassResults($bench, $tparams, $bresult, $logFile);
|
|
|
|
# Log the results.
|
|
if ($copies == 1) {
|
|
printLog($logFile, "\n>>>> Results of 1 copy\n");
|
|
} else {
|
|
printLog($logFile, "\n>>>> Sum of %d copies\n", $copies);
|
|
}
|
|
foreach my $k ( 'score', 'time', 'iterations' ) {
|
|
printLog($logFile, ">>>> %s: %s\n", $k, $bresult->{$k});
|
|
}
|
|
printLog($logFile, "\n");
|
|
|
|
# Some specific cleanup routines.
|
|
if ($bench eq "C") {
|
|
unlink(${TESTDIR} . "/cctest.o");
|
|
unlink(${TESTDIR} . "/a.out");
|
|
}
|
|
|
|
if ($verbose > 0) {
|
|
printf "\n";
|
|
}
|
|
|
|
$bresult;
|
|
}
|
|
|
|
|
|
# Run the named benchmarks.
|
|
sub runTests {
|
|
my ( $tests, $verbose, $logFile, $copies ) = @_;
|
|
|
|
# Run all the requested tests and gather the results.
|
|
my $results = { 'start' => time(), 'copies' => $copies };
|
|
foreach my $bench (@$tests) {
|
|
# Get the parameters for this benchmark.
|
|
my $params = $testParams->{$bench};
|
|
if (!defined($params)) {
|
|
abortRun("unknown benchmark \"$bench\"");
|
|
}
|
|
|
|
# If the benchmark doesn't want to run with this many copies, skip it.
|
|
my $cat = $params->{'cat'};
|
|
my $maxCopies = $testCats->{$cat}{'maxCopies'};
|
|
next if ($copies > $maxCopies);
|
|
|
|
# Run the benchmark.
|
|
my $bresult = runBenchmark($bench, $params, $verbose, $logFile, $copies);
|
|
$results->{$bench} = $bresult;
|
|
}
|
|
$results->{'end'} = time();
|
|
|
|
# Generate a sorted list of benchmarks for which we have results.
|
|
my @benches = grep {
|
|
ref($results->{$_}) eq "HASH" && defined($results->{$_}{'msg'})
|
|
} keys(%$results);
|
|
@benches = sort {
|
|
$results->{$a}{'msg'} cmp $results->{$b}{'msg'}
|
|
} @benches;
|
|
$results->{'list'} = \@benches;
|
|
|
|
# Generate index scores for the results relative to the baseline data.
|
|
indexResults($results);
|
|
|
|
$results;
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# TEXT REPORTS
|
|
############################################################################
|
|
|
|
# Display a banner indicating the configuration of the system under test
|
|
# to the given file desc.
|
|
sub displaySystem {
|
|
my ( $info, $fd ) = @_;
|
|
|
|
# Display basic system info.
|
|
printf $fd " System: %s: %s\n", $info->{'name'}, $info->{'system'};
|
|
printf $fd " OS: %s -- %s -- %s\n",
|
|
$info->{'os'}, $info->{'osRel'}, $info->{'osVer'};
|
|
printf $fd " Machine: %s (%s)\n", $info->{'mach'}, $info->{'platform'};
|
|
printf $fd " Language: %s\n", $info->{'language'};
|
|
|
|
# Get and display details on the CPUs, if possible.
|
|
my $cpus = $info->{'cpus'};
|
|
if (!defined($cpus)) {
|
|
printf $fd " CPU: no details available\n";
|
|
} else {
|
|
for (my $i = 0; $i <= $#$cpus; ++$i) {
|
|
printf $fd " CPU %d: %s (%.1f bogomips)\n",
|
|
$i, $cpus->[$i]{'model'}, $cpus->[$i]{'bogo'};
|
|
printf $fd " %s\n", $cpus->[$i]{'flags'};
|
|
}
|
|
}
|
|
|
|
if ($info->{'graphics'}) {
|
|
printf $fd " Graphics: %s\n", $info->{'graphics'};
|
|
}
|
|
|
|
# Display system load and usage info.
|
|
printf $fd " %s; runlevel %s\n\n", $info->{'load'}, $info->{'runlevel'};
|
|
}
|
|
|
|
|
|
# Display the test scores from the given set of test results.
|
|
sub logResults {
|
|
my ( $results, $outFd ) = @_;
|
|
|
|
# Display the individual test scores.
|
|
foreach my $bench (@{$results->{'list'}}) {
|
|
my $bresult = $results->{$bench};
|
|
|
|
printf $outFd "%-40s %12.1f %-5s (%.1f s, %d samples)\n",
|
|
$bresult->{'msg'},
|
|
$bresult->{'score'},
|
|
$bresult->{'scorelabel'},
|
|
$bresult->{'time'},
|
|
$bresult->{'iterations'};
|
|
}
|
|
|
|
printf $outFd "\n";
|
|
}
|
|
|
|
|
|
# Display index scores, if any, for the given run results.
|
|
sub logIndexCat {
|
|
my ( $results, $cat, $outFd ) = @_;
|
|
|
|
my $total = $results->{'numIndex'}{$cat};
|
|
my $indexed = $results->{'indexed'}{$cat};
|
|
my $iscore = $results->{'index'}{$cat};
|
|
my $full = $total == $indexed;
|
|
|
|
# If there are no indexed scores, just say so.
|
|
if (!defined($indexed) || $indexed == 0) {
|
|
printf $outFd "No index results available for %s\n\n",
|
|
$testCats->{$cat}{'name'};
|
|
return;
|
|
}
|
|
|
|
# Display the header, depending on whether we have a full set of index
|
|
# scores, or a partial set.
|
|
my $head = $testCats->{$cat}{'name'} .
|
|
($full ? " Index Values" : " Partial Index");
|
|
printf $outFd "%-40s %12s %12s %8s\n",
|
|
$head, "BASELINE", "RESULT", "INDEX";
|
|
|
|
# Display the individual test scores.
|
|
foreach my $bench (@{$results->{'list'}}) {
|
|
my $bresult = $results->{$bench};
|
|
next if $bresult->{'cat'} ne $cat;
|
|
|
|
if (defined($bresult->{'iscore'}) && defined($bresult->{'index'})) {
|
|
printf $outFd "%-40s %12.1f %12.1f %8.1f\n",
|
|
$bresult->{'msg'}, $bresult->{'iscore'},
|
|
$bresult->{'score'}, $bresult->{'index'};
|
|
} else {
|
|
printf $outFd "%-40s %12s %12.1f %8s\n",
|
|
$bresult->{'msg'}, "---",
|
|
$bresult->{'score'}, "---";
|
|
}
|
|
}
|
|
|
|
# Display the overall score.
|
|
my $title = $testCats->{$cat}{'name'} . " Index Score";
|
|
if (!$full) {
|
|
$title .= " (Partial Only)";
|
|
}
|
|
printf $outFd "%-40s %12s %12s %8s\n", "", "", "", "========";
|
|
printf $outFd "%-66s %8.1f\n", $title, $iscore;
|
|
|
|
printf $outFd "\n";
|
|
}
|
|
|
|
|
|
# Display index scores, if any, for the given run results.
|
|
sub logIndex {
|
|
my ( $results, $outFd ) = @_;
|
|
|
|
my $count = $results->{'indexed'};
|
|
foreach my $cat (keys(%$count)) {
|
|
logIndexCat($results, $cat, $outFd);
|
|
}
|
|
}
|
|
|
|
|
|
# Dump the given run results into the given report file.
|
|
sub summarizeRun {
|
|
my ( $systemInfo, $results, $verbose, $reportFd ) = @_;
|
|
|
|
# Display information about this test run.
|
|
printf $reportFd "------------------------------------------------------------------------\n";
|
|
printf $reportFd "Benchmark Run: %s %s - %s\n",
|
|
strftime("%a %b %d %Y", localtime($results->{'start'})),
|
|
strftime("%H:%M:%S", localtime($results->{'start'})),
|
|
strftime("%H:%M:%S", localtime($results->{'end'}));
|
|
printf $reportFd "%s in system; running %s of tests\n",
|
|
number($systemInfo->{'numCpus'}, "CPU"),
|
|
number($results->{'copies'}, "parallel copy", "parallel copies");
|
|
printf $reportFd "\n";
|
|
|
|
# Display the run scores.
|
|
logResults($results, $reportFd);
|
|
|
|
# Display the indexed scores, if any.
|
|
logIndex($results, $reportFd);
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# HTML REPORTS
|
|
############################################################################
|
|
|
|
# Dump the given run results into the given report file.
|
|
sub runHeaderHtml {
|
|
my ( $systemInfo, $reportFd ) = @_;
|
|
|
|
# Display information about this test run.
|
|
my $title = sprintf "Benchmark of %s / %s on %s",
|
|
$systemInfo->{'name'}, $systemInfo->{'system'},
|
|
strftime("%a %b %d %Y", localtime());
|
|
|
|
print $reportFd <<EOF;
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
|
"http://www.w3.org/TR/html4/loose.dtd">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
<meta name="keywords" content="linux, benchmarks, benchmarking">
|
|
<title>$title</title>
|
|
<style type="text/css">
|
|
table {
|
|
margin: 1em 1em 1em 0;
|
|
background: #f9f9f9;
|
|
border: 1px #aaaaaa solid;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
table th, table td {
|
|
border: 1px #aaaaaa solid;
|
|
padding: 0.2em;
|
|
}
|
|
|
|
table th {
|
|
background: #f2f2f2;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
EOF
|
|
|
|
# Display information about this test run.
|
|
printf $reportFd "<h2>%s</h2>\n", $title;
|
|
printf $reportFd "<p><b>BYTE UNIX Benchmarks (Version %s)</b></p>\n\n",
|
|
$version;
|
|
}
|
|
|
|
|
|
# Display a banner indicating the configuration of the system under test
|
|
# to the given file desc.
|
|
sub displaySystemHtml {
|
|
my ( $info, $fd ) = @_;
|
|
|
|
printf $fd "<h3>Test System Information</h3>\n";
|
|
printf $fd "<p><table>\n";
|
|
|
|
# Display basic system info.
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <td><b>System:</b></td>\n";
|
|
printf $fd " <td colspan=2>%s: %s</td>\n",
|
|
$info->{'name'}, $info->{'system'};
|
|
printf $fd "</tr><tr>\n";
|
|
printf $fd " <td><b>OS:</b></td>\n";
|
|
printf $fd " <td colspan=2>%s -- %s -- %s</td>\n",
|
|
$info->{'os'}, $info->{'osRel'}, $info->{'osVer'};
|
|
printf $fd "</tr><tr>\n";
|
|
printf $fd " <td><b>Machine:</b></td>\n";
|
|
printf $fd " <td colspan=2>%s: %s</td>\n",
|
|
$info->{'mach'}, $info->{'platform'};
|
|
printf $fd "</tr><tr>\n";
|
|
printf $fd " <td><b>Language:</b></td>\n";
|
|
printf $fd " <td colspan=2>%s</td>\n", $info->{'language'};
|
|
printf $fd "</tr>\n";
|
|
|
|
# Get and display details on the CPUs, if possible.
|
|
my $cpus = $info->{'cpus'};
|
|
if (!defined($cpus)) {
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <td><b>CPUs:</b></td>\n";
|
|
printf $fd " <td colspan=2>no details available</td>\n";
|
|
printf $fd "</tr>\n";
|
|
} else {
|
|
for (my $i = 0; $i <= $#$cpus; ++$i) {
|
|
printf $fd "<tr>\n";
|
|
if ($i == 0) {
|
|
printf $fd " <td rowspan=%d><b>CPUs:</b></td>\n", $#$cpus + 1;
|
|
}
|
|
printf $fd " <td><b>%d:</b></td>\n", $i;
|
|
printf $fd " <td>%s (%.1f bogomips)<br/>\n",
|
|
$cpus->[$i]{'model'}, $cpus->[$i]{'bogo'};
|
|
printf $fd " %s</td>\n", $cpus->[$i]{'flags'};
|
|
printf $fd "</tr>\n";
|
|
}
|
|
}
|
|
|
|
# Display graphics hardware info.
|
|
if ($info->{'graphics'}) {
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <td><b>Graphics:</b></td>\n";
|
|
printf $fd " <td colspan=2>%s</td>\n", $info->{'graphics'};
|
|
printf $fd "</tr>\n";
|
|
}
|
|
|
|
# Display system runlevel, load and usage info.
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <td><b>Uptime:</b></td>\n";
|
|
printf $fd " <td colspan=2>%s; runlevel %s</td>\n",
|
|
$info->{'load'}, $info->{'runlevel'};
|
|
printf $fd "</tr>\n";
|
|
|
|
printf $fd "</table></p>\n\n";
|
|
}
|
|
|
|
|
|
# Display the test scores from the given set of test results
|
|
# for a given category of tests.
|
|
sub logCatResultsHtml {
|
|
my ( $results, $cat, $fd ) = @_;
|
|
|
|
my $numIndex = $results->{'numIndex'}{$cat};
|
|
my $indexed = $results->{'indexed'}{$cat};
|
|
my $iscore = $results->{'index'}{$cat};
|
|
my $full = defined($indexed) && $indexed == $numIndex;
|
|
|
|
# If there are no results in this category, just ignore it.
|
|
if (!defined($results->{'numCat'}{$cat}) ||
|
|
$results->{'numCat'}{$cat} == 0) {
|
|
return;
|
|
}
|
|
|
|
# Say the category. If there are no indexed scores, just say so.
|
|
my $warn = "";
|
|
if (!defined($indexed) || $indexed == 0) {
|
|
$warn = " — no index results available";
|
|
} elsif (!$full) {
|
|
$warn = " — not all index tests were run;" .
|
|
" only a partial index score is available";
|
|
}
|
|
printf $fd "<h4>%s%s</h4>\n", $testCats->{$cat}{'name'}, $warn;
|
|
|
|
printf $fd "<p><table width=\"100%%\">\n";
|
|
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <th align=left>Test</th>\n";
|
|
printf $fd " <th align=right>Score</th>\n";
|
|
printf $fd " <th align=left>Unit</th>\n";
|
|
printf $fd " <th align=right>Time</th>\n";
|
|
printf $fd " <th align=right>Iters.</th>\n";
|
|
printf $fd " <th align=right>Baseline</th>\n";
|
|
printf $fd " <th align=right>Index</th>\n";
|
|
printf $fd "</tr>\n";
|
|
|
|
# Display the individual test scores.
|
|
foreach my $bench (@{$results->{'list'}}) {
|
|
my $bresult = $results->{$bench};
|
|
next if $bresult->{'cat'} ne $cat;
|
|
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <td><b>%s</b></td>\n", $bresult->{'msg'};
|
|
printf $fd " <td align=right><tt>%.1f</tt></td>\n",
|
|
$bresult->{'score'};
|
|
printf $fd " <td align=left><tt>%s</tt></td>\n",
|
|
$bresult->{'scorelabel'};
|
|
printf $fd " <td align=right><tt>%.1f s</tt></td>\n",
|
|
$bresult->{'time'};
|
|
printf $fd " <td align=right><tt>%d</tt></td>\n",
|
|
$bresult->{'iterations'};
|
|
|
|
if (defined($bresult->{'index'})) {
|
|
printf $fd " <td align=right><tt>%.1f</tt></td>\n",
|
|
$bresult->{'iscore'};
|
|
printf $fd " <td align=right><tt>%.1f</tt></td>\n",
|
|
$bresult->{'index'};
|
|
}
|
|
printf $fd "</tr>\n";
|
|
}
|
|
|
|
# Display the overall score.
|
|
if (defined($indexed) && $indexed > 0) {
|
|
my $title = $testCats->{$cat}{'name'} . " Index Score";
|
|
if (!$full) {
|
|
$title .= " (Partial Only)";
|
|
}
|
|
printf $fd "<tr>\n";
|
|
printf $fd " <td colspan=6><b>%s:</b></td>\n", $title;
|
|
printf $fd " <td align=right><b><tt>%.1f</tt></b></td>\n", $iscore;
|
|
printf $fd "</tr>\n";
|
|
}
|
|
|
|
printf $fd "</table></p>\n\n";
|
|
}
|
|
|
|
|
|
# Display index scores, if any, for the given run results.
|
|
sub logResultsHtml {
|
|
my ( $results, $fd ) = @_;
|
|
|
|
foreach my $cat (keys(%$testCats)) {
|
|
logCatResultsHtml($results, $cat, $fd);
|
|
}
|
|
}
|
|
|
|
|
|
# Dump the given run results into the given report file.
|
|
sub summarizeRunHtml {
|
|
my ( $systemInfo, $results, $verbose, $reportFd ) = @_;
|
|
|
|
# Display information about this test run.
|
|
my $time = $results->{'end'} - $results->{'start'};
|
|
printf $reportFd "<p><hr/></p>\n";
|
|
printf $reportFd "<h3>Benchmark Run: %s; %s</h3>\n",
|
|
number($systemInfo->{'numCpus'}, "CPU"),
|
|
number($results->{'copies'}, "parallel process", "parallel processes");
|
|
printf $reportFd "<p>Time: %s - %s; %dm %02ds</p>\n",
|
|
strftime("%H:%M:%S", localtime($results->{'start'})),
|
|
strftime("%H:%M:%S", localtime($results->{'end'})),
|
|
int($time / 60), $time % 60;
|
|
printf $reportFd "\n";
|
|
|
|
# Display the run scores.
|
|
logResultsHtml($results, $reportFd);
|
|
}
|
|
|
|
|
|
sub runFooterHtml {
|
|
my ( $reportFd ) = @_;
|
|
|
|
print $reportFd <<EOF;
|
|
<p><hr/></p>
|
|
<div><b>No Warranties:</b> This information is provided free of charge and "as
|
|
is" without any warranty, condition, or representation of any kind,
|
|
either express or implied, including but not limited to, any warranty
|
|
respecting non-infringement, and the implied warranties of conditions
|
|
of merchantability and fitness for a particular purpose. All logos or
|
|
trademarks on this site are the property of their respective owner. In
|
|
no event shall the author be liable for any
|
|
direct, indirect, special, incidental, consequential or other damages
|
|
howsoever caused whether arising in contract, tort, or otherwise,
|
|
arising out of or in connection with the use or performance of the
|
|
information contained on this web site.</div>
|
|
</body>
|
|
</html>
|
|
EOF
|
|
}
|
|
|
|
|
|
############################################################################
|
|
# MAIN
|
|
############################################################################
|
|
|
|
sub main {
|
|
my @args = @_;
|
|
|
|
my $params = parseArgs(@args);
|
|
my $verbose = $params->{'verbose'} || 1;
|
|
if ($params->{'iterations'}) {
|
|
$longIterCount = $params->{'iterations'};
|
|
$shortIterCount = int(($params->{'iterations'} + 1) / 3);
|
|
$shortIterCount = 1 if ($shortIterCount < 1);
|
|
}
|
|
|
|
# If no benchmark units have be specified, do "index".
|
|
my $tests = $params->{'tests'};
|
|
if ($#$tests < 0) {
|
|
$tests = $index;
|
|
}
|
|
|
|
preChecks();
|
|
my $systemInfo = getSystemInfo();
|
|
|
|
# If the number of copies to run was not set, set it to 1
|
|
# and the number of CPUs in the system (if > 1).
|
|
my $copies = $params->{'copies'};
|
|
if (!$copies || scalar(@$copies) == 0) {
|
|
push(@$copies, 1);
|
|
if (defined($systemInfo->{'numCpus'}) && $systemInfo->{'numCpus'} > 1) {
|
|
push(@$copies, $systemInfo->{'numCpus'});
|
|
}
|
|
}
|
|
|
|
# Display the program banner.
|
|
system("cat \"${BINDIR}/unixbench.logo\"");
|
|
|
|
if ($verbose > 1) {
|
|
printf "\n", join(", ", @$tests);
|
|
printf "Tests to run: %s\n", join(", ", @$tests);
|
|
}
|
|
|
|
# Generate unique file names for the report and log file.
|
|
my $reportFile = logFile($systemInfo);
|
|
my $reportHtml = $reportFile . ".html";
|
|
my $logFile = $reportFile . ".log";
|
|
|
|
# Open the log file for writing.
|
|
open(my $reportFd, ">", $reportFile) ||
|
|
die("Run: can't write to $reportFile\n");
|
|
open(my $reportFd2, ">", $reportHtml) ||
|
|
die("Run: can't write to $reportHtml\n");
|
|
printf $reportFd " BYTE UNIX Benchmarks (Version %s)\n\n", $version;
|
|
runHeaderHtml($systemInfo, $reportFd2);
|
|
|
|
# Dump information about the system under test.
|
|
displaySystem($systemInfo, $reportFd);
|
|
displaySystemHtml($systemInfo, $reportFd2);
|
|
|
|
# Run the tests! Do a test run once for each desired number of copies;
|
|
# for example, on a 2-CPU system, we may do a single-processing run
|
|
# followed by a dual-processing run.
|
|
foreach my $c (@$copies) {
|
|
if ($verbose > 1) {
|
|
printf "Run with %s\n", number($c, "copy", "copies");
|
|
}
|
|
my $results = runTests($tests, $verbose, $logFile, $c);
|
|
|
|
summarizeRun($systemInfo, $results, $verbose, $reportFd);
|
|
summarizeRunHtml($systemInfo, $results, $verbose, $reportFd2);
|
|
}
|
|
|
|
runFooterHtml($reportFd2);
|
|
|
|
# Finish the report.
|
|
close($reportFd);
|
|
close($reportFd2);
|
|
|
|
# Display the report, if not in quiet mode.
|
|
if ($verbose > 0) {
|
|
printf "\n";
|
|
printf "========================================================================\n";
|
|
system("cat \"$reportFile\"");
|
|
}
|
|
|
|
0;
|
|
}
|
|
|
|
|
|
exit(main(@ARGV));
|
|
|