File Coverage

lib/Benchmark/CSV.pm
Criterion Covered Total %
statement 104 117 88.8
branch 34 54 62.9
condition 3 5 60.0
subroutine 21 21 100.0
pod 5 8 62.5
total 167 205 81.4


line stmt bran cond sub pod time code
1 5     5   130263 use 5.008; # utf8
  5         21  
  5         200  
2 5     5   34 use strict;
  5         11  
  5         183  
3 5     5   39 use warnings;
  5         11  
  5         167  
4 5     5   5771 use utf8;
  5         55  
  5         28  
5              
6             package Benchmark::CSV;
7              
8             our $VERSION = '0.001001';
9              
10 5     5   6820 use Path::Tiny;
  5         97366  
  5         433  
11 5     5   52 use Carp qw( croak carp );
  5         11  
  5         355  
12 5     5   6085 use Time::HiRes qw( gettimeofday tv_interval );
  5         10366  
  5         109  
13 5     5   6955 use IO::Handle;
  5         44229  
  5         559  
14 5     5   49 use List::Util qw( shuffle );
  5         17  
  5         15876  
15              
16             # ABSTRACT: Report raw timing results in CSV-style format for advanced processing.
17              
18             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
19              
20             sub new {
21 9     9 1 724263 my ( $self, @rest ) = @_;
22 9 100       54 return bless { ref $rest[0] ? %{ $rest[0] } : @rest }, $self;
  8         56  
23             }
24              
25             sub output_fh {
26 1008     1008 1 2570 my $nargs = ( my ( $self, $value ) = @_ );
27 1008 100       3690 if ( $nargs >= 2 ) {
28 2 100       196 croak 'Cant set output_fh after finalization' if $self->{finalized};
29 1         4 return ( $self->{output_fh} = $value );
30             }
31 1006 100       5358 return $self->{output_fh} if $self->{output_fh};
32 3 100       16 if ( not $self->{output} ) {
33 2         12 return ( $self->{output_fh} = \*STDOUT );
34             }
35 1         6 return ( $self->{output_fh} = Path::Tiny::path( $self->{output} )->openw );
36             }
37              
38             sub sample_size {
39 4     4 1 29 my $nargs = ( my ( $self, $value ) = @_ );
40 4 100       21 if ( $nargs >= 2 ) {
41 2 100       260 croak 'Cant set sample_size after finalization' if $self->{finalized};
42 1         7 return ( $self->{sample_size} = $value );
43             }
44 2 100       15 return $self->{sample_size} if defined $self->{sample_size};
45 1         6 return ( $self->{sample_size} = 1 );
46             }
47              
48              
49              
50              
51              
52             sub scale_values {
53 2     2 0 6 my $nargs = ( my ( $self, $value ) = @_ );
54 2 50       9 if ( $nargs >= 2 ) {
55 0 0       0 croak 'Cant set scale_values after finalization' if $self->{finalized};
56 0         0 return ( $self->{scale_values} = $value );
57             }
58 2 100       14 return $self->{scale_values} if exists $self->{scale_values};
59 1         6 return ( $self->{scale_values} = undef );
60             }
61              
62              
63              
64              
65              
66             sub per_second {
67 4     4 0 8 my $nargs = ( my ( $self, $value ) = @_ );
68 4 50       14 if ( $nargs >= 2 ) {
69 0 0       0 croak 'Cant set per_second after finalization' if $self->{finalized};
70 0         0 $self->{per_second_values} = $value;
71             }
72 4 100       29 return $self->{per_second} if exists $self->{per_second};
73 1         23 return ( $self->{per_second} = undef );
74             }
75              
76             sub add_instance {
77 2     2 1 35 my $nargs = ( my ( $self, $name, $method ) = @_ );
78 2 50       10 croak 'Too few arguments to ->add_instance( name => sub { })' if $nargs < 3;
79 2 50       19 croak 'Cant add instances after execution/finalization' if $self->{finalized};
80 2   100     16 $self->{instances} ||= {};
81 2 50       9 croak "Cant add instance $name more than once" if exists $self->{instances}->{$name};
82 2         8 $self->{instances}->{$name} = $method;
83 2         6 return;
84             }
85              
86             # These are hard to use as a default due to linux things.
87             my $hires_gettime_methods = {
88             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars);
89              
90             'hires_cputime_process' => {
91              
92             # bits/time.h
93             # CLOCK_PROCESS_CPUTIME_ID = 2
94             start => q[my $start = Time::HiRes::clock_gettime(2)],
95             stop => q[my $stop = Time::HiRes::clock_gettime(2)],
96             diff => q[ ( $stop - $start )],
97             },
98             'hires_cputime_thread' => {
99              
100             # bits/time.h
101             # CLOCK_THREAD_CPUTIME_ID = 3
102             start => q[my $start = Time::HiRes::clock_gettime(3)],
103             stop => q[my $stop = Time::HiRes::clock_gettime(3)],
104             diff => q[ ( $stop - $start )],
105             },
106             };
107             my $timing_methods = {
108             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars);
109             'hires_wall' => {
110             start => q[my $start = [ gettimeofday ]],
111             stop => q[my $stop = [ gettimeofday ]],
112             diff => q[tv_interval( $start, [ gettimeofday ])],
113             },
114              
115             # These are all bad because they're very imprecise :(
116             'times' => {
117             start => q[my (@start) = times],
118             stop => q[my (@stop) = times],
119             diff => q[ ( $stop[0]+$stop[1] ) - ( $start[0]+$start[1] ) ],
120             },
121             'times_user' => {
122             start => q[my (@start) = times],
123             stop => q[my (@stop) = times],
124             diff => q[ ( $stop[0] - $start[0] ) ],
125             },
126             'times_system' => {
127             start => q[my (@start) = times],
128             stop => q[my (@stop) = times],
129             diff => q[ ( $stop[1] - $start[1] ) ],
130             },
131             };
132             if ( Time::HiRes->can('clock_gettime') ) {
133             $timing_methods = { %{$timing_methods}, %{$hires_gettime_methods} };
134             }
135              
136              
137              
138              
139              
140             sub timing_method {
141 6     6 0 12 my $nargs = ( my ( $self, $method ) = @_ );
142 6 50       19 if ( $nargs >= 2 ) {
143 0 0       0 croak 'Cant add instances after execution/finalization' if $self->{finalized};
144 0 0       0 if ( not exists $timing_methods->{$method} ) {
145 0         0 croak "No such timing method $method";
146             }
147 0         0 return ( $self->{timing_method} = $method );
148             }
149 6 100       37 return $self->{timing_method} if $self->{timing_method};
150 1         8 return ( $self->{timing_method} = 'hires_wall' );
151             }
152              
153             sub _timing_method {
154 6     6   11 my ($self) = @_;
155 6         16 return $timing_methods->{ $self->timing_method };
156             }
157              
158             sub _compile_timer {
159             ## no critic (Variables::ProhibitUnusedVarsStricter)
160 2     2   6 my ( $self, $name, $code, $sample_size ) = @_;
161             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars);
162 2         5 my $run_one = q[ $code->(); ];
163 2         42 my $run_batch = join qq[\n], map { $run_one } 1 .. $sample_size;
  200         359  
