File Coverage

blib/lib/Benchmark/CSV.pm
Criterion Covered Total %
statement 100 113 88.5
branch 34 54 62.9
condition 3 5 60.0
subroutine 20 20 100.0
pod 5 8 62.5
total 162 200 81.0


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