File Coverage

blib/lib/Devel/DProfLB.pm
Criterion Covered Total %
statement 37 75 49.3
branch 7 32 21.8
condition n/a
subroutine 6 8 75.0
pod 1 3 33.3
total 51 118 43.2


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             # Copyright (c) 2006 by Jeff Weisberg
4             # Author: Jeff Weisberg
5             # Created: 2006-Mar-11 22:16 (EST)
6             # Function: code profiler
7             #
8             # $Id: DProfLB.pm,v 1.3 2006/05/27 17:39:48 jaw Exp jaw $
9              
10             package Devel::DProfLB;
11             # use strict; - does not play well with the debugger
12             our $VERSION = '0.01';
13              
14             =head1 NAME
15              
16             Devel::DProfLB - tell me why my perl program runs so slowly
17              
18             =head1 SYNOPSIS
19              
20             perl -d:DProfLB program.pl
21             dprofpp
22              
23             =head1 DESCRIPTION
24              
25             The Devel::DProfLB package is a Perl code profiler.
26              
27             It is intended to produce output similar to, and
28             compatible with, Devel::DProf, but be 'Less Bad'.
29              
30             This will collect information on the execution time of a Perl
31             script and of the subs in that script. This information
32             can be used to determine which subroutines are using the
33             most time and which subroutines are being called most
34             often. This information can also be used to create an
35             execution graph of the script, showing subroutine
36             relationships.
37              
38             To profile a Perl script run the perl interpreter with the
39             -d debugging switch. The profiler uses the debugging
40             hooks. So to profile script test.pl the following command
41             should be used:
42              
43             perl5 -d:DProfLB test.pl
44              
45             When the script terminates the profiler will dump the profile information to
46             a file called tmon.out. A tool like dprofpp can be used
47             to interpret the information which is in that profile.
48             The following command will print the top 15 subroutines
49             which used the most time:
50              
51             dprofpp
52              
53             To print an execution graph of the subroutines in the
54             script use the following command:
55              
56             dprofpp -T
57              
58             Consult the dprofpp manpage for other options.
59              
60             =head1 ENVIRONMENT
61              
62             =over 4
63              
64             =item C
65              
66             Filename to save profile data to, default is F
67              
68             =back
69              
70             =head1 BUGS
71              
72             Valid profiling data is not saved until the application
73             terminates and runs this modules END{} block. Applications
74             which cause END{} blocks not to run (such as call _exit
75             or exec) will leave a corrupt and/or incomplete temporary data file.
76              
77             On most systems, the timing data recorded by this profiler has
78             a granularity of 0.01 second. This may or may not be precise
79             enough for your application.
80              
81             If the program being profiled contains subroutines which do
82             not return in a normal manner (such as by throwing an exception),
83             the timing data is estimated and may be attributed incorrectly.
84              
85             Et cetera.
86              
87             =head1 SECURITY ISSUES
88              
89             The standard dprofpp program blindly Cs portions
90             of the tmon.out datafile.
91              
92             =head1 SEE ALSO
93              
94             Devel::Profile
95             Devel::DProf
96             dprofpp
97             Yellowstone National Park
98            
99             =head1 AUTHOR
100              
101             Jeff Weisberg - http://www.tcp4me.com/
102            
103             =cut
104             ;
105              
106             package DB;
107              
108 1         7 use POSIX 'times', # different than the builtin times
109 1     1   1634 'sysconf', '_SC_CLK_TCK';
  1         7731  
