| 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__ |