#!/usr/bin/env perl # This program handles the results of the LLVM test-suite benchmark. # There are two stages: # prepare: collates multiple samples into a single log file containing # the arithmetic mean of each tests' results. # compare: compares two collated results and produce a file containing # the relative values between baseline and target runs, with # the geomean of compile and execution time at the end. # # Suggested usage: # # ... point install directory to the unmodified / previous compiler # $ ./run.sh -b # $ ./compare_lnt_benchmarks.pl prepare sanbox/build > baseline.txt # ... change install directory to the modified / current compiler # $ ./run.sh -b # $ ./compare_lnt_benchmarks.pl prepare sanbox/build > target.txt # $ ./compare_lnt_benchmarks.pl compare baseline.txt target.txt > results.txt use strict; use warnings; use Scalar::Util qw(looks_like_number); use Statistics::Descriptive; use Data::Dumper; my $syntax = "Syntax: $0 [action options]\n". "Actions:\n". "\tprepare: reads all report files in sample-N, collate into one run\n". "\t options: with report.txt or sample-N\n". "\tcompare: compares two runs, taking geomean of all benchmarks\n". "\t options: \n"; my $action = $ARGV[0]; die $syntax unless defined $action; ################################################### if ($action eq "prepare") { die $syntax unless defined $ARGV[1]; &prepare($ARGV[1]); } elsif ($action eq "compare") { die $syntax unless defined $ARGV[1] and defined $ARGV[2]; &compare($ARGV[1], $ARGV[2]); } else { die $syntax; } exit; ################################################### # prepare a number of logs into a collated results by average sub prepare($) { my ($basedir) = @_; my $logname = "report.simple.txt"; # Single report, just copy if (-f "$basedir/$logname") { my %results; my $header = &read_file("$basedir/$logname", \%results); &dump(\%results, $header); # Multiple reports, collate } elsif (-f "$basedir/sample-0/$logname") { my @results; my $header; # List all sample files opendir DIR, $basedir || die "Can't open '$basedir': $!\n"; my @samples = grep { /sample-\d+/ && -f "$basedir/$_/$logname" } readdir DIR; closedir DIR; die "Basedir '$basedir' has no txt logs\n" unless scalar @samples; # For each sample, read&push foreach my $s (@samples) { my %sample; $header = &read_file("$basedir/$s/$logname", \%sample); push @results, \%sample; } # Collate results my %result; &collate_results(\@results, \%result); &dump(\%result, $header); } else { die "Basedir '$basedir' has no txt logs\n" } } # compare two logs, producing the relative results and geomean sub compare($$) { my ($baseline_file, $target_file) = @_; die "Baseline report file '$baseline_file' doesn't exist or is not a file\n" unless -f $baseline_file; die "Target report file '$target_file' doesn't exist or is not a file\n" unless -f $target_file; my (%baseline, %target, %result) = ((), (), ()); my $header = &read_file($baseline_file, \%baseline); &read_file($target_file, \%target); &compare_results(\%baseline, \%target, \%result); &dump(\%result, '', 1); } ################################################### # reads a file, saving all data indexed by prog name # returns the header for further use sub read_file($$) { my ($filename, $result) = @_; my $header = ''; my $max = 1; open FH, $filename || die "Can't open $filename: $!\n"; while () { chomp(); my ($program, $sep, $cc_pass, $cc_time, $cc_real, $ex_pass, $ex_time, $ex_real) = split /\s+/; # Make sure we have the right file if (!$header) { die "Invalid header in $filename\n" unless $program eq "Program" and $cc_time eq "CC_Time" and $ex_time eq "Exec_Time"; $header = $_; next; } # Log each non-header line $result->{$program} = { 'cc_pass' => $cc_pass, 'cc_time' => $cc_time, 'cc_real' => $cc_real, 'ex_pass' => $ex_pass, 'ex_time' => $ex_time, 'ex_real' => $ex_real, }; # Max length prog name my $len = length($program); $max = $len if $len > $max; } close FH; $result->{'max'} = $max; return $header; } # returns "pass" AND "pass" sub pass_diff($$) { my ($baseline, $target) = @_; if (defined $baseline and defined $target and $baseline eq "pass" and $target eq "pass") { return "pass"; } elsif (!$baseline and $target) { return $target; } elsif ($baseline and !$target) { return $baseline; } else { return "fail"; } } # collate multiple reports into one, by getting the # average of each result into each field sub collate_results($$) { my ($results, $result) = @_; foreach my $sample (@$results) { foreach my $prog (keys %$sample) { next unless ref $sample->{$prog} eq "HASH"; # Set up statistics if (!defined $result->{$prog}) { $result->{$prog}->{'cc_time'} = new Statistics::Descriptive::Full(); $result->{$prog}->{'cc_real'} = new Statistics::Descriptive::Full(); $result->{$prog}->{'ex_time'} = new Statistics::Descriptive::Full(); $result->{$prog}->{'ex_real'} = new Statistics::Descriptive::Full(); } # gather results $result->{$prog}->{'cc_time'}->add_data($sample->{$prog}->{'cc_time'}); $result->{$prog}->{'cc_real'}->add_data($sample->{$prog}->{'cc_real'}); $result->{$prog}->{'ex_time'}->add_data($sample->{$prog}->{'ex_time'}); $result->{$prog}->{'ex_real'}->add_data($sample->{$prog}->{'ex_real'}); # collate pass status $result->{$prog}->{'cc_pass'} = &pass_diff($result->{$prog}->{'cc_pass'}, $sample->{$prog}->{'cc_pass'}); $result->{$prog}->{'ex_pass'} = &pass_diff($result->{$prog}->{'ex_pass'}, $sample->{$prog}->{'ex_pass'}); } # All max should be the same $result->{'max'} = $sample->{'max'}; } # collate results foreach my $prog (keys %$result) { next unless ref $result->{$prog} eq "HASH"; $result->{$prog}->{'cc_time'} = $result->{$prog}->{'cc_time'}->mean(); $result->{$prog}->{'cc_real'} = $result->{$prog}->{'cc_real'}->mean(); $result->{$prog}->{'ex_time'} = $result->{$prog}->{'ex_time'}->mean(); $result->{$prog}->{'ex_real'} = $result->{$prog}->{'ex_real'}->mean(); } } # compares two aggregated logs with absolute values # and creates a final result with relative values sub compare_results($$$) { my ($baseline, $target, $result) = @_; my $cc_geo = new Statistics::Descriptive::Full(); my $ex_geo = new Statistics::Descriptive::Full(); $result->{'cc_pass'} = "pass"; $result->{'ex_pass'} = "pass"; foreach my $prog (keys %$baseline) { next unless ref $baseline->{$prog} eq "HASH"; die "Program '$prog' in baseline doesn't exist in target\n" unless defined $target->{$prog}; # No zero on divisions my ($b, $t) = ($baseline->{$prog}, $target->{$prog}); foreach my $k (keys %$b) { $b->{$k} = 0.001 if looks_like_number($b->{$k}) and $b->{$k} == 0.0; } foreach my $k (keys %$t) { $t->{$k} = 0.001 if looks_like_number($t->{$k}) and $t->{$k} == 0.0; } # Proportional difference $result->{$prog} = { 'cc_pass' => &pass_diff($b->{'cc_pass'}, $t->{'cc_pass'}), 'cc_time' => ($t->{'cc_time'} / $b->{'cc_time'}) * 100, 'cc_real' => ($t->{'cc_real'} / $b->{'cc_real'}) * 100, 'ex_pass' => &pass_diff($b->{'ex_pass'}, $t->{'ex_pass'}), 'ex_time' => ($t->{'ex_time'} / $b->{'ex_time'}) * 100, 'ex_real' => ($t->{'ex_real'} / $b->{'ex_real'}) * 100, }; # Add data to statistical model (ignore real) $cc_geo->add_data($result->{$prog}->{'cc_time'}); $ex_geo->add_data($result->{$prog}->{'ex_time'}); # Update global "pass" status $result->{'cc_pass'} = &pass_diff($result->{'cc_pass'}, $result->{$prog}->{'cc_pass'}); $result->{'ex_pass'} = &pass_diff($result->{'ex_pass'}, $result->{$prog}->{'ex_pass'}); } # Get geomean of all differences / max length of prog name $result->{'cc_geo'} = $cc_geo->geometric_mean(); $result->{'ex_geo'} = $ex_geo->geometric_mean(); $result->{'max'} = $baseline->{'max'}; } # dumps the file in the same format as read # can dump the header or geomean, if requested sub dump($$) { my ($result, $header, $geo) = @_; my $max = $result->{'max'}; # If we have the header, print it if ($header) { print $header."\n"; } foreach my $prog (sort keys %$result) { next unless ref $result->{$prog} eq "HASH"; # Dump the program name, with spaces at the end my $p = $result->{$prog}; my $spaces = $max - length($prog); print $prog; print ' ' x $spaces; print "\t|\t"; # Compile time print "\t".$p->{'cc_pass'}; printf("\t%0.4f", $p->{'cc_time'}); printf("\t%0.4f", $p->{'cc_real'}); # Execution time printf "\t".$p->{'ex_pass'}; printf("\t%0.4f", $p->{'ex_time'}); printf("\t%0.4f", $p->{'ex_real'}); print "\n"; } # Print the final geomean line if ($geo) { print "GEOMEAN"; print ' ' x ($max - 7); print "\t|\t"; print "\t".$result->{'cc_pass'}; printf("\t%0.2f\t", $result->{'cc_geo'}); print "\t".$result->{'ex_pass'}; printf("\t%0.2f", $result->{'ex_geo'}); print "\n"; } }