File Coverage

blib/lib/Template/Benchmark.pm
Criterion Covered Total %
statement 241 351 68.6
branch 57 122 46.7
condition 26 46 56.5
subroutine 37 45 82.2
pod 11 11 100.0
total 372 575 64.7


line stmt bran cond sub pod time code
1             package Template::Benchmark;
2              
3 9     9   635968 use warnings;
  9         21  
  9         286  
4 9     9   54 use strict;
  9         21  
  9         292  
5              
6 9     9   9049 use Benchmark;
  9         3522405  
  9         71  
7              
8 9     9   19401 use File::Temp;
  9         957506  
  9         949  
9 9     9   87 use File::Path qw(mkpath rmtree);
  9         20  
  9         649  
10 9     9   54 use File::Spec;
  9         18  
  9         179  
11 9     9   8936 use IO::File;
  9         10286  
  9         1672  
12 9     9   72 use Scalar::Util;
  9         19  
  9         443  
13              
14 9         79 use Module::Pluggable ( search_path => 'Template::Benchmark::Engines',
15 9     9   11210 sub_name => 'engine_plugins' );
  9         4841633  
16              
17             our $VERSION = '1.09_01';
18              
19             my @valid_features = qw/
20             literal_text
21             scalar_variable
22             hash_variable_value
23             array_variable_value
24             deep_data_structure_value
25             array_loop_value
26             hash_loop_value
27             records_loop_value
28             array_loop_template
29             hash_loop_template
30             records_loop_template
31             constant_if_literal
32             variable_if_literal
33             constant_if_else_literal
34             variable_if_else_literal
35             constant_if_template
36             variable_if_template
37             constant_if_else_template
38             variable_if_else_template
39             constant_expression
40             variable_expression
41             complex_variable_expression
42             constant_function
43             variable_function
44             /;
45              
46             my @valid_cache_types = qw/
47             uncached_string
48             uncached_disk
49             disk_cache
50             shared_memory_cache
51             memory_cache
52             instance_reuse
53             /;
54              
55             my %option_defaults = (
56             # Feature options: these should only default on if they're
57             # widely supported, so that the default benchmark covers
58             # most template engines.
59             literal_text => 1,
60             scalar_variable => 1,
61             hash_variable_value => 0,
62             array_variable_value => 0,
63             deep_data_structure_value => 0,
64             array_loop_value => 0,
65             hash_loop_value => 0,
66             records_loop_value => 1,
67             array_loop_template => 0,
68             hash_loop_template => 0,
69             records_loop_template => 1,
70             constant_if_literal => 0,
71             variable_if_literal => 1,
72             constant_if_else_literal => 0,
73             variable_if_else_literal => 1,
74             constant_if_template => 0,
75             variable_if_template => 1,
76             constant_if_else_template => 0,
77             variable_if_else_template => 1,
78             constant_expression => 0,
79             variable_expression => 0,
80             complex_variable_expression => 0,
81             constant_function => 0,
82             variable_function => 0,
83              
84             # Cache types.
85             uncached_string => 1,
86             uncached_disk => 1,
87             disk_cache => 1,
88             shared_memory_cache => 1,
89             memory_cache => 1,
90             instance_reuse => 1,
91              
92             # Other options.
93             template_repeats => 30,
94             duration => 10,
95             dataset => 'original',
96             style => 'none',
97             keep_tmp_dirs => 0,
98             skip_output_compare => 0,
99              
100             # Plugin control.
101             only_plugin => {},
102             skip_plugin => {},
103             features_from => {},
104             cache_types_from => {},
105             );
106              
107             # Which engines to try first as the 'reference output' for templates.
108             # Note that this is merely a matter of author convenience: all template
109             # engine outputs must match, this merely determines which should be
110             # cited as 'correct' in the case of a mismatch. This should generally
111             # be a template engine that provides most features, otherwise it won't
112             # be an _available_ template engine when we need it.
113             # For author convenience I'm using Template::Sandbox as the prefered
114             # reference, however Template::Toolkit will make a better reference
115             # choice once this module has stabilized.
116             my $reference_preference = 'TS';
117              
118             my %datasets = (
119             original => {
120             hash1 => {
121             scalar_variable => 'I is a scalar, yarr!',
122             hash_variable => {
123             'hash_value_key' =>
124             'I spy with my little eye, something beginning with H.',
125             },
126             array_variable => [ qw/I have an imagination honest/ ],
127             this => { is => { a => { very => { deep => { hash => {
128             # No longer "it's", to avoid HTML-escaping inconsistencies
129             structure => "My god, it be full of hashes.",
130             } } } } } },
131             template_if_true => 'True dat',
132             template_if_false => 'Nay, Mister Wilks',
133             },
134             hash2 => {
135             array_loop => [ qw/five four three two one coming ready or not/ ],
136             hash_loop => {
137             aaa => 'first',
138             bbb => 'second',
139             ccc => 'third',
140             ddd => 'fourth',
141             eee => 'fifth',
142             },
143             records_loop => [
144             { name => 'Joe Bloggs', age => 16, },
145             { name => 'Fred Bloggs', age => 23, },
146             { name => 'Nigel Bloggs', age => 43, },
147             { name => 'Tarquin Bloggs', age => 143, },
148             { name => 'Geoffrey Bloggs', age => 13, },
149             ],
150             variable_if => 1,
151             variable_if_else => 0,
152             variable_expression_a => 20,
153             variable_expression_b => 10,
154             variable_function_arg => 'Hi there',
155             },
156             },
157             );
158              
159             sub new
160             {
161 17     17 1 4666108 my $this = shift;
162 17         35 my ( $self, $class, $options, $var_hash1, $var_hash2, %temp_options,
163             %keep_cache_types );
164              
165 17         42 $self = {};
166 17   33     114 $class = ref( $this ) || $this;
167 17         45 bless $self, $class;
168              
169 17         77 $self->{ options } = {};
170 17         36 $options = $self->{ options };
171 17         61 while( my $opt = shift )
172             {
173 31 100 66     253 if( $opt eq 'only_plugin' or $opt eq 'skip_plugin' or
      100        
      100        
174             $opt eq 'features_from' or $opt eq 'cache_types_from' )
175             {
176 30         42 my $val = shift();
177 30   50     148 $options->{ $opt } ||= {};
178 30 100       74 if( ref( $val ) )
179             {
180 17 50       94 $val = [ grep { $val->{ $_ } } keys( %{$val} ) ]
  0         0  
  0         0  
181             if ref( $val ) eq 'HASH';
182 17         26 foreach ( @{$val} )
  17         43  
183             {
184 38         154 $options->{ $opt }->{ $_ } = 1;
185             }
186             }
187             else
188             {
189 13         75 $options->{ $opt }->{ $val } = 1;
190             }
191             }
192             else
193             {
194             # TODO: should be croak
195 1 50       22 die "Unknown constructor option '$opt'"
196             unless exists $option_defaults{ $opt };
197 0         0 $self->{ options }->{ $opt } = shift();
198             }
199             }
200 16         162 foreach my $opt ( keys( %option_defaults ) )
201             {
202 640 100       1830 $options->{ $opt } = $option_defaults{ $opt }
203             unless defined $options->{ $opt };
204             }
205              
206             delete $options->{ only_plugin }
207 16 50       59 unless scalar( keys( %{$options->{ only_plugin }} ) );
  16         90  
208             delete $options->{ skip_plugin }
209 16 50       29 unless scalar( keys( %{$options->{ skip_plugin }} ) );
  16         73  
210             delete $options->{ features_from }
211 16 100       27 unless scalar( keys( %{$options->{ features_from }} ) );
  16         66  
212             delete $options->{ cache_types_from }
213 16 100       24 unless scalar( keys( %{$options->{ cache_types_from }} ) );
  16         58  
214              
215 16 50       123 if( ref( $options->{ dataset } ) )
216             {
217             # TODO: should be croaks really.
218             die "Option 'dataset' must be a dataset name or a hashref, got: " .
219             ref( $options->{ dataset } )
220 0 0       0 unless ref( $options->{ dataset } ) eq 'HASH';
221             die "Option 'dataset' hashref is missing required 'hash1' key"
222 0 0       0 unless defined( $options->{ dataset }->{ hash1 } );
223             die "Option 'dataset' hashref is missing required 'hash2' key"
224 0 0       0 unless defined( $options->{ dataset }->{ hash2 } );
225 0         0 $var_hash1 = $options->{ dataset }->{ hash1 };
226 0         0 $var_hash2 = $options->{ dataset }->{ hash2 };
227             }
228             else
229             {
230             # TODO: should be croaks really.
231             die "Unknown dataset name '$options->{ dataset }'"
232 16 50       125 unless defined( $datasets{ $options->{ dataset } } );
233 16         48 $var_hash1 = $datasets{ $options->{ dataset } }->{ hash1 };
234 16         55 $var_hash2 = $datasets{ $options->{ dataset } }->{ hash2 };
235             }
236              
237 16 100       45 if( $options->{ features_from } )
238             {
239 7         56 $self->{ features } = [ @valid_features ];
240 7         44 foreach my $plugin ( $self->engine_plugins() )
241             {
242 231         132031 my $leaf = _engine_leaf( $plugin );
243              
244 231 100       755 next unless $options->{ features_from }->{ $leaf };
245              
246 12     1   1486 eval "use $plugin";
  1     1   12  
  1     2   2  
  1     2   64  
  1     1   8  
  1     2   2  
  1     1   17  
  2     2   20  
  2     1   4  
  2     2   39  
  2     2   12  
  2     2   4  
  2         25  
  1         10  
  1         3  
  1         19  
  2         10  
  2         4  
  2         60  
  1         13  
  1         3  
  1         21  
  2         10  
  2         4  
  2         24  
  1         11  
  1         3  
  1         22  
  2         19  
  2         4  
  2         35  
  2         13  
  2         5  
  2         25  
  2         10  
  2         5  
  2         25  
247 12 50       44 next if $@;
248              
249 177         1396 $self->{ features } = [ grep
250             {
251             defined( $plugin->feature_syntax( $_ ) )
252 12         22 } @{$self->{ features }} ];
  12         90  
253             }
254             }
255             else
256             {
257             $self->{ features } =
258             [
259 9         34 grep { $options->{ $_ } } @valid_features
  216         372  
260             ];
261             }
262             # TODO: sanity-check some features are left.
263              
264 16 100       92 if( $options->{ cache_types_from } )
265             {
266 7         22 $self->{ cache_types } = [ @valid_cache_types ];
267 7         20 %keep_cache_types = ();
268             }
269             else
270             {
271             $self->{ cache_types } =
272 9         17 [ grep { $options->{ $_ } } @valid_cache_types ];
  54         128  
273             }
274             # TODO: sanity-check some cache_types are left.
275              
276 16         37 $self->{ engines } = [];
277 16         45 $self->{ engine_errors } = {};
278 16         92 foreach my $plugin ( $self->engine_plugins() )
279             {
280 528         517698 my $leaf = _engine_leaf( $plugin );
281              
282             # Force-require any features_from or cache_types_from plugin,
283             # regardless of their only_plugin or skip_plugin settings.
284 528 100 66     11218 if( ( not $options->{ features_from } or
      66        
      66        
285             not $options->{ features_from }->{ $leaf } ) and
286             ( not $options->{ cache_types_from } or
287             not $options->{ cache_types_from }->{ $leaf } ) )
288             {
289 504 50       1371 if( $options->{ only_plugin } )
290             {
291 504 100       1758 next unless $options->{ only_plugin }->{ $leaf };
292             }
293 3 50       15 if( $options->{ skip_plugin } )
294             {
295 0 0       0 next if $options->{ skip_plugin }->{ $leaf };
296             }
297             }
298 27     4   2696 eval "use $plugin";
  4     4   48  
  4     4   7  
  4     1   112  
  4     2   40  
  4     1   10  
  4     2   92  
  4     1   37  
  4     1   9  
  4         87  
  1         9  
  1         2  
  1         15  
  2         18  
  2         5  
  2         33  
  1         5  
  1         3  
  1         12  
  2         19  
  2         5  
  2         40  
  1         6  
  1         2  
  1         13  
  1         7  
  1         3  
  1         14  
299 27 50       83 if( $@ )
300             {
301 0         0 $self->engine_error( $leaf, "Engine plugin load failure: $@" );
302             }
303             else
304             {
305 27         40 push @{$self->{ engines }}, $plugin;
  27         241  
306             }
307             }
308              
309             %temp_options = (
310 16         163 TMPDIR => 1,
311             );
312 16 50       77 $temp_options{ CLEANUP } = 0 if $self->{ options }->{ keep_tmp_dirs };
313              
314             $self->{ file_temp } =
315 16 50       193 File::Temp->newdir( 'benchmark_XXXX', %temp_options )
316             or die "Unable to create File::Temp";
317              
318             $self->{ template_dir } = File::Spec->catfile(
319 16         111931 $self->{ file_temp }->dirname(),
320             'templates',
321             );
322             $self->{ cache_dir } = File::Spec->catfile(
323 16         322 $self->{ file_temp }->dirname(),
324             'caches',
325             );
326             # TODO: failure check.
327             mkpath( $self->{ template_dir } )
328 16 50       3454 or die "Unable to make template dir '$self->{ template_dir }': $!";
329             mkpath( $self->{ cache_dir } )
330 16 50       3435 or die "Unable to make cache dir '$self->{ cache_dir }': $!";
331              
332             $self->{ feature_repeats } =
333             {
334 81 50 66     1051 map
    100          
335             {
336             $_ =>
337             (
338             # Retain positive integer values.
339             ( Scalar::Util::looks_like_number( $options->{ $_ } ) &&
340             ( int( $options->{ $_ } ) == $options->{ $_ } ) &&
341             ( $options->{ $_ } > 0 ) ) ?
342             $options->{ $_ } :
343             # Normalize the rest to 1 or 0 based on true/false.
344             # TODO: probably should warn.
345             ( $options->{ $_ } ? 1 : 0 )
346             )
347 16         37 } @{$self->{ features }}
  16         57  
348             };
349              
350 16         52 $self->{ templates } = {};
351 16         41 $self->{ benchmark_functions } = {};
352 16         35 $self->{ descriptions } = {};
353 16         43 $self->{ engine_for_tag } = {};
354 16         32 ENGINE: foreach my $engine ( @{$self->{ engines }} )
  16         46  
355             {
356 27         39 my ( %benchmark_functions, $template_dir, $cache_dir, $template,
357             $template_filename, $fh, $descriptions, $missing_syntaxes, $leaf );
358              
359 27         68 $leaf = _engine_leaf( $engine );
360              
361             $template_dir =
362 27         333 File::Spec->catfile( $self->{ template_dir }, $leaf );
363             $cache_dir =
364 27         225 File::Spec->catfile( $self->{ cache_dir }, $leaf );
365             # TODO: failure check
366 27         4298 mkpath( $template_dir );
367 27         3804 mkpath( $cache_dir );
368              
369 27         66 foreach my $cache_type ( @{$self->{ cache_types }} )
  27         79  
370             {
371 162         171 my ( $method, @method_args, $functions );
372              
373 162         209 $method = "benchmark_functions_for_${cache_type}";
374              
375 162 100       1191 next unless $engine->can( $method );
376              
377 39         76 @method_args = ();
378 39 100       108 push @method_args, $template_dir
379             unless $cache_type eq 'uncached_string';
380 39 100       138 push @method_args, $cache_dir
381             unless $cache_type =~ /^uncached/o;
382              
383 39         51 eval { $functions = $engine->$method( @method_args ); };
  39         189  
384 39 50       546 if( $@ )
385             {
386 0         0 $self->engine_error( $leaf,
387             "Error calling ${method}(): $@" );
388 0         0 next;
389             }
390              
391 39 50 50     123 next unless $functions and scalar( keys( %{$functions} ) );
  39         123  
392 39         131 $benchmark_functions{ $cache_type } = $functions;
393             }
394              
395 35         115 %keep_cache_types = map { $_ => 1 }
  72         129  
396             keys( %keep_cache_types ),
397             grep { $benchmark_functions{ $_ } } @valid_cache_types
398             if $options->{ cache_types_from } and
399 27 100 66     185 $options->{ cache_types_from }->{ $leaf };
400              
401 27 50       146 unless( %benchmark_functions )
402             {
403 0         0 $self->engine_error( $leaf, 'No matching benchmark functions.' );
404 0         0 next ENGINE;
405             }
406              
407             #print "Looking at $leaf.\n";
408             #use Data::Dumper;
409             #print " features: " . Data::Dumper::Dumper( $self->{ features } ) . "\n";
410              
411 27         49 $template = '';
412 27         39 $missing_syntaxes = '';
413 27         39 foreach my $feature ( @{$self->{ features }} )
  27         76  
414             {
415 132         165 my ( $feature_syntax );
416              
417 132         369 $feature_syntax = $engine->feature_syntax( $feature );
418 132 50       758 if( defined( $feature_syntax ) )
419             {
420             $template .= ( $feature_syntax . "\n" ) x
421 132   100     602 ( $self->{ feature_repeats }->{ $feature } || 1 );
422             }
423             else
424             {
425 0         0 $missing_syntaxes .= ' ' . $feature;
426             }
427             }
428              
429 27 50       73 if( $missing_syntaxes )
430             {
431 0         0 $self->engine_error( $leaf,
432             "No syntaxes provided for:$missing_syntaxes." );
433 0         0 next ENGINE;
434             }
435              
436 27         1015334 $template = $template x $options->{ template_repeats };
437             # Allow the plugin a chance to rewrite the repeated sections,
438             # ie: some engines require unique loop names/labels.
439 27 50       221 $template = $engine->preprocess_template( $template )
440             if $engine->can( 'preprocess_template' );
441              
442 27         284 $template_filename =
443             File::Spec->catfile( $template_dir, $leaf . '.txt' );
444 27         298 $fh = IO::File->new( "> $template_filename" );
445 27 50       3918 unless( $fh )
446             {
447 0         0 $self->engine_error( $leaf,
448             "Unable to write $template_filename: $!" );
449 0         0 next ENGINE;
450             }
451 27         129 $fh->print( $template );
452 27         78618 $fh->close();
453              
454 27         1727 $template_filename = $leaf . '.txt';
455              
456 27         199 $descriptions = $engine->benchmark_descriptions();
457              
458 27         207 foreach my $type ( keys( %benchmark_functions ) )
459             {
460 39   100     204 $self->{ benchmark_functions }->{ $type } ||= {};
461              
462 39         60 foreach my $tag ( keys( %{$benchmark_functions{ $type }} ) )
  39         104  
463             {
464 39         45 my ( $function );
465              
466 39         70 $function = $benchmark_functions{ $type }->{ $tag };
467 39 100       171 if( $type =~ /_string$/ )
468             {
469             $self->{ benchmark_functions }->{ $type }->{ $tag } =
470             sub
471             {
472 0     0   0 $function->( $template,
473             $var_hash1, $var_hash2 );
474 23         249 };
475             }
476             else
477             {
478             $self->{ benchmark_functions }->{ $type }->{ $tag } =
479             sub
480             {
481 0     0   0 $function->( $template_filename,
482             $var_hash1, $var_hash2 );
483 16         95 };
484             }
485             # TODO: warn on duplicates.
486 39         213 $self->{ descriptions }->{ $tag } = $descriptions->{ $tag };
487 39         345 $self->{ engine_for_tag }->{ $tag } = $leaf;
488             }
489             }
490             }
491              
492 16 100       62 if( $options->{ cache_types_from } )
493             {
494             # We need to delete any benchmark functions that crept in
495             # before we figured out what cache types we needed.
496             $self->{ benchmark_functions } = {
497 18         58 map { $_ => $self->{ benchmark_functions }->{ $_ } }
  18         36  
498             grep { $keep_cache_types{ $_ } }
499 7         11 keys( %{$self->{ benchmark_functions }} )
  7         22  
500             };
501             }
502              
503             # Strip any cache types that ended up with no functions.
504             $self->{ cache_types } = [
505 96         203 grep { $self->{ benchmark_functions }->{ $_ } }
506 16         38 @{$self->{ cache_types }}
  16         45  
507             ];
508              
509 16         103 return( $self );
510             }
511              
512             sub benchmark
513             {
514 0     0 1 0 my ( $self ) = @_;
515 0         0 my ( $duration, $style, $result, $reference, @outputs, $errors );
516              
517 0         0 $duration = $self->{ options }->{ duration };
518 0         0 $style = $self->{ options }->{ style };
519 0         0 $errors = {};
520              
521             # First up, check each benchmark function produces the same
522             # output as all the others. This also serves to ensure that
523             # the caches become populated for those benchmarks that are
524             # cached.
525             # We run the benchmark function twice, and use the output
526             # of the second, this is to make sure we're using the output
527             # of the cached template, otherwise we could end up with a
528             # function that produces the right output when building the
529             # cache but then benchmarks insanely well because there's
530             # an error in running the cached version so it no-ops all
531             # the expensive work.
532 0         0 @outputs = ();
533 0         0 $reference = 0;
534 0         0 foreach my $type ( @{$self->{ cache_types }} )
  0         0  
535             {
536 0         0 foreach my $tag
537 0         0 ( keys( %{$self->{ benchmark_functions }->{ $type }} ) )
538             {
539 0         0 my ( $output );
540              
541             # First to cache.
542 0         0 eval { $self->{ benchmark_functions }->{ $type }->{ $tag }->(); };
  0         0  
543 0 0       0 if( $@ )
544             {
545             $self->engine_error(
546 0         0 $self->{ engine_for_tag }->{ $tag },
547             "Error running benchmark function for $tag: $@",
548             $errors );
549 0         0 delete $self->{ benchmark_functions }->{ $type }->{ $tag };
550 0         0 next;
551             }
552             # And second for output.
553 0         0 $output = eval {
554 0         0 $self->{ benchmark_functions }->{ $type }->{ $tag }->();
555             };
556 0 0       0 if( $@ )
557             {
558             $self->engine_error(
559 0         0 $self->{ engine_for_tag }->{ $tag },
560             "Error running benchmark function for $tag: $@",
561             $errors );
562 0         0 delete $self->{ benchmark_functions }->{ $type }->{ $tag };
563 0         0 next;
564             }
565 0 0 0     0 $output = ${$output} if $output and ref( $output );
  0         0  
566             # [rt #59247] Normalize newline endings, some template engines
567             # produce UNIX and some Windows line-endings when on Windows.
568 0 0       0 $output =~ s/\r//g if $output;
569 0         0 push @outputs, [ $type, $tag, $output ];
570 0 0       0 $reference = $#outputs if $tag eq $reference_preference;
571             }
572             # Prune if all our functions have errored and been pruned.
573             delete $self->{ benchmark_functions }->{ $type }
574 0 0       0 unless %{$self->{ benchmark_functions }->{ $type }};
  0         0  
575             }
576              
577             # Strip any cache types that ended up with no functions.
578             $self->{ cache_types } = [
579 0         0 grep { $self->{ benchmark_functions }->{ $_ } }
580 0         0 @{$self->{ cache_types }}
  0         0  
581             ];
582              
583 0 0       0 unless( @outputs )
584             {
585 0         0 $result =
586             {
587             result => 'NO BENCHMARKS TO RUN',
588             };
589 0 0       0 $result->{ errors } = $errors if %{$errors};
  0         0  
590 0         0 return( $result );
591             }
592              
593             #use Data::Dumper;
594             #print "Outputs: ", Data::Dumper::Dumper( \@outputs ), "\n";
595              
596             # TODO: this nasty hackery is surely telling me I need a
597             # Template::Benchmark::Result object.
598             $result = {
599             result => 'MISMATCHED TEMPLATE OUTPUT',
600             reference =>
601             {
602             type => $outputs[ $reference ]->[ 0 ],
603             tag => $outputs[ $reference ]->[ 1 ],
604             output => $outputs[ $reference ]->[ 2 ],
605             },
606 0         0 descriptions => { %{$self->{ descriptions }} },
  0         0  
607             failures => [],
608             };
609 0 0       0 $result->{ errors } = $errors if %{$errors};
  0         0  
610              
611 0 0       0 unless( $self->{ options }->{ skip_output_compare } )
612             {
613 0         0 foreach my $output ( @outputs )
614             {
615 0         0 push @{$result->{ failures }},
616             {
617             type => $output->[ 0 ],
618             tag => $output->[ 1 ],
619             output => defined( $output->[ 2 ] ) ?
620             $output->[ 2 ] : "[no content returned]\n",
621             }
622             if !defined( $output->[ 2 ] ) or
623 0 0 0     0 $output->[ 2 ] ne $result->{ reference }->{ output };
    0          
624             }
625              
626 0 0       0 return( $result ) unless $#{$result->{ failures }} == -1;
  0         0  
627             }
628              
629             # OK, all template output matched, time to do the benchmarks.
630              
631 0         0 delete $result->{ failures };
632 0         0 $result->{ result } = 'SUCCESS';
633              
634 0         0 $result->{ start_time } = time();
635             $result->{ title } = 'Template Benchmark @' .
636 0         0 localtime( $result->{ start_time } );
637              
638 0         0 $result->{ benchmarks } = [];
639 0 0       0 if( $duration )
640             {
641 0         0 foreach my $type ( @{$self->{ cache_types }} )
  0         0  
642             {
643 0         0 my ( $timings, $comparison );
644              
645             $timings = Benchmark::timethese( -$duration,
646 0         0 $self->{ benchmark_functions }->{ $type }, $style );
647 0         0 $comparison = Benchmark::cmpthese( $timings, $style );
648              
649 0         0 push @{$result->{ benchmarks }},
  0         0  
650             {
651             type => $type,
652             timings => $timings,
653             comparison => $comparison,
654             };
655             }
656             }
657              
658 0         0 return( $result );
659             }
660              
661             sub DESTROY
662             {
663 17     17   6015 my ( $self ) = @_;
664              
665             # Use a DESTROY to clean up, so that we occur in case of errors.
666 17 50       79 if( $self->{ options }->{ keep_tmp_dirs } )
667             {
668             print "Not removing cache dir ", $self->{ cache_dir }, "\n"
669 0 0       0 if $self->{ cache_dir };
670             print "Not removing template dir ", $self->{ template_dir }, "\n"
671 0 0       0 if $self->{ template_dir };
672             }
673             else
674             {
675             # Try to make our benchmark closures go out of scope so they
676             # release any locks before we try to delete the temp dirs...
677 17         592 delete $self->{ benchmark_functions };
678             }
679             }
680              
681 1     1 1 271 sub default_options { return( %option_defaults ); }
682 1     1 1 529 sub valid_cache_types { return( @valid_cache_types ); }
683 1     1 1 5 sub valid_features { return( @valid_features ); }
684              
685             sub engines
686             {
687 2     2 1 1400 my ( $self ) = @_;
688 2         5 return( @{$self->{ engines }} );
  2         21  
689             }
690              
691             sub features
692             {
693 0     0 1 0 my ( $self ) = @_;
694 0         0 return( @{$self->{ features }} );
  0         0  
695             }
696              
697             sub engine_errors
698             {
699 0     0 1 0 my ( $self ) = @_;
700 0         0 return( $self->{ engine_errors } );
701             }
702              
703             sub engine_error
704             {
705 0     0 1 0 my ( $self, $engine, $error, $errors ) = @_;
706 0         0 my ( $leaf );
707              
708 0 0       0 $errors = $self->{ engine_errors } unless $errors;
709 0         0 $leaf = _engine_leaf( $engine );
710              
711             # TODO: warn if an option asks us to?
712              
713 0   0     0 $errors->{ $leaf } ||= [];
714 0         0 push @{$errors->{ $leaf }}, $error;
  0         0  
715             }
716              
717             sub number_of_benchmarks
718             {
719 0     0 1 0 my ( $self ) = @_;
720 0         0 my ( $num_benchmarks );
721              
722 0         0 $num_benchmarks = 0;
723 0         0 foreach my $type ( @{$self->{ cache_types }} )
  0         0  
724             {
725             $num_benchmarks +=
726 0         0 scalar( keys( %{$self->{ benchmark_functions }->{ $type }} ) );
  0         0  
727             }
728              
729 0         0 return( $num_benchmarks );
730             }
731              
732             sub estimate_benchmark_duration
733             {
734 0     0 1 0 my ( $self ) = @_;
735 0         0 my ( $duration );
736              
737 0         0 $duration = $self->{ options }->{ duration };
738              
739 0         0 return( $duration * $self->number_of_benchmarks() );
740             }
741              
742             sub _engine_leaf
743             {
744 787     787   1016 my ( $engine ) = @_;
745              
746 787         3172 $engine =~ /\:\:([^\:]*)$/;
747 787   33     3327 return( $1 || $engine );
748             }
749              
750             1;
751              
752             __END__