Renato Golin | 94cc104 | 2016-04-26 11:02:23 +0100 | [diff] [blame] | 1 | #!/usr/bin/env perl |
| 2 | # This program handles the results of the LLVM test-suite benchmark. |
| 3 | # There are two stages: |
| 4 | # prepare: collates multiple samples into a single log file containing |
| 5 | # the arithmetic mean of each tests' results. |
| 6 | # compare: compares two collated results and produce a file containing |
| 7 | # the relative values between baseline and target runs, with |
| 8 | # the geomean of compile and execution time at the end. |
| 9 | # |
| 10 | # Suggested usage: |
| 11 | # |
| 12 | # ... point install directory to the unmodified / previous compiler |
| 13 | # $ ./run.sh -b |
| 14 | # $ ./compare_lnt_benchmarks.pl prepare sanbox/build > baseline.txt |
| 15 | # ... change install directory to the modified / current compiler |
| 16 | # $ ./run.sh -b |
| 17 | # $ ./compare_lnt_benchmarks.pl prepare sanbox/build > target.txt |
| 18 | # $ ./compare_lnt_benchmarks.pl compare baseline.txt target.txt > results.txt |
| 19 | |
| 20 | use strict; |
| 21 | use warnings; |
| 22 | use Scalar::Util qw(looks_like_number); |
| 23 | use Statistics::Descriptive; |
| 24 | use Data::Dumper; |
| 25 | |
| 26 | my $syntax = "Syntax: $0 <action> [action options]\n". |
| 27 | "Actions:\n". |
| 28 | "\tprepare: reads all report files in sample-N, collate into one run\n". |
| 29 | "\t options: <sanbox/build> with report.txt or sample-N\n". |
| 30 | "\tcompare: compares two runs, taking geomean of all benchmarks\n". |
| 31 | "\t options: <baseline.report.txt> <target.report.txt>\n"; |
| 32 | my $action = $ARGV[0]; |
| 33 | die $syntax unless defined $action; |
| 34 | |
| 35 | ################################################### |
| 36 | |
| 37 | if ($action eq "prepare") { |
| 38 | die $syntax unless defined $ARGV[1]; |
| 39 | &prepare($ARGV[1]); |
| 40 | } elsif ($action eq "compare") { |
| 41 | die $syntax unless defined $ARGV[1] and defined $ARGV[2]; |
| 42 | &compare($ARGV[1], $ARGV[2]); |
| 43 | } else { |
| 44 | die $syntax; |
| 45 | } |
| 46 | exit; |
| 47 | |
| 48 | ################################################### |
| 49 | |
| 50 | # prepare a number of logs into a collated results by average |
| 51 | sub prepare($) { |
| 52 | my ($basedir) = @_; |
| 53 | my $logname = "report.simple.txt"; |
| 54 | |
| 55 | # Single report, just copy |
| 56 | if (-f "$basedir/$logname") { |
| 57 | my %results; |
| 58 | my $header = &read_file("$basedir/$logname", \%results); |
| 59 | &dump(\%results, $header); |
| 60 | |
| 61 | # Multiple reports, collate |
| 62 | } elsif (-f "$basedir/sample-0/$logname") { |
| 63 | my @results; |
| 64 | my $header; |
| 65 | |
| 66 | # List all sample files |
| 67 | opendir DIR, $basedir || die "Can't open '$basedir': $!\n"; |
| 68 | my @samples = grep { /sample-\d+/ && -f "$basedir/$_/$logname" } readdir DIR; |
| 69 | closedir DIR; |
| 70 | die "Basedir '$basedir' has no txt logs\n" unless scalar @samples; |
| 71 | |
| 72 | # For each sample, read&push |
| 73 | foreach my $s (@samples) { |
| 74 | my %sample; |
| 75 | $header = &read_file("$basedir/$s/$logname", \%sample); |
| 76 | push @results, \%sample; |
| 77 | } |
| 78 | |
| 79 | # Collate results |
| 80 | my %result; |
| 81 | &collate_results(\@results, \%result); |
| 82 | &dump(\%result, $header); |
| 83 | |
| 84 | } else { |
| 85 | die "Basedir '$basedir' has no txt logs\n" |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | # compare two logs, producing the relative results and geomean |
| 90 | sub compare($$) { |
| 91 | my ($baseline_file, $target_file) = @_; |
| 92 | die "Baseline report file '$baseline_file' doesn't exist or is not a file\n" |
| 93 | unless -f $baseline_file; |
| 94 | die "Target report file '$target_file' doesn't exist or is not a file\n" |
| 95 | unless -f $target_file; |
| 96 | my (%baseline, %target, %result) = ((), (), ()); |
| 97 | |
| 98 | my $header = &read_file($baseline_file, \%baseline); |
| 99 | &read_file($target_file, \%target); |
| 100 | |
| 101 | &compare_results(\%baseline, \%target, \%result); |
| 102 | |
| 103 | &dump(\%result, '', 1); |
| 104 | } |
| 105 | |
| 106 | ################################################### |
| 107 | |
| 108 | # reads a file, saving all data indexed by prog name |
| 109 | # returns the header for further use |
| 110 | sub read_file($$) { |
| 111 | my ($filename, $result) = @_; |
| 112 | my $header = ''; |
| 113 | my $max = 1; |
| 114 | open FH, $filename || die "Can't open $filename: $!\n"; |
| 115 | while (<FH>) { |
| 116 | chomp(); |
| 117 | my ($program, $sep, |
| 118 | $cc_pass, $cc_time, $cc_real, |
| 119 | $ex_pass, $ex_time, $ex_real) = split /\s+/; |
| 120 | # Make sure we have the right file |
| 121 | if (!$header) { |
| 122 | die "Invalid header in $filename\n" |
| 123 | unless $program eq "Program" |
| 124 | and $cc_time eq "CC_Time" |
| 125 | and $ex_time eq "Exec_Time"; |
| 126 | $header = $_; |
| 127 | next; |
| 128 | } |
| 129 | # Log each non-header line |
| 130 | $result->{$program} = { |
| 131 | 'cc_pass' => $cc_pass, |
| 132 | 'cc_time' => $cc_time, |
| 133 | 'cc_real' => $cc_real, |
| 134 | 'ex_pass' => $ex_pass, |
| 135 | 'ex_time' => $ex_time, |
| 136 | 'ex_real' => $ex_real, |
| 137 | }; |
| 138 | |
| 139 | # Max length prog name |
| 140 | my $len = length($program); |
| 141 | $max = $len if $len > $max; |
| 142 | } |
| 143 | close FH; |
| 144 | |
| 145 | $result->{'max'} = $max; |
| 146 | |
| 147 | return $header; |
| 148 | } |
| 149 | |
| 150 | # returns "pass" AND "pass" |
| 151 | sub pass_diff($$) { |
| 152 | my ($baseline, $target) = @_; |
| 153 | if (defined $baseline and defined $target and |
| 154 | $baseline eq "pass" and $target eq "pass") { |
| 155 | return "pass"; |
| 156 | } elsif (!$baseline and $target) { |
| 157 | return $target; |
| 158 | } elsif ($baseline and !$target) { |
| 159 | return $baseline; |
| 160 | } else { |
| 161 | return "fail"; |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | # collate multiple reports into one, by getting the |
| 166 | # average of each result into each field |
| 167 | sub collate_results($$) { |
| 168 | my ($results, $result) = @_; |
| 169 | |
| 170 | foreach my $sample (@$results) { |
| 171 | foreach my $prog (keys %$sample) { |
| 172 | next unless ref $sample->{$prog} eq "HASH"; |
| 173 | |
| 174 | # Set up statistics |
| 175 | if (!defined $result->{$prog}) { |
| 176 | $result->{$prog}->{'cc_time'} = new Statistics::Descriptive::Full(); |
| 177 | $result->{$prog}->{'cc_real'} = new Statistics::Descriptive::Full(); |
| 178 | $result->{$prog}->{'ex_time'} = new Statistics::Descriptive::Full(); |
| 179 | $result->{$prog}->{'ex_real'} = new Statistics::Descriptive::Full(); |
| 180 | } |
| 181 | # gather results |
| 182 | $result->{$prog}->{'cc_time'}->add_data($sample->{$prog}->{'cc_time'}); |
| 183 | $result->{$prog}->{'cc_real'}->add_data($sample->{$prog}->{'cc_real'}); |
| 184 | $result->{$prog}->{'ex_time'}->add_data($sample->{$prog}->{'ex_time'}); |
| 185 | $result->{$prog}->{'ex_real'}->add_data($sample->{$prog}->{'ex_real'}); |
| 186 | # collate pass status |
| 187 | $result->{$prog}->{'cc_pass'} = |
| 188 | &pass_diff($result->{$prog}->{'cc_pass'}, |
| 189 | $sample->{$prog}->{'cc_pass'}); |
| 190 | $result->{$prog}->{'ex_pass'} = |
| 191 | &pass_diff($result->{$prog}->{'ex_pass'}, |
| 192 | $sample->{$prog}->{'ex_pass'}); |
| 193 | } |
| 194 | # All max should be the same |
| 195 | $result->{'max'} = $sample->{'max'}; |
| 196 | } |
| 197 | # collate results |
| 198 | foreach my $prog (keys %$result) { |
| 199 | next unless ref $result->{$prog} eq "HASH"; |
| 200 | $result->{$prog}->{'cc_time'} = $result->{$prog}->{'cc_time'}->mean(); |
| 201 | $result->{$prog}->{'cc_real'} = $result->{$prog}->{'cc_real'}->mean(); |
| 202 | $result->{$prog}->{'ex_time'} = $result->{$prog}->{'ex_time'}->mean(); |
| 203 | $result->{$prog}->{'ex_real'} = $result->{$prog}->{'ex_real'}->mean(); |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | # compares two aggregated logs with absolute values |
| 208 | # and creates a final result with relative values |
| 209 | sub compare_results($$$) { |
| 210 | my ($baseline, $target, $result) = @_; |
| 211 | my $cc_geo = new Statistics::Descriptive::Full(); |
| 212 | my $ex_geo = new Statistics::Descriptive::Full(); |
| 213 | |
| 214 | $result->{'cc_pass'} = "pass"; |
| 215 | $result->{'ex_pass'} = "pass"; |
| 216 | foreach my $prog (keys %$baseline) { |
| 217 | next unless ref $baseline->{$prog} eq "HASH"; |
| 218 | die "Program '$prog' in baseline doesn't exist in target\n" |
| 219 | unless defined $target->{$prog}; |
| 220 | |
| 221 | # No zero on divisions |
| 222 | my ($b, $t) = ($baseline->{$prog}, $target->{$prog}); |
| 223 | foreach my $k (keys %$b) { |
| 224 | $b->{$k} = 0.001 if looks_like_number($b->{$k}) |
| 225 | and $b->{$k} == 0.0; |
| 226 | } |
| 227 | foreach my $k (keys %$t) { |
| 228 | $t->{$k} = 0.001 if looks_like_number($t->{$k}) |
| 229 | and $t->{$k} == 0.0; |
| 230 | } |
| 231 | |
| 232 | # Proportional difference |
| 233 | $result->{$prog} = { |
| 234 | 'cc_pass' => &pass_diff($b->{'cc_pass'}, $t->{'cc_pass'}), |
| 235 | 'cc_time' => ($t->{'cc_time'} / $b->{'cc_time'}) * 100, |
| 236 | 'cc_real' => ($t->{'cc_real'} / $b->{'cc_real'}) * 100, |
| 237 | 'ex_pass' => &pass_diff($b->{'ex_pass'}, $t->{'ex_pass'}), |
| 238 | 'ex_time' => ($t->{'ex_time'} / $b->{'ex_time'}) * 100, |
| 239 | 'ex_real' => ($t->{'ex_real'} / $b->{'ex_real'}) * 100, |
| 240 | }; |
| 241 | |
| 242 | # Add data to statistical model (ignore real) |
| 243 | $cc_geo->add_data($result->{$prog}->{'cc_time'}); |
| 244 | $ex_geo->add_data($result->{$prog}->{'ex_time'}); |
| 245 | |
| 246 | # Update global "pass" status |
| 247 | $result->{'cc_pass'} = &pass_diff($result->{'cc_pass'}, |
| 248 | $result->{$prog}->{'cc_pass'}); |
| 249 | $result->{'ex_pass'} = &pass_diff($result->{'ex_pass'}, |
| 250 | $result->{$prog}->{'ex_pass'}); |
| 251 | } |
| 252 | |
| 253 | # Get geomean of all differences / max length of prog name |
| 254 | $result->{'cc_geo'} = $cc_geo->geometric_mean(); |
| 255 | $result->{'ex_geo'} = $ex_geo->geometric_mean(); |
| 256 | $result->{'max'} = $baseline->{'max'}; |
| 257 | } |
| 258 | |
| 259 | # dumps the file in the same format as read |
| 260 | # can dump the header or geomean, if requested |
| 261 | sub dump($$) { |
| 262 | my ($result, $header, $geo) = @_; |
| 263 | my $max = $result->{'max'}; |
| 264 | |
| 265 | # If we have the header, print it |
| 266 | if ($header) { |
| 267 | print $header."\n"; |
| 268 | } |
| 269 | |
| 270 | foreach my $prog (sort keys %$result) { |
| 271 | next unless ref $result->{$prog} eq "HASH"; |
| 272 | |
| 273 | # Dump the program name, with spaces at the end |
| 274 | my $p = $result->{$prog}; |
| 275 | my $spaces = $max - length($prog); |
| 276 | print $prog; |
| 277 | print ' ' x $spaces; |
| 278 | print "\t|\t"; |
| 279 | |
| 280 | # Compile time |
| 281 | print "\t".$p->{'cc_pass'}; |
| 282 | printf("\t%0.4f", $p->{'cc_time'}); |
| 283 | printf("\t%0.4f", $p->{'cc_real'}); |
| 284 | |
| 285 | # Execution time |
| 286 | printf "\t".$p->{'ex_pass'}; |
| 287 | printf("\t%0.4f", $p->{'ex_time'}); |
| 288 | printf("\t%0.4f", $p->{'ex_real'}); |
| 289 | |
| 290 | print "\n"; |
| 291 | } |
| 292 | |
| 293 | # Print the final geomean line |
| 294 | if ($geo) { |
| 295 | print "GEOMEAN"; |
| 296 | print ' ' x ($max - 7); |
| 297 | print "\t|\t"; |
| 298 | print "\t".$result->{'cc_pass'}; |
| 299 | printf("\t%0.2f\t", $result->{'cc_geo'}); |
| 300 | print "\t".$result->{'ex_pass'}; |
| 301 | printf("\t%0.2f", $result->{'ex_geo'}); |
| 302 | print "\n"; |
| 303 | } |
| 304 | } |