#!/usr/bin/perl -w
require
5.002;
$| = 1;
use
vars
qw($opt_c $opt_t $opt_d $opt_s $opt_v)
;
getopts(
"c:t:d:sv"
) or usage();
my
@perls
;
for
(
@ARGV
) {
eval
{
push
(
@perls
, Perl->new(
$_
));
};
if
($@) {
$@ =~ s/ at (.*) line (\d+).*\n//;
warn
"$@, skipping...\n"
;
}
}
usage()
unless
@perls
;
my
$HOSTNAME_HTML
= htmlesc(hostname());
my
$dir
=
$opt_d
;
unless
(
$dir
) {
my
$cnt
= 1;
while
(1) {
$dir
=
sprintf
"benchres-%03d"
,
$cnt
;
last
unless
-e
$dir
;
$cnt
++;
}
}
mkdir
(
$dir
, 0755) ||
die
"Can't mkdir(\"$dir\"): $!"
;
open
(INDEX,
">$dir/index.html"
) ||
die
"Can't create $dir/index.html: $!"
;
print
INDEX
"<html>\n"
;
print
INDEX
"<head>\n"
;
print
INDEX
" <title>PerlBench $HOSTNAME_HTML "
. time2iso() .
"</title>\n"
;
print
INDEX
qq( <link rel="stylesheet" href="style.css" type="text/css">\n)
;
link_or_copy(
"style.css"
);
my
$use_overlib
;
if
(link_or_copy(
"overlib.js"
)) {
$use_overlib
++;
print
INDEX
qq( <script type="text/javascript" src="overlib.js"></script>\n)
;
}
print
INDEX
"</head>\n"
;
print
INDEX
"<body>\n"
;
print
INDEX
qq(<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>\n)
;
print
INDEX
qq(<h1>PerlBench results from $HOSTNAME_HTML at )
. time2iso() .
qq(</h1>\n)
;
my
%config_summary
;
{
my
%cnf
;
my
$keymax
=
length
(
"version"
);
for
my
$p
(
@perls
) {
while
(
my
(
$k
,
$v
) =
each
%{
$p
->{config} || {}}) {
$cnf
{
$k
}{
$v
}++;
$keymax
=
length
(
$k
)
if
length
(
$k
) >
$keymax
;
}
}
for
my
$p
(
@perls
) {
my
$label
=
$p
->{label} ||
''
;
my
$name
=
$p
->{name} || basename(
$p
->{path});
print
"$label) $name\n"
;
printf
"\t%-*s = %s\n"
,
$keymax
,
"version"
,
$p
->{version};
printf
"\t%-*s = %s\n"
,
$keymax
,
"git-version"
,
$p
->{git_version}
if
$p
->{git_version};
printf
"\t%-*s = %s\n"
,
$keymax
,
"path"
,
$p
->{path};
for
my
$k
(
sort
keys
%{
$p
->{config} || {}}) {
next
if
$cnf
{
$k
}{
$p
->{config}{
$k
}} ==
@perls
;
printf
"\t%-*s = %s\n"
,
$keymax
,
$k
,
$p
->{config}{
$k
};
$config_summary
{
$k
}{
$label
} =
$p
->{config}{
$k
};
}
print
"\n"
;
open
(RES,
">$dir/CONFIG-$p->{label}.txt"
) ||
die
;
$p
->run_cmd(
*V
,
"-V"
) ||
die
"Can't run $p->{path}: $?"
;
while
(<V>) {
print
RES
$_
;
}
close
(V);
close
(RES) ||
die
"Can't write: $!"
;
}
}
my
$factor
=
$opt_c
;
unless
(
$factor
) {
$factor
= -e
'cpu_factor'
? `$^X cpu_factor` : `$^X -S cpu_factor`;
chomp
(
$factor
);
die
"Can't calculate cpu speed factor"
unless
$factor
;
}
file(
"$dir/CPU_FACTOR"
,
"$factor\n"
);
die
"No test directory found"
unless
-d
't'
;
my
@tests
;
find(
sub
{ /\.t$/ &&
push
(
@tests
,
$File::Find::name
) },
"benchmarks"
);
if
(
$opt_t
) {
@tests
=
grep
/
$opt_t
/o,
@tests
;
}
@tests
=
sort
@tests
;
my
%empty_cycles
;
for
my
$p
(
@perls
) {
$p
->{empty_cycles} = (
$empty_cycles
{
$p
->{path}} ||=
do
{
my
$empty_cycles
;
$p
->run_cmd(
*P
,
"empty.t"
,
$factor
);
while
(<P>) {
next
unless
/^Cycles-Per-Sec:\s*(\S+)/;
$empty_cycles
=
int
($1);
last
;
}
close
(P);
die
"Could not determine empty test speed for $p->{path}"
unless
$empty_cycles
;
$empty_cycles
;
});
$p
->{point_sum} = 0;
}
print
INDEX
"<table border=1>\n"
;
print
INDEX
" <tr>\n <th> </th>\n"
;
print
"\n"
;
print
" "
x 20;
for
my
$p
(
@perls
) {
printf
"%8s"
,
$p
->{label};
my
$h
= htmlesc(
$p
->{label});
my
$overlib_attr
=
""
;
if
(
$use_overlib
&&
$p
->{name}) {
$overlib_attr
=
qq( onmouseover="return overlib('$p->{name}')
;" onmouseout=
"return nd();"
);
}
print
INDEX
qq( <th><a href="CONFIG-$h.txt"$overlib_attr>$h</a></th>\n)
;
}
print
"\n"
;
print
INDEX
" </tr>\n"
;
print
" "
x 20;
for
my
$p
(
@perls
) {
printf
"%8s"
, (
"-"
x max(3,
length
(
$p
->{label})));
}
print
"\n"
;
my
$test
;
for
$test
(
@tests
) {
unless
(
open
(T,
$test
)) {
warn
"Can't open $test: $!"
;
next
;
}
my
$name
=
$test
;
$name
=~ s,^benchmarks/,,;
$name
=~ s,\.t$,,;
my
$save_file
=
"$dir/$name/test.txt"
;
mkpath(dirname(
$save_file
), 0, 0755);
open
(SAVE,
">$save_file"
) ||
die
"Can't create $save_file: $!"
;
(
my
$save_file_link
=
$save_file
) =~ s,^\Q
$dir
\E/,,;
$save_file_link
= htmlesc(
$save_file_link
);
my
%prop
;
while
(<T>) {
print
SAVE
$_
;
next
unless
/^\
my
(
$k
,
$v
) = (
lc
($1), $2);
if
(
defined
$prop
{
$k
}) {
$prop
{
$k
} .=
"\n$v"
;
}
else
{
$prop
{
$k
} =
$v
;
}
}
close
(T);
close
(SAVE) ||
die
"Can't write $save_file: $!"
;
printf
"%-20s"
,
$name
;
my
$overlib_attr
=
""
;
if
(
$use_overlib
&&
$prop
{name}) {
$overlib_attr
=
qq( onmouseover="return overlib('$prop{name}')
;" onmouseout=
"return nd();"
);
}
print
INDEX
qq( <tr>\n <th align=left><a href="$save_file_link"$overlib_attr>)
. htmlesc(
$name
) .
"</a></th>\n"
;
my
$scale
;
my
$p
;
for
my
$p
(
@perls
) {
if
(
$p
->{version} <
$prop
{
'require'
}) {
printf
"%8s"
,
"N/A"
;
print
INDEX
" <td>N/A</td>\n"
;
next
;
}
my
$res_file
=
"$dir/$name/"
.
$p
->{label} .
".txt"
;
mkpath(dirname(
$res_file
), 0, 0755);
open
(RES,
">$res_file"
) ||
die
"Can't create $res_file: $!"
;
(
my
$res_file_link
=
$res_file
) =~ s,^\Q
$dir
\E/,,;
$res_file_link
= htmlesc(
$res_file_link
);
my
$points
;
my
$popup_text
=
""
;
$p
->run_cmd(
*P
,
$test
,
$factor
,
$p
->{empty_cycles});
while
(<P>) {
print
RES
$_
;
if
(/^Bench-Points:\s+(\S+)/) {
$points
= $1;
}
if
(/^(?:\w+-Time|CPU|Cycles-Per-Sec|Loop-Overhead):/) {
$popup_text
.=
"<br>"
if
length
(
$popup_text
);
$popup_text
.=
$_
;
chomp
(
$popup_text
);
}
}
close
(P);
close
(RES);
my
$overlib_attr
=
""
;
if
(
$use_overlib
) {
$overlib_attr
=
qq( onmouseover="return overlib('$popup_text')
;" onmouseout=
"return nd();"
);
}
unless
(
defined
$points
) {
printf
"%8s"
,
"-"
;
print
INDEX
qq( <td><a href="$res_file_link"$overlib_attr>??</a></td>\n)
;
next
;
}
unless
(
$opt_s
) {
unless
(
defined
$scale
) {
$scale
= 100 /
$points
;
}
$points
*=
$scale
;
}
printf
"%8.0f"
,
$points
;
printf
INDEX
qq( <td align=right><a href="%s"%s>%.0f</a></td>\n)
,
$res_file_link
,
$overlib_attr
,
$points
;
$p
->{point_sum} +=
$points
;
$p
->{no_tests}++;
}
print
INDEX
" </tr>\n"
;
print
"\n"
;
}
print
"\n"
;
printf
"%-20s"
,
"AVERAGE"
;
for
my
$p
(
@perls
) {
printf
"%8.0f"
,
$p
->{point_sum} /
$p
->{no_tests};
}
print
INDEX
" <tr>\n"
;
print
INDEX
" <th align=left>Average</th>\n"
;
for
my
$p
(
@perls
) {
printf
INDEX
qq( <td align=right>%.0f</td>\n)
,
$p
->{point_sum} /
$p
->{no_tests};
}
print
INDEX
" </tr>\n"
;
print
INDEX
"</table>\n"
;
print
INDEX
"<p><small>Higher numbers are better. 200 is twice as fast as 100.</small></p>\n"
;
print
INDEX
"<h2>Configuration summary</h2>\n"
;
print
INDEX
"<p>Test ran on a $^O machine"
;
if
($^O ne
"MSWin32"
) {
my
$uname
= `uname -a`;
if
(
$uname
) {
print
INDEX
qq( that reports its uname as ")
. htmlesc(
$uname
) .
qq(")
;
}
}
print
INDEX
".\n"
;
print
INDEX
" Test run completed at "
.
substr
(time2iso(), 11) .
".\n"
;
print
INDEX
"</p>\n"
;
print
INDEX
"<table border=1>\n"
;
print
INDEX
" <tr>\n <th> </th>\n"
;
for
my
$p
(
@perls
) {
my
$h
= htmlesc(
$p
->{label});
print
INDEX
qq( <th><a href="CONFIG-$h.txt">$h</a></th>\n)
;
}
for
my
$k
(
"name"
,
"version"
,
"path"
) {
print
INDEX
" <tr>\n <th>$k</th>\n"
;
for
my
$p
(
@perls
) {
print
INDEX
" <td>"
. htmlesc(
$p
->{
$k
}) .
"</td>\n"
;
}
print
INDEX
" </tr>\n"
;
}
print
INDEX
" </tr>\n"
;
for
my
$k
(
sort
keys
%config_summary
) {
print
INDEX
" <tr>\n <th>"
. htmlesc(
$k
) .
"</th>\n"
;
for
my
$lab
(
map
$_
->{label},
@perls
) {
my
$v
=
$config_summary
{
$k
}{
$lab
};
$v
=
""
unless
defined
(
$v
);
my
$len
=
length
(
$v
);
$v
=
$len
? htmlesc(
$v
) :
" "
;
$v
=
"<small>$v</small>"
if
$len
> 40;
print
INDEX
" <td align=left>$v</td>\n"
;
}
print
INDEX
" </tr>\n"
;
}
print
INDEX
"</table>\n"
;
print
INDEX
"</body>\n</html>\n"
;
close
(INDEX) ||
die
"Can't write $dir/index.html\n"
;
my
$index_url
= abs_path(
$dir
);
if
($^O eq
"MSWin32"
) {
$index_url
=~ s,\\,/,g;
$index_url
=~ s,^([A-Za-z]):,/$1|,;
}
$index_url
=
"file://$index_url/index.html"
;
print
"\n\nResults saved in $index_url\n"
;
sub
usage
{
$0 =~ s,.*/,,;
die
"Usage: $0 [options] [lab1=]<perl1> [lab2=]<perl2>...
if
an arg is a directory,
'perl'
is appended to it
Recognized options:
-s don't scale numbers (so that first perl is always 100)
-t <filter> only tests that match <filter> regex are timed
-c <cpu-factor>
use
this factor to scale tests instead of running the
'cpu_factor'
program to determine it.
-d <dirname> where to save results
-v verbose - a bit of debug
";
}
sub
max
{
my
$max
=
shift
;
while
(
@_
) {
my
$n
=
shift
;
$max
=
$n
if
$n
>
$max
;
}
return
$max
;
}
sub
file {
my
$name
=
shift
;
if
(
@_
) {
my
$content
=
shift
;
open
(
my
$f
,
">"
,
$name
) ||
die
"Can't create '$name': $!"
;
binmode
(
$f
);
print
$f
$content
;
close
(
$f
) ||
die
"Can't write to '$name': $!"
;
if
(
@_
) {
my
$mode
=
shift
;
change_mode(
$mode
,
$name
);
}
}
else
{
open
(
my
$f
,
"<"
,
$name
) ||
return
undef
;
binmode
(
$f
);
local
$/;
return
scalar
<
$f
>;
}
}
sub
link_or_copy {
my
$f
=
shift
;
link
(
$f
,
"$dir/$f"
) ||
do
{
File::Copy::copy(
$f
,
$f
);
}
}
sub
htmlesc {
my
$str
=
shift
||
''
;
$str
=~ s/&/
&
;/g;
$str
=~ s/</
<
;/g;
$str
;
}
sub
time2iso
{
my
$time
=
shift
;
$time
=
time
unless
defined
$time
;
my
(
$sec
,
$min
,
$hour
,
$mday
,
$mon
,
$year
) =
localtime
(
$time
);
return
sprintf
(
"%04d-%02d-%02d %02d:%02d:%02d"
,
$year
+1900,
$mon
+1,
$mday
,
$hour
,
$min
,
$sec
);
}
BEGIN {
my
$NEXT_LABEL
=
"A"
;
sub
new
{
my
(
$class
,
$path
) =
@_
;
my
$label
;
if
(
$path
=~ s/^(\S+)=//) {
$label
= $1;
}
else
{
$label
=
$NEXT_LABEL
++;
}
unless
(-x
$path
) {
die
"$path is not executable"
;
next
;
}
if
(-d
$path
and -x
"$path/perl"
) {
$path
=
"$path/perl"
;
print
"updating given dir path to $path\n"
if
$::opt_v;
}
my
$self
=
bless
{
path
=>
$path
,
label
=>
$label
},
$class
;
$self
->run_cmd(
*V
,
'-e'
,
'print qq(This is perl ), $]+0, qq(\n)'
);
my
$version
= <V>;
close
V or
die
"closing pipe from perl: exit code $?"
;
chomp
$version
;
unless
(
$version
=~ /^This is perl (\d+.\d+)/) {
die
"$path does not appear to be a working perl"
;
}
$self
->{version} = $1;
$self
->run_cmd(
*V
,
'-v'
);
while
(<V>) {
if
(/^This is perl, v(\S+)/) {
$self
->{name} =
"perl-$1"
;
}
if
(/^This is perl (\d), version (\d+), subversion (\d+) \((\S+) (?:\((\S+)\){2})?/) {
print
"new format: $4 $5\n"
if
$::opt_v;
$self
->{name} =
"perl-$1"
;
$self
->{git_version} = $5
}
if
(/^Binary build (\d+.*) provided by ActiveState/) {
$self
->{name} .=
" build $1"
;
$self
->{name} =~ s/^perl/ActivePerl/;
}
}
close
(V);
if
(
$self
->{version} >= 5) {
my
$prog
=
'use Config; Config::config_vars(qw(cc ccversion gccversion optimize ccflags usethreads use64bitint use64bitall usemymalloc))'
;
$self
->run_cmd(
*CONFIG
,
'-e'
,
$prog
);
while
(<CONFIG>) {
next
unless
/^(\w+)=
'([^'
]+)
'/; #'
#
$self
->{config}{$1} = $2;
}
close
(CONFIG);
}
return
$self
;
}
my
$ld_path
= Cwd::extLibpath()
if
$^O eq
'os2'
;
$ld_path
.=
';'
if
$ld_path
and $^O eq
'os2'
;
sub
cmd
{
my
$self
=
shift
;
my
$path
=
$self
->{path};
(
my
$pdir
=
$path
) =~ s,[/\\][^/\\]+$,/,;
if
(-d
"$pdir/lib"
) {
Cwd::extLibpath_set(
"$ld_path$pdir"
)
if
$^O eq
'os2'
;
(
$path
,
'-I'
,
"$pdir/lib"
);
}
else
{
$path
;
}
}
sub
run_cmd
{
my
$self
=
shift
;
my
@cmd
=
$self
->cmd;
my
$fh
=
shift
;
my
@args
=
map
{/\s/ ? OS ne
'MSWin32'
?
"'$_'"
:
"\"$_\""
:
$_
}
@_
;
open
(
$fh
,
"@cmd @args |"
) or
die
"Cannot pipe from '@cmd @args': $!"
;
}
}