File Coverage

blib/lib/Devel/SimpleProfiler.pm
Criterion Covered Total %
statement 49 84 58.3
branch 3 4 75.0
condition 1 10 10.0
subroutine 8 12 66.6
pod 3 3 100.0
total 64 113 56.6


line stmt bran cond sub pod time code
1             package Devel::SimpleProfiler;
2              
3 1     1   38565 use strict;
  1         2  
  1         31  
4 1     1   5 use warnings;
  1         1  
  1         28  
5              
6 1     1   1312 use Aspect;
  1         177243  
  1         13  
7 1     1   11406 use Time::HiRes;
  1         2758  
  1         10  
8 1     1   357 use Data::Dumper;
  1         2  
  1         70  
9              
10 1     1   4533 use File::Temp qw/ tempfile /;
  1         23137  
  1         90  
11              
12 1     1   10 use vars qw($VERSION);
  1         2  
  1         1511  
13              
14             $VERSION = '1.0';
15              
16             our( @stack, %calltimes, %callers, %calls, $tmpFile, $re );
17              
18             =head1 NAME
19              
20             Devel::SimpleProfiler - quick and dirty perl code profiler
21              
22             =head1 SYNPOSIS
23              
24             use Devel::SimpleProfiler;
25              
26             Devel::SimpleProfiler::init( "/tmp/tmpfile", qr/RegexToMatchSubNames/ );
27             Devel::SimpleProfiler::start;
28              
29             ....
30              
31             if( ! fork ) {
32             # must restart for child process
33             Devel::SimpleProfiler::start;
34             }
35              
36             ....
37              
38             Devel::SimpleProfiler::analyze('total');
39             exit;
40              
41             # ---- PRINTS OUT (and sorts by total) -----
42             performance stats ( all times are in ms)
43              
44             sub | # calls | total t | mean t | avg t | max t | min t
45             -----------------+---------+---------+--------+-------+-------+------
46             main::test_suite | 1 | 2922 | 2922 | 2922 | 2922 | 2922
47             OtherThing::fun | 27 | 152 | 1 | 5 | 63 | 0
48             SomeObj::new | 3 | 26 | 8 | 8 | 8 | 8
49              
50             ....
51              
52             =head1 DESCRIPTION
53              
54             This is meant to be a simple way to get a performance benchmark for
55             perl subs. It uses the fantastic Aspect module written by
56             Adam Kennedy, Marcel Gruenauer and Ran Eilam to monkey patch select
57             perl subs and gather statistics about them.
58              
59             =head1 METHODS
60              
61             =head2 init
62              
63             init takes two arguments : a temp file to use and a regular expression
64             to find subs to measure. By default, the file is /tmp/foo and the
65             regex is qr/^main:/;
66            
67             init should be called once for a run.
68              
69             =cut
70             sub init {
71 0     0 1 0 ( $tmpFile, $re ) = @_;
72 0   0     0 $tmpFile ||= '/tmp/foo';
73 0   0     0 $re ||= qr/^main:/;
74 0         0 unlink $tmpFile;
75             }
76              
77             =head2 analyze
78              
79             analyze simply outputs the data collected from the profiler so far in
80             a table with the columns
81             * sub name
82             * total number of calls
83             * total time in ms
84             * mean time in ms
85             * average time in ms
86             * max time in ms
87             * min time in ms
88              
89             This can be called as many times as desired. It takes an optional
90             argument to sort by, which can be one of :
91             'calls', 'total', 'mean', 'avg', 'max', 'min'
92             The default sorting is by average.
93              
94             =cut
95             sub analyze {
96 0     0 1 0 my $sort = shift;
97 0         0 my( %funtimes, %funcalls, %funcalled );
98 0         0 open( IN, "<$tmpFile" );
99 0         0 while( ) {
100 0         0 chomp;
101 0         0 my( $fun, $time, $stack ) = split /\|/, $_;
102 0         0 push @{$funtimes{ $fun }}, $time;
  0         0  
103 0         0 my( @stack ) = split ',', $stack;
104 0         0 for my $call ( @stack ) {
105 0         0 $funcalls{$call}{$fun}++;
106 0         0 $funcalled{$fun}{$call}++
107             }
108             }
109 0         0 _analyze( \%funtimes, \%funcalled, \%funcalls, $sort );
110             } #analyze
111              
112             sub _analyze {
113 2     2   681 my( $calltimes, $callers, $calls, $sort ) = @_;
114 2   50     8 $sort ||= 'avg';
115 2         3 my %stats;
116 2         4 my $longsub = 0;
117 2         10 for my $subr ( keys %$calltimes ) {
118 6 100       15 if( length( $subr ) > $longsub ) { $longsub = length( $subr ) };
  2         3  
119 6         9 my @times = sort { $a <=> $b } @{$calltimes->{$subr}};
  38         56  
  6         24  
120 6         12 my $calls = scalar( @times );
121 6         8 my $tottime = 0;
122 6         11 map { $tottime += $_ } @times;
  28         45  
123 6 50       60 $stats{$subr} = {
124             calls => $calls,
125             total => $tottime,
126             mean => $times[ int( @times/2 ) ],
127             avg => $calls ? int($tottime / $calls) : '?',
128             max => $times[$#times],
129             min => $times[0],
130             };
131             }
132 2         8 my( @titles ) = ( 'sub', '# calls', 'total t', 'mean t', 'avg t', 'max t', 'min t' );
133 2         3 my $minwidth = 7;
134 2         4 my $buf = "\n performance stats ( all times are in ms)\n\n";
135 2         14 $buf .= sprintf( "%*s | ", $longsub, "sub" ). join( " | ", map { sprintf( "%*s", $minwidth, $_ ) } @titles[1..$#titles] ) ."\n";
  12         33  
136 2         11 $buf .= '-' x $longsub . '--+-' . join( "-+-", map { '-' x $minwidth } @titles[1..$#titles] )."\n";
  12         25  
137             # for my $subr (sort { $stats{$b}{total} <=> $stats{$a}{total} } keys %stats) {
138 2         9 for my $subr (sort { $stats{$b}{$sort} <=> $stats{$a}{$sort} } keys %stats) {
  6         14  
139             $buf .= join( " | ", sprintf( "%*s ", $longsub, $subr ),
140 6         14 map { sprintf( "%*d", $minwidth, $stats{$subr}{$_} ) }
  36         97  
141             qw( calls total mean avg max min ) )."\n";
142             }
143 2         4 if( 0 ) {
144             $buf .= "Who Calls What\n";
145             for my $subr (sort { $stats{$a}->{total} <=> $stats{$b}->{total} } keys %stats) {
146             my $calls = [sort { $calls->{$subr}{$b} <=> $calls->{$subr}{$a} } keys %{$calls->{$subr}||{}}];
147             my $called_by = [sort { $callers->{$subr}{$b} <=> $callers->{$subr}{$a} } keys %{$callers->{$subr}||{}}];
148             $buf .= " $subr\n" .
149             " Called by :" . ( @$called_by ? "\n\t" . join( "\n\t", map { "$_ $callers->{$subr}{$_}" } @$called_by ) : '' ) . "\n" .
150             " Calls :" . ( @$calls ? "\n\t" . join( "\n\t", map { "$_ $calls->{$subr}{$_}" } @$calls ) : '' ) . "\n";
151             }
152             $buf .= "\n\n";
153             }
154 2         15 $buf;
155             } #_analyze
156              
157             =head2 start
158              
159             This is called to start or continue the data collection process. It takes
160             an option regex parameter in case something different is desired than the
161             one given at init. This must be called to continue the profiling in a
162             child thread if one is forked.
163              
164             =cut
165             sub start {
166 0   0 0 1   my $re = shift || $re;
167 0           my $count = 0;
168             around {
169 0     0     my $subname = $_->{sub_name};
170 0           my $start = [Time::HiRes::gettimeofday]; # returns [ seconds, microseconds ]
171              
172 0           push @stack, $subname;
173 0           $_->proceed;
174              
175 0           pop @stack;
176              
177 0           map { $callers{$subname}{$_}++; $calls{$_}{$subname}++ } @stack;
  0            
  0            
178            
179             # tv_interval returns floating point seconds, convert to ms
180 0           push @{$calltimes{$subname}}, 1_000 * Time::HiRes::tv_interval( $start );
  0            
181              
182 0           my $line = "$subname|" . (1_000 * Time::HiRes::tv_interval( $start ) ) . "|" . join(",", @stack );
183 0           ++$count;
184 0           open( OUT, ">>$tmpFile" );
185 0           print OUT "$line\n";
186 0           close OUT;
187              
188 0           } call $re;
189             } #start
190              
191             1;
192              
193             __END__