Author: timbo
Date: Tue May 22 02:45:49 2007
New Revision: 9581

Modified:
   dbi/trunk/Changes
   dbi/trunk/DBI.pm
   dbi/trunk/DBI.xs
   dbi/trunk/lib/DBI/Profile.pm
   dbi/trunk/lib/DBI/ProfileData.pm
   dbi/trunk/lib/DBI/ProfileDumper.pm
   dbi/trunk/lib/DBI/ProfileDumper/Apache.pm
   dbi/trunk/t/41prof_dump.t
   dbi/trunk/t/86gofer_fail.t

Log:
Added $profile->filename method to DBI::ProfileDumper
Allow filename to be a code ref.
Optimise flush_to_disk().
Write data with full precision (not %.6f)
Enable DBI_PROFILE_APACHE_LOG_DIR env var for mod_perl1 as well as mod_perl2.
Polish up and extend docs for DBI::ProfileDumper && DBI::ProfileDumper::Apache.
Add Quiet=>1 to DBI::ProfileDumper::Apache to not write to STDERR in 
flush_to_disk.
Increase acceptable range of values in t/86gofer_fail.t.


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Tue May 22 02:45:49 2007
@@ -8,6 +8,13 @@
 
 Assorted TODO notes:
 
+Add to profile Path for 'self clocking' sample periods:
+    !Time - integer unix time GMT
+    !TimeN - integer(time/N)*N
+
+Add count of identical frozen_request (plus sum(results size)) to Gofer status
+Highlight those seen before.
+
 Move post-request cleanup into separate method and enable hooks so
     it can be done after the response has been sent
 
@@ -33,6 +40,10 @@
 Add trace modules that just records the last N trace messages into an array
 and prepends them to any error message.
 
+=head2 Changes in DBI 1.57 (svn rev XXX),  XX May 2007
+
+  Fixed unused var warning thanks to JDHEDDEN.
+
 =head2 Changes in DBI 1.56 (svn rev 9561),  13th May 2007
 
   Fixed printf arg warnings thanks to JDHEDDEN.

Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm    (original)
+++ dbi/trunk/DBI.pm    Tue May 22 02:45:49 2007
@@ -9,7 +9,7 @@
 require 5.006_00;
 
 BEGIN {
-$DBI::VERSION = "1.56"; # ==> ALSO update the version in the pod text below!
+$DBI::VERSION = "1.57"; # ==> ALSO update the version in the pod text below!
 }
 
 =head1 NAME
@@ -120,7 +120,7 @@
 
 =head2 NOTES
 
-This is the DBI specification that corresponds to the DBI version 1.56
+This is the DBI specification that corresponds to the DBI version 1.57
 ($Revision$).
 
 The DBI is evolving at a steady pace, so it's good to check that

Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs    (original)
+++ dbi/trunk/DBI.xs    Tue May 22 02:45:49 2007
@@ -4175,7 +4175,8 @@
        if (!SvROK(dest) || SvTYPE(SvRV(dest)) != SVt_PVAV)
            croak("dbi_profile_merge_nodes(%s,...) not an array reference", 
neatsvpv(dest,0));
        if (items <= 1) {
-           (void)cv;
+           (void)cv;   /* avoid unused var warnings */
+           (void)ix;
            RETVAL = 0;
        }
        else {

Modified: dbi/trunk/lib/DBI/Profile.pm
==============================================================================
--- dbi/trunk/lib/DBI/Profile.pm        (original)
+++ dbi/trunk/lib/DBI/Profile.pm        Tue May 22 02:45:49 2007
@@ -709,10 +709,6 @@
        unless UNIVERSAL::isa($thingy,'ARRAY');
 
     push @$leaves, $thingy if $leaves;
-    if (0) {
-       use Data::Dumper;
-       return Dumper($thingy);
-    }
     my ($count, $total_time, $first_time, $min, $max, $first_called, 
$last_called) = @$thingy;
     return sprintf "%s%fs\n", ($pad x $depth), $total_time
        if $count <= 1;

Modified: dbi/trunk/lib/DBI/ProfileData.pm
==============================================================================
--- dbi/trunk/lib/DBI/ProfileData.pm    (original)
+++ dbi/trunk/lib/DBI/ProfileData.pm    Tue May 22 02:45:49 2007
@@ -221,22 +221,19 @@
             $path[$index] = $key; # place new key at end
 
         }
