File Coverage

blib/lib/DBI/ProfileDumper.pm
Criterion Covered Total %
statement 82 83 98.8
branch 27 40 67.5
condition 8 17 47.0
subroutine 12 12 100.0
pod 2 7 28.5
total 131 159 82.3


line stmt bran cond sub pod time code
1             package DBI::ProfileDumper;
2 4     4   200285 use strict;
  4         9  
  4         191  
3              
4             =head1 NAME
5              
6             DBI::ProfileDumper - profile DBI usage and output data to a file
7              
8             =head1 SYNOPSIS
9              
10             To profile an existing program using DBI::ProfileDumper, set the
11             DBI_PROFILE environment variable and run your program as usual. For
12             example, using bash:
13              
14             DBI_PROFILE=2/DBI::ProfileDumper program.pl
15              
16             Then analyze the generated file (F) with L:
17              
18             dbiprof
19              
20             You can also activate DBI::ProfileDumper from within your code:
21              
22             use DBI;
23              
24             # profile with default path (2) and output file (dbi.prof)
25             $dbh->{Profile} = "!Statement/DBI::ProfileDumper";
26              
27             # same thing, spelled out
28             $dbh->{Profile} = "!Statement/DBI::ProfileDumper/File:dbi.prof";
29              
30             # another way to say it
31             use DBI::ProfileDumper;
32             $dbh->{Profile} = DBI::ProfileDumper->new(
33             Path => [ '!Statement' ],
34             File => 'dbi.prof' );
35              
36             # using a custom path
37             $dbh->{Profile} = DBI::ProfileDumper->new(
38             Path => [ "foo", "bar" ],
39             File => 'dbi.prof',
40             );
41              
42              
43             =head1 DESCRIPTION
44              
45             DBI::ProfileDumper is a subclass of L which
46             dumps profile data to disk instead of printing a summary to your
47             screen. You can then use L to analyze the data in
48             a number of interesting ways, or you can roll your own analysis using
49             L.
50              
51             B For Apache/mod_perl applications, use
52             L.
53              
54             =head1 USAGE
55              
56             One way to use this module is just to enable it in your C<$dbh>:
57              
58             $dbh->{Profile} = "1/DBI::ProfileDumper";
59              
60             This will write out profile data by statement into a file called
61             F. If you want to modify either of these properties, you
62             can construct the DBI::ProfileDumper object yourself:
63              
64             use DBI::ProfileDumper;
65             $dbh->{Profile} = DBI::ProfileDumper->new(
66             Path => [ '!Statement' ],
67             File => 'dbi.prof'
68             );
69              
70             The C option takes the same values as in
71             L. The C option gives the name of the
72             file where results will be collected. If it already exists it will be
73             overwritten.
74              
75             You can also activate this module by setting the DBI_PROFILE
76             environment variable:
77              
78             $ENV{DBI_PROFILE} = "!Statement/DBI::ProfileDumper";
79              
80             This will cause all DBI handles to share the same profiling object.
81              
82             =head1 METHODS
83              
84             The following methods are available to be called using the profile
85             object. You can get access to the profile object from the Profile key
86             in any DBI handle:
87              
88             my $profile = $dbh->{Profile};
89              
90             =head2 flush_to_disk
91              
92             $profile->flush_to_disk()
93              
94             Flushes all collected profile data to disk and empties the Data hash. Returns
95             the filename written to. If no profile data has been collected then the file is
96             not written and flush_to_disk() returns undef.
97              
98             The file is locked while it's being written. A process 'consuming' the files
99             while they're being written to, should rename the file first, then lock it,
100             then read it, then close and delete it. The C option to
101             L does the right thing.
102              
103             This method may be called multiple times during a program run.
104              
105             =head2 empty
106              
107             $profile->empty()
108              
109             Clears the Data hash without writing to disk.
110              
111             =head2 filename
112              
113             $filename = $profile->filename();
114              
115             Get or set the filename.
116              
117             The filename can be specified as a CODE reference, in which case the referenced
118             code should return the filename to be used. The code will be called with the
119             profile object as its first argument.
120              
121             =head1 DATA FORMAT
122              
123             The data format written by DBI::ProfileDumper starts with a header
124             containing the version number of the module used to generate it. Then
125             a block of variable declarations describes the profile. After two
126             newlines, the profile data forms the body of the file. For example:
127              
128             DBI::ProfileDumper 2.003762
129             Path = [ '!Statement', '!MethodName' ]
130             Program = t/42profile_data.t
131              
132             + 1 SELECT name FROM users WHERE id = ?
133             + 2 prepare
134             = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
135             + 2 execute
136             1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
137             + 2 fetchrow_hashref
138             = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
139             + 1 UPDATE users SET name = ? WHERE id = ?
140             + 2 prepare
141             = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
142             + 2 execute
143             = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
144              
145             The lines beginning with C<+> signs signify keys. The number after
146             the C<+> sign shows the nesting level of the key. Lines beginning
147             with C<=> are the actual profile data, in the same order as
148             in DBI::Profile.
149              
150             Note that the same path may be present multiple times in the data file
151             since C may be called more than once. When read by
152             DBI::ProfileData the data points will be merged to produce a single
153             data set for each distinct path.
154              
155             The key strings are transformed in three ways. First, all backslashes
156             are doubled. Then all newlines and carriage-returns are transformed
157             into C<\n> and C<\r> respectively. Finally, any NULL bytes (C<\0>)
158             are entirely removed. When DBI::ProfileData reads the file the first
159             two transformations will be reversed, but NULL bytes will not be
160             restored.
161              
162             =head1 AUTHOR
163              
164             Sam Tregar
165              
166             =head1 COPYRIGHT AND LICENSE
167              
168             Copyright (C) 2002 Sam Tregar
169              
170             This program is free software; you can redistribute it and/or modify
171             it under the same terms as Perl 5 itself.
172              
173             =cut
174              
175             # inherit from DBI::Profile
176 4     4   1505 use DBI::Profile;
  4         10  
  4         296  
