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   416673 use warnings;
  9         30  
  9         343  
4 9     9   56 use strict;
  9         20  
  9         337  
5              
6 9     9   13376 use Benchmark;
  9         177179  
  9         179  
7              
8 9     9   31774 use File::Temp;
  9         549532  
  9         1775  
9 9     9   160 use File::Path qw(mkpath rmtree);
  9         23  
  9         591  
10 9     9   57 use File::Spec;
  9         23  
  9         187  
11 9     9   11023 use IO::File;
  9         10912  
  9         26136  
12 9     9   93 use Scalar::Util;
  9         22  
  9         553  
13              
14 9         76 use Module::Pluggable ( search_path => 'Template::Benchmark::Engines',
15 9     9   10064 sub_name => 'engine_plugins' );
  9         160768  
16              
17             our $VERSION = '1.09';
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 70938 my $this = shift;
162 17         60 my ( $self, $class, $options, $var_hash1, $var_hash2, %temp_options,
163             %keep_cache_types );
164              
165 17         55 $self = {};
166 17   33     206 $class = ref( $this ) || $this;
167 17         50 bless $self, $class;
168              
169 17         76 $self->{ options } = {};
170 17         43 $options = $self->{ options };
171 17         100 while( my $opt = shift )
172             {
173 31 100 66     1447 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         68 my $val = shift();
177 30   50     192 $options->{ $opt } ||= {};
178 30 100       87 if( ref( $val ) )
179             {
180 17 50       60 $val = [ grep { $val->{ $_ } } keys( %{$val} ) ]
  0         0  
  0         0  
181             if ref( $val ) eq 'HASH';
182 17         32 foreach ( @{$val} )
  17         52  
183             {
184 38         179 $options->{ $opt }->{ $_ } = 1;
185             }
186             }
187             else
188             {
189 13         87 $options->{ $opt }->{ $val } = 1;
190             }
191             }
192             else
193             {
194             # TODO: should be croak
195 1 50       23 die "Unknown constructor option '$opt'"
196             unless exists $option_defaults{ $opt };
197 0         0 $self->{ options }->{ $opt } = shift();
198             }
199             }
200 16         215 foreach my $opt ( keys( %option_defaults ) )
201             {
202 640 100       2017 $options->{ $opt } = $option_defaults{ $opt }
203             unless defined $options->{ $opt };
204             }
205              
206             delete $options->{ only_plugin }
207 16 50       73 unless scalar( keys( %{$options->{ only_plugin }} ) );
  16         85  
208             delete $options->{ skip_plugin }
209 16 50       29 unless scalar( keys( %{$options->{ skip_plugin }} ) );
  16         89  
210             delete $options->{ features_from }
211 16 100       28 unless scalar( keys( %{$options->{ features_from }} ) );
  16         328  
212             delete $options->{ cache_types_from }
213 16 100       32 unless scalar( keys( %{$options->{ cache_types_from }} ) );
  16         71  