-       elsif (/^=/) {
+       elsif (s/^=\s+//) {
             # it's data - file in the node array with the path in index 0
            # (the optional minus is to make it more robust against systems
            # with unstable high-res clocks - typically due to poor NTP config
            # of kernel SMP behaviour, i.e. min time may be -0.000008))
-            @data = /^=\s+(\d+)
-                       \s+(-?\d+\.?\d*)
-                       \s+(-?\d+\.?\d*)
-                       \s+(-?\d+\.?\d*)
-                       \s+(-?\d+\.?\d*)
-                       \s+(\d+\.?\d*)
-                       \s+(\d+\.?\d*)
-                       \s*$/x;
+
+            @data = split / /, $_;
 
             # corrupt data?
-            croak("Invalid data syntax format in $filename line $.: $_") 
unless @data;
+            croak("Invalid number of fields in $filename line $.: $_")
+                unless @data == 7;
+            croak("Invalid leaf node characters $filename line $.: $_")
+                unless m/^[-+ 0-9eE\.]+$/;
 
            # hook to enable pre-processing of the data - such as mangling SQL
            # so that slightly different statements get treated as the same

Modified: dbi/trunk/lib/DBI/ProfileDumper.pm
==============================================================================
--- dbi/trunk/lib/DBI/ProfileDumper.pm  (original)
+++ dbi/trunk/lib/DBI/ProfileDumper.pm  Tue May 22 02:45:49 2007
@@ -34,8 +34,10 @@
                         File => 'dbi.prof' );
 
   # using a custom path
-  $dbh->{Profile} = DBI::ProfileDumper->new( Path => [ "foo", "bar" ],
-                                             File => 'dbi.prof' );
+  $dbh->{Profile} = DBI::ProfileDumper->new(
+      Path => [ "foo", "bar" ],
+      File => 'dbi.prof',
+  );
 
 
 =head1 DESCRIPTION
@@ -61,8 +63,9 @@
 
   use DBI::Profile;
   $dbh->{Profile} = DBI::ProfileDumper->new(
-                        Path => [ '!Statement' ]
-                        File => 'dbi.prof' );
+      Path => [ '!Statement' ],
+      File => 'dbi.prof'
+  );
 
 The C<Path> option takes the same values as in
 L<DBI::Profile>.  The C<File> option gives the name of the
@@ -84,18 +87,29 @@
 
   my $profile = $dbh->{Profile};
 
-=over 4
+=head2 flush_to_disk
 
-=item $profile->flush_to_disk()
+  $profile->flush_to_disk()
 
 Flushes all collected profile data to disk and empties the Data hash.
 This method may be called multiple times during a program run.
 
-=item $profile->empty()
+=head2 empty
+
+  $profile->empty()
 
 Clears the Data hash without writing to disk.
 
-=back
+=head2 filename
+
+  $profile->empty($filename)
+  $filename = $profile->filename();
+
+Get or set the filename.
+
+The filename can be specified as a CODE reference, in which case the referenced
+code should return the filename to be used. The code will be called with the
+profile object as its first argument.
 
 =head1 DATA FORMAT
 
@@ -104,7 +118,7 @@
 a block of variable declarations describes the profile.  After two
 newlines, the profile data forms the body of the file.  For example:
 
-  DBI::ProfileDumper 1.0
+  DBI::ProfileDumper 2.003762
   Path = [ '!Statement', '!MethodName' ]
   Program = t/42profile_data.t
 
@@ -161,95 +175,109 @@
 use Carp qw(croak);
 use Symbol;
 
+my $program_header;
+
+
 # validate params and setup default
 sub new {
     my $pkg = shift;
     my $self = $pkg->SUPER::new(@_);
 
-    # File defaults to dbi.prof
-    $self->{File} = "dbi.prof" unless exists $self->{File};
+    # provide a default filename
+    $self->filename("dbi.prof") unless $self->filename;
 
     return $self;
 }
 
+
+# get/set filename to use
+sub filename {
+    my $self = shift;
+    $self->{File} = shift if @_;
+    my $filename = $self->{File};
+    $filename = $filename->($self) if ref($filename) eq 'CODE';
+    return $filename;
+}
+
+
 # flush available data to disk
 sub flush_to_disk {
     my $self = shift;
-    my $data = $self->{Data};
+    my $filename = $self->filename;
 
     my $fh = gensym;
     if ($self->{_wrote_header}) {
         # append more data to the file
-        open($fh, ">>$self->{File}") 
-          or croak("Unable to open '$self->{File}' for profile output: $!");
+        # XXX assumes that Path hasn't changed
+        open($fh, ">>$filename") 
+          or croak("Unable to open '$filename' for profile output: $!");
     } else {
-        # create new file and write the header
-        open($fh, ">$self->{File}") 
-          or croak("Unable to open '$self->{File}' for profile output: $!");
+        # create new file (overwrite existing) and write the header
+        open($fh, ">$filename") 
+          or croak("Unable to open '$filename' for profile output: $!");
         $self->write_header($fh);
         $self->{_wrote_header} = 1;
     }
 
     $self->write_data($fh, $self->{Data}, 1);
 
-    close($fh) or croak("Error closing '$self->{File}': $!");
+    close($fh) or croak("Error closing '$filename': $!");
 
     $self->empty();
 }
 
