File Coverage

blib/lib/BenchmarkAnything/Evaluations.pm
Criterion Covered Total %
statement 90 111 81.0
branch 17 48 35.4
condition 6 11 54.5
subroutine 8 8 100.0
pod 2 2 100.0
total 123 180 68.3


line stmt bran cond sub pod time code
1 1     1   365 use 5.010; # Perl 5.10+ needed for PDL/PDLA
  1         2  
2 1     1   3 use strict;
  1         1  
  1         15  
3 1     1   3 use warnings;
  1         1  
  1         45  
4             package BenchmarkAnything::Evaluations;
5             # git description: v0.002-3-g453b389
6              
7             our $AUTHORITY = 'cpan:SCHWIGON';
8             # ABSTRACT: Evaluation support for BenchmarkAnything data
9             $BenchmarkAnything::Evaluations::VERSION = '0.003';
10 1     1   658 use PDLA::Core;
  1         39517  
  1         9  
11 1     1   765 use PDLA::Stats;
  1         170  
  1         7  
12 1     1   176078 use PDLA::Ufunc;
  1         2  
  1         6  
13              
14              
15             sub multi_point_stats
16             {
17 12     12 1 13 my ($values) = @_;
18              
19 12         37 my $data = pdl(@$values);
20 12         5916 my $avg = average($data);
21             return {
22 12         33 avg => sclr($avg),
23             stdv => stdv($data),
24             min => min($data),
25             max => max($data),
26             ci_95_lower => $avg - 1.96 * se($data),
27             ci_95_upper => $avg + 1.96 * se($data),
28             };
29             }
30              
31              
32             sub transform_chartlines
33             {
34 1     1 1 291 my ($chartlines, $options) = @_;
35              
36 1         3 my $x_key = $options->{x_key};
37 1         1 my $x_type = $options->{x_type};
38 1         2 my $y_key = $options->{y_key};
39 1         2 my $y_type = $options->{y_type};
40 1         2 my $aggregation = $options->{aggregation};
41 1         1 my $verbose = $options->{verbose};
42 1         1 my $debug = $options->{debug};
43 1         2 my $dropnull = $options->{dropnull};
44 1         1 my $rawnumbers = "";
45              
46             # from all chartlines collect values into buckets for the dimensions we need
47             #
48             # chartline = title
49             # x = perlconfig_version
50             # y = VALUE
51 1         1 my @titles;
52             my %VALUES;
53             CHARTLINE:
54 1         2 foreach my $chartline (@$chartlines)
55             {
56 3         3 my $title = $chartline->{title};
57 3         8 my $results = $chartline->{results};
58 3         5 my $NAME = $results->[0]{NAME};
59              
60             # skip typical empty results
61 3 50 33     18 if (not @$results or (@$results == 1 and not $results->[0]{NAME}))
      33        
62             {
63 0 0       0 print STDERR "benchmarkanything: transform_chartlines: ignore empty chartline '$title'\n" if $verbose;
64 0         0 next CHARTLINE;
65             }
66 3         4 push @titles, $title;
67              
68 3         13 my $rawline = sprintf("* %-20s - %-40s\n", $title, $NAME);
69 3 50       183 print STDERR $rawline if $verbose;
70 3         10 $rawnumbers.=$rawline;
71              
72 3 50       9 print STDERR " VALUE_IDs: ".join(",", map {$_->{VALUE_ID}} @$results)."\n" if $debug;
  0         0  
73              
74             POINT:
75 3         6 foreach my $point (@$results)
76             {
77 12         18 my $x = $point->{$x_key};
78 12         13 my $y = $point->{$y_key};
79 12 50       19 if (not defined $x)
80             {
81 0         0 require Data::Dumper;
82 0 0       0 print STDERR "benchmarkanything: transform_chartlines: chartline '$title': ignore data point (missing key '$x_key'): ".Data::Dumper::Dumper($results) if $verbose;
83 0         0 next POINT;
84             }
85 12         5 push @{$VALUES{$title}{$x}{values}}, $y; # maybe multiple for same X - average them later
  12         42  
86             }
87             }
88              
89             # statistical aggregations of multi points
90 1         4 foreach my $title (keys %VALUES)
91             {
92 3         157 foreach my $x (keys %{$VALUES{$title}})
  3         16  
93             {
94 12         47555 my $multi_point_values = $VALUES{$title}{$x}{values};
95 12         23 $VALUES{$title}{$x}{stats} = multi_point_stats($multi_point_values);
96             }
97             }
98              
99             # find out all available x-values from all chartlines
100 1         78 my %all_x;
101 1         3 foreach my $title (keys %VALUES)
102             {
103 3         27 foreach my $x (keys %{$VALUES{$title}})
  3         6  
104             {
105 12         14 $all_x{$x} = 1;
106             }
107             }
108 1         9 my @all_x = keys %all_x;
109             @all_x =
110 5         56 $x_type eq 'version' ? sort {version->parse($a) <=> version->parse($b)} @all_x
111 0         0 : $x_type eq 'numeric' ? sort {$a <=> $b} @all_x
112 0         0 : $x_type eq 'string' ? sort {$a cmp $b} @all_x
113 1 0       12 : $x_type eq 'date' ? sort { die "TODO: sort by date" ; $a cmp $b} @all_x
  0 0       0  
  0 0       0  
    50          
114             : @all_x;
115              
116             # drop complete chartlines if it has gaps on versions that the other chartlines provide values
117 1         4 my %clean_chartlines;
118 1 50       6 if ($dropnull) {
119 0         0 foreach my $title (keys %VALUES) {
120 0         0 my $ok = 1;
121 0         0 foreach my $x (@all_x) {
122 0 0       0 if (not @{$VALUES{$title}{$x}{values} || []}) {
  0 0       0  
123 0 0       0 print STDERR "skip: $title (missing values for $x)\n" if $verbose;
124 0         0 $ok = 0;
125             }
126             }
127 0 0       0 if ($ok) {
128 0         0 $clean_chartlines{$title} = 1;
129 0 0       0 print STDERR "okay: $title\n" if $verbose;
130             }
131             }
132             }
133              
134             # intermediate debug output
135 1         6 foreach my $title (keys %VALUES)
136             {
137 3         4 foreach my $x (keys %{$VALUES{$title}})
  3         10  
138             {
139 12   50     11 my $count = scalar @{$VALUES{$title}{$x}{values} || []} || 0;
140 12 50       21 next if not $count;
141 12         17 my $avg = $VALUES{$title}{$x}{stats}{avg};
142 12         14 my $stdv = $VALUES{$title}{$x}{stats}{stdv};
143 12         12 my $ci95l = $VALUES{$title}{$x}{stats}{ci_95_lower};
144 12         12 my $ci95u = $VALUES{$title}{$x}{stats}{ci_95_upper};
145 12         66 my $rawline = sprintf(" %-20s . %-7s . (ci95l..avg..ci95u) = (%2.2f .. %2.2f .. %2.2f) +- stdv %5.2f (%3d points)\n", $title, $x, $ci95l, $avg, $ci95u, $stdv, $count);
146 12         703 $rawnumbers .= $rawline;
147 12 50       578 print STDERR $rawline if $verbose;
148             }
149             }
150              
151             # result data structure, as needed per chart type
152 1         4 my @RESULTMATRIX;
153              
154 1 50       3 @titles = grep { !$dropnull or $clean_chartlines{$_} } @titles; # dropnull
  3         11  
155              
156 1         4 for (my $i=0; $i<@all_x; $i++) # rows
157             {
158 4         6 my $x = $all_x[$i];
159 4         10 for (my $j=0; $j<@titles; $j++) # columns
160             {
161 12         12 my $title = $titles[$j];
162 12         18 my $value = $VALUES{$title}{$x}{stats}{$aggregation};
163             # stringify to unbless from PDL, then numify for type-aware JSON
164 12 50       82 $value = $value ? (0+sprintf("%6.2f", $value)) : undef;
165 12 100 100     37 $RESULTMATRIX[0] [0] = $x_key if $i == 0 && $j == 0;
166 12 100       19 $RESULTMATRIX[0] [$j+1] = $title if $i == 0;
167 12 100       26 $RESULTMATRIX[$i+1] [0] = $x if $j == 0;
168 12         45 $RESULTMATRIX[$i+1] [$j+1] = $value;
169             }
170             }
171              
172 1 50       4 if (wantarray) {
173 0         0 return (\@RESULTMATRIX, $rawnumbers);
174             } else {
175 1         37 return \@RESULTMATRIX;
176             }
177             }
178              
179             1;
180              
181             __END__
182              
183             =pod
184              
185             =encoding UTF-8
186              
187             =head1 NAME
188              
189             BenchmarkAnything::Evaluations - Evaluation support for BenchmarkAnything data
190              
191             =head2 multi_point_stats (\@values)
192              
193             For an array of values it gets basic statistical aggregations, like
194             average, standard deviation, and confidence interval.
195              
196             =head2 transform_chartlines ($chartlines, $options)
197              
198             Gets an array of query results, each one from a different query
199             against the backend store, and returns a matrix for rendering those
200             chartlines, currently suited for the google charts api.
201              
202             Multiple results for the same data X-axis are aggregated (default:
203             avg).
204              
205             =over 4
206              
207             =item INPUT:
208              
209             [ title: "dpath-T-n64",
210             results: [
211             {N:dpath, V:1000, version:2.0.13},
212             {N:dpath, V:1170, version:2.0.14},
213             {N:dpath, V:660, version:2.0.15},
214             {N:dpath, V:1030, version:2.0.16}
215             ]
216             ],
217             [ title: "Mem-nT-n64",
218             results: [
219             {N:Mem, V:400, version:2.0.13},
220             {N:Mem, V:460, version:2.0.14},
221             {N:Mem, V:1120, version:2.0.15},
222             {N:Mem, V:540, version:2.0.16}
223             ]
224             ],
225             [ title: "Fib-T-64",
226             results: [
227             {N:Fib, V:100, version:2.0.13},
228             {N:Fib, V:100, version:2.0.14},
229             {N:Fib, V:100, version:2.0.15},
230             {N:Fib, V:200, version:2.0.16}
231             ]
232             ]
233              
234             =item OUTPUT:
235              
236             # multiple results for same version would become aggregated (avg), not show here
237             ['version', 'dpath', 'Mem', 'Fib'],
238             ['2.0.13', 1000, 400, 100],
239             ['2.0.14', 1170, 460, 100],
240             ['2.0.15', 660, 1120, 100],
241             ['2.0.16', 1030, 540, 200]
242              
243             =back
244              
245             There are assumptions for the transformation:
246              
247             =over 4
248              
249             =item * there is only one NAME per chartline resultset
250              
251             =item * titles are unique
252              
253             =back
254              
255             =head1 AUTHOR
256              
257             Steffen Schwigon <ss5@renormalist.net>
258              
259             =head1 COPYRIGHT AND LICENSE
260              
261             This software is copyright (c) 2016 by Steffen Schwigon.
262              
263             This is free software; you can redistribute it and/or modify it under
264             the same terms as the Perl 5 programming language system itself.
265              
266             =cut