214              
215 16 50       54 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       366 unless defined( $datasets{ $options->{ dataset } } );
233 16         61 $var_hash1 = $datasets{ $options->{ dataset } }->{ hash1 };
234 16         81 $var_hash2 = $datasets{ $options->{ dataset } }->{ hash2 };
235             }
236              
237 16 100       52 if( $options->{ features_from } )
238             {
239 7         63 $self->{ features } = [ @valid_features ];
240 7         46 foreach my $plugin ( $self->engine_plugins() )
241             {
242 224         107486 my $leaf = _engine_leaf( $plugin );
243              
244 224 100       767 next unless $options->{ features_from }->{ $leaf };
245              
246 12     1   1168 eval "use $plugin";
  1     1   12  
  1     2   3  
  1     2   53  
  1     1   4  
  1     2   2  
  1     1   12  
  2     2   22  
  2     1   6  
  2     2   45  
  2     2   12  
  2     2   5  
  2         628  
  1         11  
  1         1  
  1         17  
  2         16  
  2         4  
  2         1184  
  1         14  
  1         2  
  1         20  
  2         14  
  2         4  
  2         29  
  1         12  
  1         3  
  1         19  
  2         22  
  2         5  
  2         36  
  2         14  
  2         5  
  2         28  
  2         13  
  2         5  
  2         28  
247 12 50       43 next if $@;
248              
249 177         1314 $self->{ features } = [ grep
250             {
251             defined( $plugin->feature_syntax( $_ ) )
252 12         18 } @{$self->{ features }} ];
  12         83  
253             }
254             }
255             else
256             {
257             $self->{ features } =
258             [
259 9         28 grep { $options->{ $_ } } @valid_features
  216         426  
260             ];
261             }
262             # TODO: sanity-check some features are left.
263              
264 16 100       91 if( $options->{ cache_types_from } )
265             {
266 7         37 $self->{ cache_types } = [ @valid_cache_types ];
267 7         22 %keep_cache_types = ();
268             }
269             else
270             {
271             $self->{ cache_types } =
272 9         21 [ grep { $options->{ $_ } } @valid_cache_types ];
  54         128  
273             }
274             # TODO: sanity-check some cache_types are left.
275              
276 16         45 $self->{ engines } = [];
277 16         51 $self->{ engine_errors } = {};
278 16         101 foreach my $plugin ( $self->engine_plugins() )
279             {
280 512         486027 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 512 100 66     6030 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 488 50       1093 if( $options->{ only_plugin } )
290             {
291 488 100       1608 next unless $options->{ only_plugin }->{ $leaf };
292             }
293 3 50       16 if( $options->{ skip_plugin } )
294             {
295 0 0       0 next if $options->{ skip_plugin }->{ $leaf };
296             }
297             }
298 27     4   3224 eval "use $plugin";
  4     4   42  
  4     4   9  
  4     1   98  
  4     2   45  
  4     1   7  
  4     2   141  
  4     1   37  
  4     1   12  
  4         99  
  1         6  
  1         2  
  1         12  
  2         26  
  2         6  
  2         45  
  1         6  
  1         2  
  1         12  
  2         24  
  2         3  
  2         51  
  1         6  
  1         2  
  1         16  
  1         6  
  1         3  
  1         13  
299 27 50       106 if( $@ )
300             {
301 0         0 $self->engine_error( $leaf, "Engine plugin load failure: $@" );
302             }
303             else
304             {
305 27         45 push @{$self->{ engines }}, $plugin;
  27         255  
306             }
307             }
308              
309             %temp_options = (
310 16         156 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       224 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         21034 $self->{ file_temp }->dirname(),
320             'templates',
321             );
322             $self->{ cache_dir } = File::Spec->catfile(
323 16         363 $self->{ file_temp }->dirname(),
324             'caches',
325             );
326             # TODO: failure check.
327             mkpath( $self->{ template_dir } )
328 16 50       4519 or die "Unable to make template dir '$self->{ template_dir }': $!";
329             mkpath( $self->{ cache_dir } )
330 16 50       3891 or die "Unable to make cache dir '$self->{ cache_dir }': $!";
331              
332             $self->{ feature_repeats } =
333             {
334 81 50 66     3464 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         39 } @{$self->{ features }}
  16         73  
348             };
349              
350 16         78 $self->{ templates } = {};
351 16         49 $self->{ benchmark_functions } = {};
352 16         44 $self->{ descriptions } = {};
353 16         47 $self->{ engine_for_tag } = {};
354 16         36 ENGINE: foreach my $engine ( @{$self->{ engines }} )
  16         57  