+
 # empty out profile data
 sub empty {
     shift->{Data} = {};
 }
 
-sub _print {
-       my($fh) = shift;
-       
-       # isolate us against globals which effect print
-       local($\, $,);
-       
-       print $fh @_;
-}
-
 
 # write header to a filehandle
 sub write_header {
     my ($self, $fh) = @_;
 
+    # isolate us against globals which effect print
+    local($\, $,);
+
     # module name and version number
-    _print $fh, ref($self), " ", $self->VERSION, "\n";
+    print $fh ref($self), " ", $self->VERSION, "\n";
 
-    # print out Path
-    my @path_words;
-    if ($self->{Path}) {
-        foreach (@{$self->{Path}}) {
-            push @path_words, $_;
-        }
-    }
-    _print $fh, "Path = [ ", join(', ', @path_words), " ]\n";
+    # print out Path (may contain CODE refs etc)
+    my @path_words = map { escape_key($_) } @{ $self->{Path} || [] };
+    print $fh "Path = [ ", join(', ', @path_words), " ]\n";
 
     # print out $0 and @ARGV
-    _print $fh, "Program = $0";
-    _print $fh, " ", join(", ", @ARGV) if @ARGV;
-    _print $fh, "\n";
+    if (!$program_header) {
+        # XXX should really quote as well as escape
+        $program_header = "Program = "
+            . join(" ", map { escape_key($_) } $0, @ARGV)
+            . "\n";
+    }
+    print $fh $program_header;
 
     # all done
-    _print $fh, "\n";
+    print $fh "\n";
 }
 