164 2         19 my ( $starter, $stopper, $diff ) = map { $self->_timing_method->{$_} } qw( start stop diff );
  6         19  
165 2         6 my $sub;
166 2 50 33     7 if ( $self->per_second and $self->scale_values ) {
    50          
    50          
167 0         0 $diff = "( ( $diff > 0 ) ? (( 1 / $diff ) * $sample_size ) : 0 )";
168             }
169             elsif ( $self->per_second ) {
170 0         0 $diff = "( ( $diff > 0 ) ? ( 1 / $diff ) : 0 )";
171             }
172             elsif ( $self->scale_values ) {
173 0         0 $diff = "( $diff / $sample_size )";
174             }
175              
176 2         24 my $build_sub = <<"EOF";
177             \$sub = sub {
178             $starter;
179             $run_batch;
180             $stopper;
181             return ( \$name, sprintf '%f', ( $diff ));
182             };
183             1
184             EOF
185 2         6 local $@ = undef;
186             ## no critic (BuiltinFunctions::ProhibitStringyEval, Lax::ProhibitStringyEval::ExceptForRequire)
187 2 50       1193 if ( not eval $build_sub ) {
188 0         0 carp $build_sub;
189 0         0 croak $@;
190             }
191 2         15 return $sub;
192             }
193              
194             sub _write_header {
195 1     1   3 my ($self) = @_;
196 1 50       4 return if $self->{headers_written};
197 1         5 $self->output_fh->printf( "%s\n", join q[,], sort keys %{ $self->{instances} } );
  1         325  
198 1         32 $self->{headers_written} = 1;
199 1         79 $self->{finalized} = 1;
200 1         5 return;
201             }
202              
203             sub _write_result {
204 1000     1000   25359 my ( $self, $result ) = @_;
205 1000         3361 $self->output_fh->printf( "%s\n", join q[,], map { $result->{$_} } sort keys %{$result} );
  2000         9545  
  1000         6325  
206 1000         13670 return;
207             }
208              
209             sub run_iterations {
210 1     1 1 8 my $nargs = ( my ( $self, $count ) = @_ );
211 1 50       5 croak 'Arguments missing to ->run_iterations( num )' if $nargs < 2;
212 1         6 $self->_write_header;
213 1         5 my $sample_size = $self->sample_size;
214 1         3 my $timers = {};
215 1         3 for my $instance ( keys %{ $self->{instances} } ) {
  1         6  
216 2         12 $timers->{$instance} = $self->_compile_timer( $instance, $self->{instances}->{$instance}, $sample_size );
217             }
218 1         4 my @timer_names = keys %{$timers};
  1         6  
219 1         9 for ( 1 .. ( $count / $sample_size ) ) {
220 1000         5022 $self->_write_result( +{ map { $timers->{$_}->() } shuffle @timer_names } );
  2000         79703  
221             }
222 1         4 $self->output_fh->flush;
223 1         5 return;
224             }
225              
226             1;
227              
228             __END__