355             {
356 27         53 my ( %benchmark_functions, $template_dir, $cache_dir, $template,
357             $template_filename, $fh, $descriptions, $missing_syntaxes, $leaf );
358              
359 27         87 $leaf = _engine_leaf( $engine );
360              
361             $template_dir =
362 27         370 File::Spec->catfile( $self->{ template_dir }, $leaf );
363             $cache_dir =
364 27         258 File::Spec->catfile( $self->{ cache_dir }, $leaf );
365             # TODO: failure check
366 27         11291 mkpath( $template_dir );
367 27         5962 mkpath( $cache_dir );
368              
369 27         60 foreach my $cache_type ( @{$self->{ cache_types }} )
  27         109  
370             {
371 162         197 my ( $method, @method_args, $functions );
372              
373 162         275 $method = "benchmark_functions_for_${cache_type}";
374              
375 162 100       3697 next unless $engine->can( $method );
376              
377 39         99 @method_args = ();
378 39 100       176 push @method_args, $template_dir
379             unless $cache_type eq 'uncached_string';
380 39 100       170 push @method_args, $cache_dir
381             unless $cache_type =~ /^uncached/o;
382              
383 39         79 eval { $functions = $engine->$method( @method_args ); };
  39         246  
384 39 50       500 if( $@ )
385             {
386 0         0 $self->engine_error( $leaf,
387             "Error calling ${method}(): $@" );
388 0         0 next;
389             }
390              
391 39 50 50     158 next unless $functions and scalar( keys( %{$functions} ) );
  39         153  
392 39         178 $benchmark_functions{ $cache_type } = $functions;
393             }
394              
395 35         142 %keep_cache_types = map { $_ => 1 }
  72         162  
396             keys( %keep_cache_types ),
397             grep { $benchmark_functions{ $_ } } @valid_cache_types
398             if $options->{ cache_types_from } and
399 27 100 66     235 $options->{ cache_types_from }->{ $leaf };
400              
401 27 50       192 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         391 $template = '';
412 27         68 $missing_syntaxes = '';
413 27         41 foreach my $feature ( @{$self->{ features }} )
  27         88  
414             {
415 132         165 my ( $feature_syntax );
416              
417 132         784 $feature_syntax = $engine->feature_syntax( $feature );
418 132 50       5276 if( defined( $feature_syntax ) )
419             {
420             $template .= ( $feature_syntax . "\n" ) x
421 132   100     2989 ( $self->{ feature_repeats }->{ $feature } || 1 );
422             }
423             else
424             {
425 0         0 $missing_syntaxes .= ' ' . $feature;
426             }
427             }
428              
429 27 50       95 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         196 $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       1442 $template = $engine->preprocess_template( $template )
440             if $engine->can( 'preprocess_template' );
441              
442 27         375 $template_filename =
443             File::Spec->catfile( $template_dir, $leaf . '.txt' );
444 27         301 $fh = IO::File->new( "> $template_filename" );
445 27 50       4844 unless( $fh )
446             {
447 0         0 $self->engine_error( $leaf,
448             "Unable to write $template_filename: $!" );
449 0         0 next ENGINE;
450             }
451 27         155 $fh->print( $template );
452 27         574 $fh->close();
453              
454 27         1767 $template_filename = $leaf . '.txt';
455              
456 27         221 $descriptions = $engine->benchmark_descriptions();
457              
458 27         236 foreach my $type ( keys( %benchmark_functions ) )
459             {
460 39   100     250 $self->{ benchmark_functions }->{ $type } ||= {};
461              
462 39         58 foreach my $tag ( keys( %{$benchmark_functions{ $type }} ) )
  39         125  
463             {
464 39         58 my ( $function );
465              
466 39         77 $function = $benchmark_functions{ $type }->{ $tag };
467 39 100       211 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         183 };
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         1276 };
484             }
485             # TODO: warn on duplicates.
486 39         135 $self->{ descriptions }->{ $tag } = $descriptions->{ $tag };
487 39         6739 $self->{ engine_for_tag }->{ $tag } = $leaf;
488             }
489             }
490             }
491              
492 16 100       71 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         72 map { $_ => $self->{ benchmark_functions }->{ $_ } }
  18         44  
498             grep { $keep_cache_types{ $_ } }
499 7         17 keys( %{$self->{ benchmark_functions }} )
  7         35  
500             };
501             }
502              
503             # Strip any cache types that ended up with no functions.
504             $self->{ cache_types } = [
505 96         216 grep { $self->{ benchmark_functions }->{ $_ } }
506 16         46 @{$self->{ cache_types }}
  16         50  
507             ];
508              
509 16         122 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   180667 my ( $self ) = @_;
664              
665             # Use a DESTROY to clean up, so that we occur in case of errors.
666 17 50       87 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         660 delete $self->{ benchmark_functions };
678             }
679             }
680              
681 1     1 1 387 sub default_options { return( %option_defaults ); }
682 1     1 1 617 sub valid_cache_types { return( @valid_cache_types ); }
683 1     1 1 6 sub valid_features { return( @valid_features ); }
684              
685             sub engines
686             {
687 2     2 1 1514 my ( $self ) = @_;
688 2         4 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 764     764   998 my ( $engine ) = @_;
745              
746 764         5958 $engine =~ /\:\:([^\:]*)$/;
747 764   33     3649 return( $1 || $engine );
748             }
749              
750             1;
751              
752             __END__