+
 # write data in the proscribed format
 sub write_data {
     my ($self, $fh, $data, $level) = @_;
 
+    # XXX it's valid for $data to be an ARRAY ref, i.e., Path is empty.
     # produce an empty profile for invalid $data
     return unless $data and UNIVERSAL::isa($data,'HASH');
     
+    # isolate us against globals which affect print
+    local ($\, $,);
+
     while (my ($key, $value) = each(%$data)) {
         # output a key
-        _print $fh, "+ ", $level, " ", quote_key($key), "\n";
+        print $fh "+ $level ". escape_key($key). "\n";
         if (UNIVERSAL::isa($value,'ARRAY')) {
             # output a data set for a leaf node
-            _print $fh, sprintf "= %4d %.6f %.6f %.6f %.6f %.6f %.6f\n", 
@$value;
+            print $fh "= ".join(' ', @$value)."\n";
         } else {
             # recurse through keys - this could be rewritten to use a
             # stack for some small performance gain
@@ -258,8 +286,9 @@
     }
 }
 
-# quote a key for output
-sub quote_key {
+
+# escape a key for output
+sub escape_key {
     my $key = shift;
     $key =~ s!\\!\\\\!g;
     $key =~ s!\n!\\n!g;
@@ -268,6 +297,7 @@
     return $key;
 }
 
+
 # flush data to disk when profile object goes out of scope
 sub on_destroy {
     shift->flush_to_disk();

Modified: dbi/trunk/lib/DBI/ProfileDumper/Apache.pm
==============================================================================
--- dbi/trunk/lib/DBI/ProfileDumper/Apache.pm   (original)
+++ dbi/trunk/lib/DBI/ProfileDumper/Apache.pm   Tue May 22 02:45:49 2007
@@ -10,33 +10,17 @@
 
   PerlSetEnv DBI_PROFILE 2/DBI::ProfileDumper::Apache
 
-Under mod_perl2 RC5+ you'll need to also add:
-
-  PerlSetEnv DBI_PROFILE_APACHE_LOG_DIR /server_root/logs
-
-OR add
-
-  PerlOptions +GlobalRequest
-
-to the gobal config section you're about test with DBI::ProfileDumper::Apache.
-If you don't do this, you'll see messages in your error_log similar to:
-
-  DBI::ProfileDumper::Apache on_destroy failed: Global $r object is not 
available. Set:
-    PerlOptions +GlobalRequest in httpd.conf at 
..../DBI/ProfileDumper/Apache.pm line 144
+(If you're using mod_perl2, see L</When using mod_perl2> for some additional 
notes.)
 
 Then restart your server.  Access the code you wish to test using a
 web browser, then shutdown your server.  This will create a set of
-F<dbi.prof.*> files in your Apache log directory.  Get a profiling
-report with L<dbiprof|dbiprof>:
-
-  dbiprof /usr/local/apache/logs/dbi.prof.*
+F<dbi.prof.*> files in your Apache log directory.
 
-When you're ready to perform another profiling run, delete the old
-files
+Get a profiling report with L<dbiprof|dbiprof>:
 
-  rm /usr/local/apache/logs/dbi.prof.*
+  dbiprof /path/to/your/apache/logs/dbi.prof.*
 
-and start again.
+When you're ready to perform another profiling run, delete the old files and 
start again.
 
 =head1 DESCRIPTION
 
@@ -44,7 +28,7 @@
 this module you can collect profiling data from mod_perl applications.
 It works by creating a DBI::ProfileDumper data file for each Apache
 process.  These files are created in your Apache log directory.  You
-can then use dbiprof to analyze the profile files.
+can then use the dbiprof utility to analyze the profile files.
 
 =head1 USAGE
 
@@ -55,17 +39,40 @@
 
   PerlSetEnv DBI_PROFILE 2/DBI::ProfileDumper::Apache
 
-If you want to use one of DBI::Profile's other Path settings, you can
-use a string like:
-
-  PerlSetEnv DBI_PROFILE 2/DBI::ProfileDumper::Apache
+The DBI will look after loading and using the module when the first DBI handle
+is created.
 
 It's also possible to use this module by setting the Profile attribute
 of any DBI handle:
 
   $dbh->{Profile} = "2/DBI::ProfileDumper::Apache";
 
-See L<DBI::ProfileDumper> for more possibilities.
+See L<DBI::ProfileDumper> for more possibilities, and L<DBI::Profile> for full
+details of the DBI's profiling mechanism.
+
+=head2 WRITING PROFILE DATA
+
+The profile data files will be written to your Apache log directory by default.
+You can use the C<DBI_PROFILE_APACHE_LOG_DIR> env var to change that. For 
example:
+
+  PerlSetEnv DBI_PROFILE_APACHE_LOG_DIR /server_root/logs
+
+=head3 When using mod_perl2
+
+Under mod_perl2 you'll need to either set the C<DBI_PROFILE_APACHE_LOG_DIR> 
env var,
+or enable the mod_perl2 C<GlobalRequest> option, like this:
+
+  PerlOptions +GlobalRequest
+
+to the global config section you're about test with DBI::ProfileDumper::Apache.
+If you don't do one of those then you'll see messages in your error_log 
similar to:
+
+  DBI::ProfileDumper::Apache on_destroy failed: Global $r object is not 
available. Set:
+    PerlOptions +GlobalRequest in httpd.conf at 
..../DBI/ProfileDumper/Apache.pm line 144
+
+=head3 Naming the files
+
+XXX
 
 =head2 GATHERING PROFILE DATA
 
@@ -74,11 +81,11 @@
 files will be produced when Apache exits and you'll see something like
 this in your error_log:
 
-  DBI::ProfileDumper::Apache writing to /usr/local/apache/logs/dbi.prof.2619
+  DBI::ProfileDumper::Apache writing to 
/usr/local/apache/logs/dbi.prof.2604.2619
 
 Now you can use dbiprof to examine the data:
 
-  dbiprof /usr/local/apache/logs/dbi.prof.*
+  dbiprof /usr/local/apache/logs/dbi.prof.2604.*
 
 By passing dbiprof a list of all generated files, dbiprof will
 automatically merge them into one result set.  You can also pass
@@ -89,22 +96,23 @@
 Once you've made some code changes, you're ready to start again.
 First, delete the old profile data files:
 
-  rm /usr/local/apache/logs/dbi.prof.* 
+  rm /usr/local/apache/logs/dbi.prof. 
 
 Then restart your server and get back to work.
 
 =head1 MEMORY USAGE
 
-DBI::Profile can use a lot of memory for very active applications.  It
-collects profiling data in memory for each distinct query your
-application runs.  You can avoid this problem with a call like this:
+DBI::Profile can use a lot of memory for very active applications because it
+collects profiling data in memory for each distinct query run.
+Calling C<flush_to_disk()> will write the current data to disk and free the
+memory it's using. For example:
 
   $dbh->{Profile}->flush_to_disk() if $dbh->{Profile};
 
-Calling C<flush_to_disk()> will clear out the profile data and write
-it to disk.  Put this someplace where it will run on every request,
-like a CleanupHandler, and your memory troubles should go away.  Well,
-at least the ones caused by DBI::Profile anyway.
+or, rather than flush every time, you could flush less often:
+
+  $dbh->{Profile}->flush_to_disk()
+    if $dbh->{Profile} and ++$i % 100;
 
 =head1 AUTHOR
 
@@ -137,29 +145,33 @@
     
     # setup File per process
     my $path;
-    if (MP2) {
-        if ($ENV{DBI_PROFILE_APACHE_LOG_DIR}) {
-            $path = $ENV{DBI_PROFILE_APACHE_LOG_DIR};
-        }
-        else {
+    if ($ENV{DBI_PROFILE_APACHE_LOG_DIR}) {
+        $path = $ENV{DBI_PROFILE_APACHE_LOG_DIR};
+    }
+    else {
+        if (MP2) {
             require Apache2::RequestUtil;
             require Apache2::ServerUtil;
             $path = 
Apache2::ServerUtil::server_root_relative(Apache2::RequestUtil->request()->pool,
 "logs/")
         }
+        else {
+            require Apache;
+            $path = Apache->server_root_relative("logs/");
+        }
     }
-    else {
-       require Apache;
-       $path = Apache->server_root_relative("logs/");
-    }
-    my $old_file = $self->{File};
-    $self->{File} = File::Spec->catfile($path, "$old_file.$$");
+
+    # to be able to identify groups of profile files from the same set of
+    # apache processes, we include the parent pid in the file name.
+    my $parent_pid = getppid();
+
+    my $filename = $self->filename();
+    local $self->{File} = File::Spec->catfile($path, 
"$filename.$parent_pid.$$");
+
+    print STDERR "DBI::ProfileDumper::Apache writing to $self->{File}\n"
+        unless $self->{Quiet};
 
     # write out to disk
-    print STDERR "DBI::ProfileDumper::Apache writing to $self->{File}\n";
-    $self->SUPER::flush_to_disk(@_);
-   
-    # reset File to previous setting
-    $self->{File} = $old_file;    
+    return $self->SUPER::flush_to_disk(@_);
 }
 
 1;

Modified: dbi/trunk/t/41prof_dump.t
==============================================================================
--- dbi/trunk/t/41prof_dump.t   (original)
+++ dbi/trunk/t/41prof_dump.t   Tue May 22 02:45:49 2007
@@ -63,13 +63,15 @@
 my @prof = <PROF>;
 close PROF;
 
+print @prof;
+
 # has a header?
-ok( $prof[0] =~ /^DBI::ProfileDumper\s+([\d.]+)/, 'Found a version number' );
-# Can't use like() because we need $1
+like( $prof[0], '/^DBI::ProfileDumper\s+([\d.]+)/', 'Found a version number' );
 
 # version matches VERSION? (DBI::ProfileDumper uses $self->VERSION so
 # it's a stringified version object that looks like N.N.N)
-is( $1, DBI::ProfileDumper->VERSION, 'Version numbers match' );
+$prof[0] =~ /^DBI::ProfileDumper\s+([\d.]+)/;
+is( $1, DBI::ProfileDumper->VERSION, "Version numbers match in $prof[0]" );
 
 like( $prof[1], qr{^Path\s+=\s+\[\s+\]}, 'Found the Path');
 ok( $prof[2] =~ m{^Program\s+=\s+(\S+)}, 'Found the Program');

Modified: dbi/trunk/t/86gofer_fail.t
==============================================================================
--- dbi/trunk/t/86gofer_fail.t  (original)
+++ dbi/trunk/t/86gofer_fail.t  Tue May 22 02:45:49 2007
@@ -90,7 +90,7 @@
     ReadOnly => 1,
 } );
 between_ok precentage_exceptions(100, sub { $dbh_50r1ro->do("set foo=1") }),
-    15, 35, 'should fail ~25% (ie 50% with one retry)';
+    10, 40, 'should fail ~25% (ie 50% with one retry)';
 between_ok $dbh_50r1ro->{go_transport}->meta->{request_retry_count},
     35, 65, 'transport request_retry_count should be around 50';
 

Reply via email to