177              
178             our @ISA = ("DBI::Profile");
179              
180             our $VERSION = "2.015325";
181              
182 4     4   23 use Carp qw(croak);
  4         8  
  4         133  
183 4     4   19 use Fcntl qw(:flock);
  4         7  
  4         401  
184 4     4   981 use Symbol;
  4         2268  
  4         3257  
185              
186             my $HAS_FLOCK = (defined $ENV{DBI_PROFILE_FLOCK})
187             ? $ENV{DBI_PROFILE_FLOCK}
188             : do { local $@; eval { flock STDOUT, 0; 1 } };
189              
190             my $program_header;
191              
192              
193             # validate params and setup default
194             sub new {
195 6     6 0 13 my $pkg = shift;
196 6         38 my $self = $pkg->SUPER::new(
197             LockFile => $HAS_FLOCK,
198             @_,
199             );
200              
201             # provide a default filename
202 6 50       20 $self->filename("dbi.prof") unless $self->filename;
203              
204 0         0 DBI->trace_msg("$self: @{[ %$self ]}\n",0)
205 6 50 33     18 if $self->{Trace} && $self->{Trace} >= 2;
206              
207 6         46 return $self;
208             }
209              
210              
211             # get/set filename to use
212             sub filename {
213 56     56 1 94 my $self = shift;
214 56 50       140 $self->{File} = shift if @_;
215 56         116 my $filename = $self->{File};
216 56 50       123 $filename = $filename->($self) if ref($filename) eq 'CODE';
217 56         108 return $filename;
218             }
219              
220              
221             # flush available data to disk
222             sub flush_to_disk {
223 50     50 1 1450 my $self = shift;
224 50         112 my $class = ref $self;
225 50         159 my $filename = $self->filename;
226 50         111 my $data = $self->{Data};
227              
228 50         73 if (1) { # make an option
229 50 100 33     388 if (not $data or ref $data eq 'HASH' && !%$data) {
      66        
230 2 50       7 DBI->trace_msg("flush_to_disk skipped for empty profile\n",0) if $self->{Trace};
231 2         6 return undef;
232             }
233             }
234              
235 48         131 my $fh = gensym;
236 48 100 100     642 if (($self->{_wrote_header}||'') eq $filename) {
237             # append more data to the file
238             # XXX assumes that Path hasn't changed
239 42 50       1401 open($fh, ">>", $filename)
240             or croak("Unable to open '$filename' for $class output: $!");
241             } else {
242             # create new file (or overwrite existing)
243 6 100       140 if (-f $filename) {
244 2         9 my $bak = $filename.'.prev';
245 2         35 unlink($bak);
246 2 50       116 rename($filename, $bak)
247             or warn "Error renaming $filename to $bak: $!\n";
248             }
249 6 50       378 open($fh, ">", $filename)
250             or croak("Unable to open '$filename' for $class output: $!");
251             }
252             # lock the file (before checking size and writing the header)
253 48 50       610 flock($fh, LOCK_EX) if $self->{LockFile};
254             # write header if file is empty - typically because we just opened it
255             # in '>' mode, or perhaps we used '>>' but the file had been truncated externally.
256 48 100       330 if (-s $fh == 0) {
257 6 50       23 DBI->trace_msg("flush_to_disk wrote header to $filename\n",0) if $self->{Trace};
258 6         25 $self->write_header($fh);
259 6         15 $self->{_wrote_header} = $filename;
260             }
261              
262 48         189 my $lines = $self->write_data($fh, $self->{Data}, 1);
263 48 50       146 DBI->trace_msg("flush_to_disk wrote $lines lines to $filename\n",0) if $self->{Trace};
264              
265 48 50       1943 close($fh) # unlocks the file
266             or croak("Error closing '$filename': $!");
267              
268 48         263 $self->empty();
269              
270              
271 48         1139 return $filename;
272             }
273              
274              
275             # write header to a filehandle
276             sub write_header {
277 6     6 0 18 my ($self, $fh) = @_;
278              
279             # isolate us against globals which effect print
280 6         27 local($\, $,);
281              
282             # $self->VERSION can return undef during global destruction
283 6   33     149 my $version = $self->VERSION || $VERSION;
284              
285             # module name and version number
286 6         76 print $fh ref($self)." $version\n";
287              
288             # print out Path (may contain CODE refs etc)
289 6 100       13 my @path_words = map { escape_key($_) } @{ $self->{Path} || [] };
  8         16  
  6         27  
290 6         24 print $fh "Path = [ ", join(', ', @path_words), " ]\n";
291              
292             # print out $0 and @ARGV
293 6 100       25 if (!$program_header) {
294             # XXX should really quote as well as escape
295             $program_header = "Program = "
296 4         12 . join(" ", map { escape_key($_) } $0, @ARGV)
  4         9  
297             . "\n";
298             }
299 6         13 print $fh $program_header;
300              
301             # all done
302 6         19 print $fh "\n";
303             }
304              
305              
306             # write data in the proscribed format
307             sub write_data {
308 140     140 0 282 my ($self, $fh, $data, $level) = @_;
309              
310             # XXX it's valid for $data to be an ARRAY ref, i.e., Path is empty.
311             # produce an empty profile for invalid $data
312 140 50 33     595 return 0 unless $data and UNIVERSAL::isa($data,'HASH');
313              
314             # isolate us against globals which affect print
315 140         438 local ($\, $,);
316              
317 140         212 my $lines = 0;
318 140         467 while (my ($key, $value) = each(%$data)) {
319             # output a key
320 374         753 print $fh "+ $level ". escape_key($key). "\n";
321 374 100       792 if (UNIVERSAL::isa($value,'ARRAY')) {
322             # output a data set for a leaf node
323 282         3251 print $fh "= ".join(' ', @$value)."\n";
324 282         825 $lines += 1;
325             } else {
326             # recurse through keys - this could be rewritten to use a
327             # stack for some small performance gain
328 92         246 $lines += $self->write_data($fh, $value, $level + 1);
329             }
330             }
331 140         410 return $lines;
332             }
333              
334              
335             # escape a key for output
336             sub escape_key {
337 386     386 0 471 my $key = shift;
338 386         605 $key =~ s!\\!\\\\!g;
339 386         448 $key =~ s!\n!\\n!g;
340 386         448 $key =~ s!\r!\\r!g;
341 386         443 $key =~ s!\0!!g;
342 386         1106 return $key;
343             }
344              
345              
346             # flush data to disk when profile object goes out of scope
347             sub on_destroy {
348 6     6 0 24 shift->flush_to_disk();
349             }
350              
351             1;