blob: f0d513944e95edb6a27213d000784f061ef33c21 [file] [log] [blame]
Renato Golin94cc1042016-04-26 11:02:23 +01001#!/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
20use strict;
21use warnings;
22use Scalar::Util qw(looks_like_number);
23use Statistics::Descriptive;
24use Data::Dumper;
25
26my $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";
32my $action = $ARGV[0];
33die $syntax unless defined $action;
34
35###################################################
36
37if ($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}
46exit;
47
48###################################################
49
50# prepare a number of logs into a collated results by average
51sub 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
90sub 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
110sub 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"
151sub 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
167sub 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
209sub 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
261sub 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}