110              
111             my @prof_stack = (); # call stack, to account for subs that haven't returned
112             my $realtime_adj; # because it overflows an int32
113             my $hz; # clock ticks per second
114             my $prof_pid; # process id of process being profiled
115             my @overhead; # calibration overhead
116             my $tmpfile; # temporary data file
117             my $monfile = $ENV{PERL_DPROF_OUT_FILE_NAME} || 'tmon.out';
118             my $NCALOOP = 1000;
119             my $calibrated = 0;
120             our $sub;
121              
122 0     0 0 0 sub DB {}
123              
124             BEGIN {
125              
126 1     1   1651 $prof_pid = $$;
127 1         4 $tmpfile = "tmon$$.out";
128 1 50       110 open(PROF, ">$tmpfile") || die "cannot open $tmpfile: $!\n";
129            
130             # calculate hertz
131 1         3 eval { $hz = sysconf( _SC_CLK_TCK ) };
  1         20  
132 1 50       4 unless( $hz ){
133             # if unavailable, estimate
134 0         0 my($st) = times();
135 0         0 sleep 1;
136 0         0 my($et) = times();
137 0         0 $hz = $et - $st;
138             }
139            
140 1         897 ($realtime_adj) = times();
141              
142             }
143             END {
144              
145             # original process only, if we fork()
146 1 50   1   174 return unless $$ == $prof_pid;
147            
148 1         5 my($rt, $ut, $st) = prof_times();
149              
150             # generate data for any unfinished subs
151 1 50       6 if( @prof_stack ){
152 0         0 print PROF "# the following did not return, due to program termination\n";
153 0         0 for my $asx (reverse @prof_stack){
154 0         0 print PROF "- $ut $st $rt $asx\n";
155             }
156             }
157            
158 1         92 close PROF;
159              
160             # reopen data, add headers, output new file
161 1 50       45 open(TMP, $tmpfile) || warn "cannot open $tmpfile: $!\n";
162 1 50       101 open(PROF, ">$monfile") || warn "cannot open $monfile: $!\n";
163            
164             # output header
165 1         7 print PROF "#fOrTyTwO\n";
166             # this portion of the header is blindly evaled by dprofpp
167             # any valid perl may be placed here
168             # print PR0F "`echo pwned, rm -rf /`;\n"; # Yikes!
169             # print PROF 'warn "SECURITY WARNING: this version of $0 may be unsafe. upgrade?\n";', "\n";
170 1         7 print PROF "\$hz=$hz;\n\$XS_VERSION='DProfLB-$Devel::DProfLB::VERSION';\n";
171 1         2 print PROF "# All timing values are given in HZ\n";
172 1         7 print PROF "\$over_utime=$overhead[1]; \$over_stime=$overhead[2]; \$over_rtime=$overhead[0];\n";
173 1         5 print PROF "\$over_tests=$NCALOOP;\n";
174 1         5 print PROF "\$rrun_utime=$ut; \$rrun_stime=$st; \$rrun_rtime=$rt;\n";
175 1         2 print PROF "PART2\n";
176              
177             # remove calibration artifacts
178 1 50       32 while(){ last if /^\#CALIBRATED/ }
  1         8  
179             # copy the rest
180 1         10 while(){ print PROF }
  0         0  
181            
182 1         22 close TMP;
183 1         40 close PROF;
184 1         92 unlink $tmpfile;
185 1         0 $prof_pid = undef;
186             }
187              
188             sub prof_times {
189 3     3 0 25 my @t = times;
190              
191             # NB: ^T * HZ > MAX_INT32
192             # => force positive
193 3         8 $t[0] -= $realtime_adj;
194 3         9 @t;
195             }
196              
197             sub sub {
198              
199 0     0 1 0 my($rt, $ut, $st) = prof_times();
200            
201 0         0 my $sx = $sub;
202 0 0       0 if( ref $sx ){
203 0         0 my @c = caller;
204             # was 0, now 1
205             # nb: @c = (pkg, file, line, ...)
206 0         0 $sx = ":$c[1]:$c[2]";
207             }
208              
209             # do not corrupt data on fork()
210 0         0 my $noprof = $$ != $prof_pid;
211 0 0       0 if( $noprof ){
212 0         0 close PROF;
213             }
214            
215 0 0       0 print PROF "+ $ut $st $rt $sx\n" unless $noprof;
216            
217 0         0 push @prof_stack, $sx;
218 0         0 my $ss = @prof_stack;
219            
220 0         0 my( $wa, $r, @r );
221 0         0 $wa = wantarray;
222 0 0       0 if( $wa ){
    0          
223 0         0 @r = &$sub;
224             }elsif( defined $wa ){
225 0         0 $r = &$sub;
226             }else{
227 0         0 &$sub;
228             }
229              
230 0         0 ($rt, $ut, $st) = prof_times();
231            
232 0 0       0 if( $ss < @prof_stack ){
233             # we took an exception - account for aborted subs
234              
235 0         0 while( $ss < @prof_stack ){
236 0         0 my $asx = pop @prof_stack;
237 0 0       0 next if $noprof;
238 0         0 print PROF "# $asx did not return normally\n";
239 0         0 print PROF "- $ut $st $rt $asx\n";
240             }
241             }
242            
243 0         0 pop @prof_stack;
244 0 0       0 print PROF "- $ut $st $rt $sx\n" unless $noprof;
245              
246 0 0       0 if( $wa ){
247 0         0 @r;
248             }else{
249 0         0 $r;
250             }
251             }
252              
253             # calculate (estimate) profiler overhead
254             package Devel::DProfLB;
255 1     1   8 use strict;
  1         2  
  1         170  
256             my @st = DB::prof_times();
257             sub __db_calibrate_adj {
258 1000     1000   1175 my $x = shift;
259             }
260             for my $i (1..$NCALOOP){
261             __db_calibrate_adj();
262             }
263             my @et = DB::prof_times();
264             for my $i (0..2){ $overhead[$i] = $et[$i] - $st[$i] }
265             print DB::PROF "#CALIBRATED\n";
266              
267             1;