| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  |  | 
| 2 |  |  |  |  |  |  | =head1 NAME | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | Profile::Log - collect loggable application profiling stats | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | use Profile::Log; | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | ... | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | sub event_processor { | 
| 13 |  |  |  |  |  |  | my $timer = Profile::Log->new() if PROFILE; | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | do_something(); | 
| 16 |  |  |  |  |  |  | $timer->did("minor") if PROFILE > 1; | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | $timer->mark("parallel") if PROFILE; | 
| 19 |  |  |  |  |  |  | do_parallel_things(); | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | wait_for_thing1(); | 
| 22 |  |  |  |  |  |  | $timer->did("thing1", "parallel") if PROFILE; | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | wait_for_thing2(); | 
| 25 |  |  |  |  |  |  | $timer->did("thing2", "parallel") if PROFILE; | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | finish_up(); | 
| 28 |  |  |  |  |  |  | $timer->did("finish") if PROFILE > 1; | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | # this module does not handle logging itself. | 
| 31 |  |  |  |  |  |  | print LOG $timer->logline if PROFILE; | 
| 32 |  |  |  |  |  |  | } | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | # later... available processing methods | 
| 35 |  |  |  |  |  |  | my $timer = Profile::Log->new($log_line); | 
| 36 |  |  |  |  |  |  | print $timer->zero;  # profile start time | 
| 37 |  |  |  |  |  |  | print $timer->end;   # profile stop time | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | # ... t.b.c. ... | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | C<Profile::Log> is about breaking down time spent in "critical paths", | 
| 44 |  |  |  |  |  |  | such as in transaction processing servers, into logical pieces - with | 
| 45 |  |  |  |  |  |  | easily tunable operation that does not incur undue performance | 
| 46 |  |  |  |  |  |  | penalities when it is not being used. | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | C<Profile::Log> exports the C<PROFILE> constant into the environment, | 
| 49 |  |  |  |  |  |  | depending on how it is configured (see L</CONFIGURATION>).  This will | 
| 50 |  |  |  |  |  |  | be set if profiling has been selected for the given script or module. | 
| 51 |  |  |  |  |  |  | As this is exported as a "constant subroutine", using the module as | 
| 52 |  |  |  |  |  |  | per the above synopsis will not incur any penalty at all (except, in | 
| 53 |  |  |  |  |  |  | the case above, the allocation of one undef scalar and the | 
| 54 |  |  |  |  |  |  | compile-time inclusion of C<Profile::Log> itself; in long-running | 
| 55 |  |  |  |  |  |  | application servers, this is an extremely minor concern). | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | The timing information is logged in a way that suits syslog, and is | 
| 58 |  |  |  |  |  |  | casually easy to inspect; the above example, on profiling level 2, | 
| 59 |  |  |  |  |  |  | might log (though all on one line): | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | 0=12:34:56.123504; tot=0.504; minor: 0.020; m0:parallel=0.000; \ | 
| 62 |  |  |  |  |  |  | m0:thing1=0.450; m0:thing2=0.454; finish: 0.030 | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | The first item is the time that the C<Profile::Log> object was | 
| 65 |  |  |  |  |  |  | created.  The "tot" is the total length of time from when the object | 
| 66 |  |  |  |  |  |  | was created to the time that it was stopped (such as by asking for the | 
| 67 |  |  |  |  |  |  | log line). | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | On profiling level 1, you would instead get (assuming the same times | 
| 70 |  |  |  |  |  |  | for each component): | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | 0=12:34:56.123504; tot=0.504; m0:parallel=0.020; \ | 
| 73 |  |  |  |  |  |  | m0:thing1=0.450; m0:thing2=0.454 | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | =cut | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | package Profile::Log; | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 5 |  |  | 5 |  | 138366 | use strict; | 
|  | 5 |  |  |  |  | 13 |  | 
|  | 5 |  |  |  |  | 205 |  | 
| 80 | 5 |  |  | 5 |  | 25 | use warnings; | 
|  | 5 |  |  |  |  | 14 |  | 
|  | 5 |  |  |  |  | 167 |  | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 5 |  |  | 5 |  | 25 | use Carp; | 
|  | 5 |  |  |  |  | 16 |  | 
|  | 5 |  |  |  |  | 479 |  | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 5 |  |  | 5 |  | 5292 | use Time::HiRes qw(gettimeofday tv_interval); | 
|  | 5 |  |  |  |  | 8747 |  | 
|  | 5 |  |  |  |  | 26 |  | 
| 85 | 5 |  |  | 5 |  | 4769 | use YAML qw(LoadFile Dump); | 
|  | 5 |  |  |  |  | 59438 |  | 
|  | 5 |  |  |  |  | 380 |  | 
| 86 | 5 |  |  | 5 |  | 56 | use List::Util qw(reduce); | 
|  | 5 |  |  |  |  | 12 |  | 
|  | 5 |  |  |  |  | 596 |  | 
| 87 | 5 |  |  | 5 |  | 29 | use Scalar::Util qw(blessed); | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 2219 |  | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | our $VERSION = "0.02"; | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | =head1 EXPORTS | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | This module exports the C<PROFILE> constant to the caller's namespace. | 
| 94 |  |  |  |  |  |  | This will be set to 0 by default, or a number if configured in the | 
| 95 |  |  |  |  |  |  | per-user or environment specified configuration file.  See | 
| 96 |  |  |  |  |  |  | L</CONFIGURATION> for details. | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | If PROFILE is already defined as a subroutine or C<use constant> in | 
| 99 |  |  |  |  |  |  | the calling package, then that is not touched. | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | =cut | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | our $config; | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | sub import { | 
| 106 | 7 |  |  | 7 |  | 2331 | my $package = shift; | 
| 107 | 7 |  |  |  |  | 26 | my ($caller_package, $filename) = caller; | 
| 108 | 7 | 50 |  |  |  | 25 | if ( defined &{$caller_package."::PROFILE"} ) { | 
|  | 7 |  |  |  |  | 68 |  | 
| 109 | 0 | 0 |  |  |  | 0 | print STDERR (__PACKAGE__.": bypassing auto-config for " | 
| 110 |  |  |  |  |  |  | ."$filename ($caller_package) - PROFILE already" | 
| 111 |  |  |  |  |  |  | ." defined\n") | 
| 112 |  |  |  |  |  |  | if $ENV{PROFILE_LOG_DEBUG}; | 
| 113 |  |  |  |  |  |  | } else { | 
| 114 | 7 |  |  |  |  | 61 | $filename =~ s{.*/}{}; | 
| 115 | 7 |  | 66 |  |  | 40 | $config ||= do { | 
| 116 | 5 |  | 66 |  |  | 41 | my $config_file = ($ENV{PROFILE_LOG_CONFIG} || | 
| 117 |  |  |  |  |  |  | "$ENV{HOME}/.profilerc.yml"); | 
| 118 | 5 | 100 |  |  |  | 132 | if ( -e $config_file ) { | 
| 119 | 1 | 50 |  |  |  | 6 | print STDERR __PACKAGE__.": loading settings from $config_file\n" | 
| 120 |  |  |  |  |  |  | if $ENV{PROFILE_LOG_DEBUG}; | 
| 121 | 1 |  |  |  |  | 6 | LoadFile $config_file | 
| 122 |  |  |  |  |  |  | } else { | 
| 123 | 4 |  |  |  |  | 22 | {}; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  | }; | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | #print STDERR "Config is: ".Dump($config); | 
| 128 |  |  |  |  |  |  | #print STDERR "stuff is: ".Dump({caller_package => $caller_package, | 
| 129 |  |  |  |  |  |  | #filename => $filename }); | 
| 130 |  |  |  |  |  |  |  | 
| 131 | 7 |  |  |  |  | 24839 | my %import_config; | 
| 132 | 7 | 100 | 100 |  |  | 72 | if ( $config->{modules} and $config->{modules}{$caller_package} ) { | 
| 133 | 2 |  |  |  |  | 3 | %import_config = %{ $config->{modules}{$caller_package} }; | 
|  | 2 |  |  |  |  | 13 |  | 
| 134 |  |  |  |  |  |  | } | 
| 135 | 7 | 100 | 100 |  |  | 43 | if ( $config->{files} and $config->{files}{$filename} ) { | 
| 136 | 1 |  |  |  |  | 6 | %import_config = (%import_config, | 
| 137 | 1 |  |  |  |  | 4 | %{ $config->{files}{$filename} }); | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  |  | 
| 140 | 7 |  | 100 |  |  | 35 | my $profiling = $import_config{profile} || 0; | 
| 141 | 7 | 50 |  |  |  | 38 | print STDERR (__PACKAGE__.": profiling level for $filename " | 
| 142 |  |  |  |  |  |  | ."($caller_package) is $profiling\n") | 
| 143 |  |  |  |  |  |  | if $ENV{PROFILE_LOG_DEBUG}; | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 5 |  |  | 5 |  | 31 | no strict 'refs'; | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 4780 |  | 
| 146 | 7 |  |  |  |  | 4333 | *{$caller_package."::PROFILE"} = sub() { | 
| 147 | 0 |  |  | 0 |  | 0 | $profiling; | 
| 148 | 7 |  |  |  |  | 69 | }; | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | =head1 CONSTRUCTOR | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | my $timer = Profile::Log->new() if PROFILE; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | Mark beginning of a profiled section, by creating a new | 
| 158 |  |  |  |  |  |  | C<Profile::Log> object. | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | Normally, you don't pass any arguments to the C<Profile::Log-E<gt>new> | 
| 161 |  |  |  |  |  |  | constructor.  However, if you want to reconstruct a previous | 
| 162 |  |  |  |  |  |  | C<Profile::Log> object from a line from your logs, then you can pass | 
| 163 |  |  |  |  |  |  | that in instead. | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | my $loaded_timer = Profile::Log->new($log_line); | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | For now, you need to strip off any leading C<syslog> wrappers to the | 
| 168 |  |  |  |  |  |  | front of the string you pass in as C<$log_line>. | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | =cut | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | sub new { | 
| 173 | 10 |  |  | 10 | 0 | 48 | my $class = shift; | 
| 174 | 10 | 100 |  |  |  | 53 | if ( @_ ) { | 
| 175 | 8 |  |  |  |  | 14 | my $logline = shift; | 
| 176 | 8 |  |  |  |  | 9 | my ($state); | 
| 177 | 8 |  |  |  |  | 59 | my $self = bless { t => [], mc => 0 }, $class; | 
| 178 | 8 |  |  |  |  | 15 | my $time; | 
| 179 |  |  |  |  |  |  | my @marks; | 
| 180 | 8 |  |  |  |  | 58 | while ( $logline =~ m{\G([^=]+)=([^;]*)(?:;\s+)?}g ) { | 
| 181 | 32 |  |  |  |  | 78 | my ($k, $v) = ($1, $2); | 
| 182 | 32 | 100 | 100 |  |  | 205 | if ( !$state and $k ne "0" ) { | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 183 | 2 |  |  |  |  | 15 | $self->{tag}{$k}=$v; | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  | elsif ( !$state and $k eq "0" ) { | 
| 186 | 8 |  |  |  |  | 32 | $v =~ m{(\d+):(\d+):(\d+)\.(\d+)}; | 
| 187 | 8 |  |  | 16 |  | 90 | $self->{0} = to_local([ (reduce { $a * 60 + $b } $1, $2, $3), | 
|  | 16 |  |  |  |  | 79 |  | 
| 188 |  |  |  |  |  |  | $4 * 10**(6-length($4)) ]); | 
| 189 | 8 |  |  |  |  | 32 | $time = $self->{0}; | 
| 190 | 8 |  |  |  |  | 50 | $state = "tot"; | 
| 191 |  |  |  |  |  |  | } elsif ( $state eq "tot" ) { | 
| 192 | 8 |  |  |  |  | 39 | $self->{Z} = time_add($time,[0,$v*1e6]); | 
| 193 | 8 |  |  |  |  | 45 | $state = "times" | 
| 194 |  |  |  |  |  |  | } elsif ( $state eq "times" ) { | 
| 195 | 14 |  |  |  |  | 17 | push @{ $self->{t} }, $k, $v; | 
|  | 14 |  |  |  |  | 43 |  | 
| 196 | 14 | 100 |  |  |  | 42 | if ( $k =~ m{m(\d+):(.*)} ) { | 
| 197 | 4 |  |  |  |  | 10 | my ($m, $label) = ($1, $2); | 
| 198 | 4 | 100 |  |  |  | 12 | if ( $m >= $self->{mc} ) { | 
| 199 | 1 |  |  |  |  | 2 | $marks[$m] = $label; | 
| 200 | 1 |  |  |  |  | 5 | $time = $self->{m}{$label} | 
| 201 |  |  |  |  |  |  | = time_add($time, [0,$v*1e6]); | 
| 202 | 1 |  |  |  |  | 8 | $self->{mc}++; | 
| 203 |  |  |  |  |  |  | } else { | 
| 204 | 3 |  |  |  |  | 15 | $time = time_add($self->{m}{$marks[$m]}, | 
| 205 |  |  |  |  |  |  | [0,$v*1e6]); | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  | } else { | 
| 208 | 10 |  |  |  |  | 39 | $time = time_add($time,[0,$v*1e6]); | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  | } | 
| 212 | 8 |  |  |  |  | 30 | return $self; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  | else { | 
| 215 | 2 |  |  |  |  | 20 | my @now = gettimeofday; | 
| 216 | 2 |  |  |  |  | 23 | return bless { 0 => \@now, | 
| 217 |  |  |  |  |  |  | l => [@now], | 
| 218 |  |  |  |  |  |  | m => {}, | 
| 219 |  |  |  |  |  |  | mc => 0, | 
| 220 |  |  |  |  |  |  | t => [], | 
| 221 |  |  |  |  |  |  | }, $class; | 
| 222 |  |  |  |  |  |  | } | 
| 223 |  |  |  |  |  |  | } | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | =head2 ALTERNATE CONSTRUCTOR | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | It is also possible to feed in lines that came out of L<syslog(8)>. | 
| 228 |  |  |  |  |  |  | These are expected to be in the form: | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | Mon DD HH:MM:SS hostname ... | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | These must be fed into the alternate constructor | 
| 233 |  |  |  |  |  |  | C<-E<gt>new_from_syslog>.  Information present in the syslog line, | 
| 234 |  |  |  |  |  |  | such as the hostname, any process name (sans PID), and extra | 
| 235 |  |  |  |  |  |  | information leading up to the beginning of the C<-E<gt>logline()> part | 
| 236 |  |  |  |  |  |  | are put into tags. | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | =cut | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | sub new_from_syslog { | 
| 241 | 7 |  |  | 7 | 0 | 20432 | my $class = shift; | 
| 242 | 7 |  |  |  |  | 11 | my $line = shift; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 7 | 50 |  |  |  | 100 | my ($syslog_line, $logline) | 
| 245 |  |  |  |  |  |  | = ($line =~ m{^(.*?)(\S[^=\s]*=[^;]*;\s.*)$}) | 
| 246 |  |  |  |  |  |  | or return undef; | 
| 247 | 7 |  |  |  |  | 28 | my $self = $class->new($logline); | 
| 248 | 7 |  |  |  |  | 18 | $self->add_syslog($syslog_line); | 
| 249 | 7 |  |  |  |  | 22 | return $self; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | # this is a bit of a hack - a version of timelocal for syslog dates | 
| 253 |  |  |  |  |  |  | my $timelocal_ready; | 
| 254 |  |  |  |  |  |  | our %mon; | 
| 255 |  |  |  |  |  |  | our ($y,$m,$d); | 
| 256 |  |  |  |  |  |  | sub syslog_timelocal { | 
| 257 | 7 |  |  | 7 | 0 | 10 | my $syslog_date = shift; | 
| 258 | 7 |  |  |  |  | 39 | my ($sec, $min, $hour, $mday, $monname) = reverse | 
| 259 |  |  |  |  |  |  | ( $syslog_date =~ m{^(\w+) \s+ (\d+) \s+ (\d+):(\d+):(\d+)}x ); | 
| 260 |  |  |  |  |  |  |  | 
| 261 | 7 | 100 |  |  |  | 17 | unless ( $timelocal_ready ) { | 
| 262 | 5 |  |  | 5 |  | 32 | no strict 'refs'; | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 8635 |  | 
| 263 | 1 |  |  |  |  | 11 | require I18N::Langinfo; | 
| 264 | 1 |  |  |  |  | 556226 | require Time::Local; | 
| 265 | 1 |  |  |  |  | 2092 | for my $mon ( 1..12 ) { | 
| 266 | 12 |  |  |  |  | 67 | my $mname = lc(&I18N::Langinfo::langinfo | 
| 267 | 12 |  |  |  |  | 18 | (&{"I18N::Langinfo::ABMON_$mon"})); | 
| 268 | 12 |  |  |  |  | 36 | $mon{$mname} = $mon-1; | 
| 269 |  |  |  |  |  |  | } | 
| 270 | 1 |  |  |  |  | 7 | ($y, $m, $d) = (localtime(time()))[5,4,3]; | 
| 271 | 1 |  |  |  |  | 27 | $timelocal_ready = 1; | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  | # if the month is greater than today, assume it's last year. | 
| 274 | 7 |  |  |  |  | 16 | my $mon = $mon{lc($monname)}; | 
| 275 |  |  |  |  |  |  | #kill 2, $$; | 
| 276 | 7 | 50 |  |  |  | 14 | my $year = ($mon > $m) ? $y-1 : $y; | 
| 277 | 7 |  |  |  |  | 22 | return Time::Local::timelocal($sec, $min, $hour, | 
| 278 |  |  |  |  |  |  | $mday, $mon, $year); | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | sub add_syslog { | 
| 282 | 7 |  |  | 7 | 0 | 8 | my $self = shift; | 
| 283 | 7 |  |  |  |  | 9 | my $syslog_header = shift; | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 7 | 50 |  |  |  | 67 | if ( my ($syslog_date, $hostname, $process, $comment) | 
| 286 |  |  |  |  |  |  | = ( $syslog_header =~ | 
| 287 |  |  |  |  |  |  | m{^(\w+ \s+ \d+ \s+ \d+:\d+:\d+) \s+ # syslog date | 
| 288 |  |  |  |  |  |  | (\w+) \s+                         # hostname | 
| 289 |  |  |  |  |  |  | (?: (\S+?) (?:\[\d+\])? : \s* )?  # process name, PID | 
| 290 |  |  |  |  |  |  | (?: (\S.*?) \s* )? $                   # extra comment | 
| 291 |  |  |  |  |  |  | }x )) { | 
| 292 |  |  |  |  |  |  |  | 
| 293 | 7 |  |  |  |  | 20 | $self->tag("hostname" => $hostname); | 
| 294 | 7 |  |  |  |  | 17 | $self->tag("process" => $process); | 
| 295 | 7 | 50 |  |  |  | 12 | $self->tag("comment" => $comment) if $comment; | 
| 296 |  |  |  |  |  |  |  | 
| 297 | 7 | 50 |  |  |  | 21 | if ( $self->{0}[0] < 7 * 86400 ) { | 
| 298 |  |  |  |  |  |  | # we set the top half of the 0 to the month and day *not later | 
| 299 |  |  |  |  |  |  | # than* the syslog time. | 
| 300 | 7 |  |  |  |  | 21 | my $syslog_localtime = syslog_timelocal($syslog_date); | 
| 301 | 7 |  |  |  |  | 438 | my $self_time = $self->{0}[0] % 86400; | 
| 302 |  |  |  |  |  |  |  | 
| 303 | 7 |  |  |  |  | 120 | my @local_syslog = localtime($syslog_localtime); | 
| 304 | 7 |  |  |  |  | 119 | my @local_self   = localtime($self_time); | 
| 305 |  |  |  |  |  |  |  | 
| 306 | 7 |  |  |  |  | 26 | my $proposed_time = Time::Local::timelocal | 
| 307 |  |  |  |  |  |  | (@local_self[0,1,2],@local_syslog[3,4,5]); | 
| 308 |  |  |  |  |  |  |  | 
| 309 | 7 | 100 |  |  |  | 310 | if ( $proposed_time > $syslog_localtime ) { | 
| 310 |  |  |  |  |  |  | # must be the previous day | 
| 311 | 3 |  |  |  |  | 4 | $syslog_localtime -= 86400; | 
| 312 | 3 |  |  |  |  | 52 | @local_syslog = localtime($syslog_localtime); | 
| 313 | 3 |  |  |  |  | 10 | $proposed_time = Time::Local::timelocal | 
| 314 |  |  |  |  |  |  | (@local_self[0,1,2],@local_syslog[3,4,5]); | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  |  | 
| 317 | 7 |  |  |  |  | 134 | my $old_time = $self->{0}[0]; | 
| 318 | 7 |  |  |  |  | 19 | my ($old_diff) = ($self->{Z}[0] - $self->{0}[0]) % 86400; | 
| 319 | 7 |  |  |  |  | 13 | $self->{0}[0] = $proposed_time; | 
| 320 | 7 |  |  |  |  | 11 | $self->{Z}[0] = $proposed_time + $old_diff; | 
| 321 | 7 | 50 |  |  |  | 36 | if ( $self->{m} ) { | 
| 322 | 0 |  |  |  |  | 0 | my $to_add = ($proposed_time - $old_time); | 
| 323 | 0 |  |  |  |  | 0 | while ( my ($mark, $t) = each %{$self->{m}} ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 324 | 0 |  |  |  |  | 0 | $t->[0] += $to_add; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  | } | 
| 327 |  |  |  |  |  |  | } | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | my $tz_offset; | 
| 332 |  |  |  |  |  |  | sub to_local { | 
| 333 | 8 |  |  | 8 | 0 | 11 | my $t = shift; | 
| 334 |  |  |  |  |  |  | # FIXME - non-hour aligned timezones like NZ-CHAT | 
| 335 | 8 |  | 33 |  |  | 249 | $t->[0] -= ($tz_offset ||= ((localtime(0))[2])) * 3600; | 
| 336 | 8 | 50 |  |  |  | 30 | $t->[0] %= 86400 if $t->[0] < 0; | 
| 337 | 8 |  |  |  |  | 59 | $t; | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | sub time_add { | 
| 341 | 22 |  |  | 22 | 0 | 30 | my $t1 = shift; | 
| 342 | 22 |  |  |  |  | 31 | my $t2 = shift; | 
| 343 | 22 |  |  |  |  | 44 | my $usec = $t1->[1] + $t2->[1]; | 
| 344 | 22 |  |  |  |  | 141 | return [ $t1->[0] + $t2->[0] + int($usec / 1e6), | 
| 345 |  |  |  |  |  |  | $usec % 1e6 ]; | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | =head1 OBJECT METHODS | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | =head2 TIMING METHODS | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | =over | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =item C<-E<gt>did($event, [$mark])> | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | Indicate that the time elapsed since the timer was constructed or the | 
| 357 |  |  |  |  |  |  | last time C<-E<gt>did()> or C<-E<gt>mark()> was called to the current | 
| 358 |  |  |  |  |  |  | time was spent doing "C<$event>".  If you specify a C<$mark> (see | 
| 359 |  |  |  |  |  |  | below), then all the time back from when you created that mark is | 
| 360 |  |  |  |  |  |  | considered to have been spent doing C<$event>. | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | =cut | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | sub did { | 
| 365 | 7 |  |  | 7 | 1 | 501895 | my $self = shift; | 
| 366 | 7 |  |  |  |  | 43 | my $event = shift; | 
| 367 | 7 | 50 |  |  |  | 72 | $event !~ m{\s} or croak "event must not contain whitespace"; | 
| 368 | 7 |  |  |  |  | 15 | my $t0; | 
| 369 | 7 | 100 |  |  |  | 33 | if ( @_ ) { | 
| 370 | 3 |  |  |  |  | 13 | my $mark = shift; | 
| 371 | 3 |  |  |  |  | 17 | $t0 = $self->{m}{$mark}; | 
| 372 | 3 |  |  |  |  | 23 | $event = "m$t0->[2]:$event"; | 
| 373 |  |  |  |  |  |  | } else { | 
| 374 | 4 |  |  |  |  | 49 | $t0 = $self->{l}; | 
| 375 |  |  |  |  |  |  | } | 
| 376 | 7 |  |  |  |  | 60 | my $now = [gettimeofday]; | 
| 377 | 7 |  |  |  |  | 15 | push @{ $self->{t} }, ($event => tv_interval($t0, $now)); | 
|  | 7 |  |  |  |  | 48 |  | 
| 378 | 7 |  |  |  |  | 147 | $self->{l} = $now; | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | =item C<-E<gt>mark($mark)> | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | Set a time mark for later back-reference.  Typically you would call | 
| 384 |  |  |  |  |  |  | this just before doing something that involves running things in | 
| 385 |  |  |  |  |  |  | parallel, and call C<-E<gt>did()> above with the optional C<$mark> | 
| 386 |  |  |  |  |  |  | parameter when each independent task completes. | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | =cut | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | sub mark { | 
| 391 | 1 |  |  | 1 | 1 | 8 | my $self = shift; | 
| 392 | 1 |  |  |  |  | 1 | my $mark = shift; | 
| 393 | 1 | 50 |  |  |  | 9 | $mark !~ m{\s} or croak "mark must not contain whitespace"; | 
| 394 |  |  |  |  |  |  | # this is a touch naughty - hang extra information on the nice | 
| 395 |  |  |  |  |  |  | # handy array there (Time::HiRes doesn't care) | 
| 396 | 1 |  |  |  |  | 2 | my $m; | 
| 397 | 1 |  |  |  |  | 10 | $self->{m}{$mark}=[gettimeofday, ($m=$self->{mc}++)]; | 
| 398 | 1 |  |  |  |  | 12 | $self->did("m$m:$mark"); | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | =item C<-E<gt>logline()> | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | Returns the timing information in a summarised format, suitable for | 
| 404 |  |  |  |  |  |  | sending to C<syslog> or something similar. | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | This method automatically stops the timer the first time it is called. | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | =back | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | =cut | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | sub logline { | 
| 413 | 4 |  |  | 4 | 1 | 124945 | my $self = shift; | 
| 414 | 4 |  | 100 |  |  | 77 | my $final = ($self->{Z}||=[gettimeofday]); | 
| 415 |  |  |  |  |  |  |  | 
| 416 | 4 |  |  |  |  | 9 | my @ts; | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 4 | 100 |  |  |  | 18 | @ts = map { "$_=$self->{tag}{$_}" } sort keys %{ $self->{tag} } | 
|  | 6 |  |  |  |  | 52 |  | 
|  | 3 |  |  |  |  | 21 |  | 
| 419 |  |  |  |  |  |  | if $self->{tag}; | 
| 420 |  |  |  |  |  |  |  | 
| 421 | 4 |  |  |  |  | 24 | push @ts, ("0=".$self->getTimeStamp($self->{0}), | 
| 422 |  |  |  |  |  |  | "tot=".$self->getInterval($self->{0}, $final)); | 
| 423 | 4 |  |  |  |  | 14 | my $l = $self->{t}; | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | # collect rounding errors along the way, fudge onto the next value | 
| 426 |  |  |  |  |  |  | # so they don't accumulate.  ie, if one task takes 0.4074s, and | 
| 427 |  |  |  |  |  |  | # the next 0.0011s, they will be displayed as 0.407 and 0.002 | 
| 428 | 4 |  |  |  |  | 6 | my $re = 0; | 
| 429 | 4 |  |  |  |  | 27 | for ( my $i = 0; $i < $#$l; $i += 2 ) { | 
| 430 | 23 |  |  |  |  | 43 | my $delta = $l->[$i+1] + $re; | 
| 431 | 23 |  |  |  |  | 22 | my $ms; | 
| 432 |  |  |  |  |  |  | # very short deltas might end up negative - so add the error | 
| 433 |  |  |  |  |  |  | # to the next value instead. | 
| 434 | 23 | 50 |  |  |  | 48 | if ( $delta < 0 ) { | 
| 435 | 0 |  |  |  |  | 0 | ($ms, my $extra) = getInterval($l->[$i+1]); | 
| 436 | 0 |  |  |  |  | 0 | $re += $extra; | 
| 437 |  |  |  |  |  |  | } else { | 
| 438 | 23 |  |  |  |  | 38 | ($ms, $re) = getInterval($delta); | 
| 439 |  |  |  |  |  |  | } | 
| 440 | 23 |  |  |  |  | 138 | push @ts, "$l->[$i]=$ms"; | 
| 441 |  |  |  |  |  |  | } | 
| 442 | 4 |  |  |  |  | 35 | return join ("; ", @ts); | 
| 443 |  |  |  |  |  |  | } | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | =head2 TRACKING AND INSPECTING METHODS | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | These methods are about making sure custom details about what is being | 
| 448 |  |  |  |  |  |  | logged can easily be logged with the profiling information. | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | For instance, in application servers it is often useful to log the | 
| 451 |  |  |  |  |  |  | type of transaction being processed, or the URL.  In multi-tier | 
| 452 |  |  |  |  |  |  | systems, you need to log a unique identifier with each request if you | 
| 453 |  |  |  |  |  |  | are to correlate individual timings through the system. | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | Also, these methods cover getting useful information out of the object | 
| 456 |  |  |  |  |  |  | once you have read it in from a log file. | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  | =over | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | =item C<-E<gt>tag($tag, [$value])> | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | Set (2 argument version) or get (1 argument version) an arbitrary tag. | 
| 463 |  |  |  |  |  |  | The C<$tag> name should not contain a semicolon or equals sign, and | 
| 464 |  |  |  |  |  |  | the C<$value> must not contain any semicolons.  This is not enforced. | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | =item C<-E<gt>tags> | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | Returns a list of tags of this profile, in no particular order. | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | =cut | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | sub tag { | 
| 473 | 17 |  |  | 17 | 1 | 5098 | my $self = shift; | 
| 474 | 17 |  |  |  |  | 25 | my $title = shift; | 
| 475 | 17 | 50 |  |  |  | 53 | $title !~ m{[\s=;]} | 
| 476 |  |  |  |  |  |  | or croak("tag name must not contain whitespace, equals symbol" | 
| 477 |  |  |  |  |  |  | ." or semicolon"); | 
| 478 | 17 | 100 |  |  |  | 41 | if ( @_ ) { | 
| 479 | 16 |  |  |  |  | 16 | my $value = shift; | 
| 480 | 16 |  |  |  |  | 66 | $self->{tag}{$title}=$value; | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  | else { | 
| 483 | 1 |  |  |  |  | 10 | return $self->{tag}{$title}; | 
| 484 |  |  |  |  |  |  | } | 
| 485 |  |  |  |  |  |  | } | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | sub tags { | 
| 488 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 489 |  |  |  |  |  |  |  | 
| 490 | 0 |  |  |  |  | 0 | return keys %{ $self->{tag} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 491 |  |  |  |  |  |  | } | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | =item C<-E<gt>zero> | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | Return the number of seconds between midnight (UTC) and the time this | 
| 496 |  |  |  |  |  |  | profiling object was created. | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | In list context, returns a Unix epoch time and a number of | 
| 499 |  |  |  |  |  |  | microseconds, C<Time::HiRes> style. | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | =cut | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | sub zero { | 
| 504 | 2 |  |  | 2 | 1 | 5 | my $self = shift; | 
| 505 | 2 |  |  |  |  | 20 | return $self->{0}[0] % 86400 + $self->{0}[1] / 1e6; | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | sub zero_t { | 
| 509 | 7 |  |  | 7 | 0 | 9520 | my $self = shift; | 
| 510 | 7 |  |  |  |  | 8 | return @{ $self->{0} } | 
|  | 7 |  |  |  |  | 24 |  | 
| 511 |  |  |  |  |  |  | } | 
| 512 |  |  |  |  |  |  |  | 
| 513 |  |  |  |  |  |  | =item C<-E<gt>diff($t2)> | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | Returns the difference between two times, in seconds.  If the dates | 
| 516 |  |  |  |  |  |  | are fully specified, then it will return an asolute (floating point) | 
| 517 |  |  |  |  |  |  | number of seconds. | 
| 518 |  |  |  |  |  |  |  | 
| 519 |  |  |  |  |  |  | This method is available as the overloaded C<cmp> operator, for easy | 
| 520 |  |  |  |  |  |  | use with C<sort>. | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | =cut | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | sub diff { | 
| 525 | 0 |  |  | 0 | 1 | 0 | my $a = shift; | 
| 526 | 0 |  |  |  |  | 0 | my $b = shift; | 
| 527 |  |  |  |  |  |  |  | 
| 528 | 0 |  |  |  |  | 0 | my @a = $a->zero; | 
| 529 | 0 |  |  |  |  | 0 | my @b = $b->zero; | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | # Profile::Log objects don't need fully qualified dates; if the | 
| 532 |  |  |  |  |  |  | # date value is too small, then compare by seconds only, in the | 
| 533 |  |  |  |  |  |  | # closest half of the day. | 
| 534 | 0 | 0 | 0 |  |  | 0 | if ( $a[0] > 10*86400 and $b[0] > 10*86400 ) { | 
| 535 | 0 |  |  |  |  | 0 | return $a[0] - $b[0] + ( $a[0] - $b[0] ) / 1e6; | 
| 536 |  |  |  |  |  |  | } else { | 
| 537 | 0 |  |  |  |  | 0 | my $diff = ( ($a[0] - $b[0]) % 86400 | 
| 538 |  |  |  |  |  |  | + ( $a[0] - $b[0] ) / 1e6); | 
| 539 | 0 | 0 |  |  |  | 0 | $diff += 86400 if $diff < -86400/2; | 
| 540 | 0 | 0 |  |  |  | 0 | $diff -= 86400 if $diff >  86400/2; | 
| 541 | 0 |  |  |  |  | 0 | return $diff; | 
| 542 |  |  |  |  |  |  | } | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | use overload | 
| 546 | 5 |  |  |  |  | 47 | 'cmp' => \&diff, | 
| 547 | 5 |  |  | 5 |  | 157 | 'fallback' => 1; | 
|  | 5 |  |  |  |  | 10 |  | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | =item C<-E<gt>end> | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | Return the number of seconds since midnight (UTC) and the time this | 
| 552 |  |  |  |  |  |  | profiling object's clock was stopped. | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | =cut | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | sub end { | 
| 557 | 2 |  |  | 2 | 1 | 6 | my $self = shift; | 
| 558 | 2 |  | 50 |  |  | 8 | my $z = $self->{Z}||=[gettimeofday]; | 
| 559 | 2 |  |  |  |  | 10 | return $z->[0] % 86400 + $z->[1] / 1e6; | 
| 560 |  |  |  |  |  |  | } | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | sub end_t { | 
| 563 | 7 |  |  | 7 | 0 | 25 | my $self = shift; | 
| 564 | 7 |  | 50 |  |  | 22 | my $z = $self->{Z}||=[gettimeofday]; | 
| 565 | 7 |  |  |  |  | 18 | return @$z; | 
| 566 |  |  |  |  |  |  | } | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | =item C<-E<gt>marks> | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | Returns a list of marks as an array.  This will always include "0", | 
| 571 |  |  |  |  |  |  | the starting mark. | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | =cut | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  | sub marks { | 
| 576 | 1 |  |  | 1 | 1 | 3 | my $self = shift; | 
| 577 | 0 | 50 |  |  |  | 0 | my @marks = (0, sort { tv_interval($self->{m}{$a}, $self->{m}{$b}) | 
|  | 1 |  |  |  |  | 7 |  | 
| 578 | 1 |  |  |  |  | 2 | } keys %{ $self->{m}||{} }); | 
| 579 | 1 | 50 |  |  |  | 12 | wantarray ? @marks : \@marks; | 
| 580 |  |  |  |  |  |  | } | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | =item C<-E<gt>iter> | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | Returns an iterator that iterates over every delta, and mark, in the | 
| 585 |  |  |  |  |  |  | Profiler object. | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | The iterator responds to these methods; note that these are not method | 
| 588 |  |  |  |  |  |  | calls: | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  | =over | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | =item C<$iter-E<gt>("next")> | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | iterate.  returns a true value unless there is nowhere to iterate to. | 
| 595 |  |  |  |  |  |  |  | 
| 596 |  |  |  |  |  |  | =item C<$iter-E<gt>("start")> | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | Returns the offset from time 0 that this delta started in fractional | 
| 599 |  |  |  |  |  |  | seconds. | 
| 600 |  |  |  |  |  |  |  | 
| 601 |  |  |  |  |  |  | =item C<$iter-E<gt>("length")> | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | Returns the length of this delta in (fractional) seconds. | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | =item C<$iter-E<gt>("name")> | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | Returns the name of this delta, including the mark identifier (C<m> | 
| 608 |  |  |  |  |  |  | followed by a number and a colon, such as "C<m0:>"). | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | =back | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | =cut | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | sub iter { | 
| 615 | 3 |  |  | 3 | 1 | 972 | my $self = shift; | 
| 616 |  |  |  |  |  |  |  | 
| 617 | 3 |  |  |  |  | 5 | my $i = -1; | 
| 618 |  |  |  |  |  |  |  | 
| 619 | 3 |  |  |  |  | 5 | my $cue = 0; | 
| 620 | 3 |  |  |  |  | 6 | my @m = (); | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | my $it = sub { | 
| 623 | 24 |  |  |  |  | 103 | $cue += $self->{t}[2*$i+1] | 
| 624 | 27 | 100 | 100 | 27 |  | 72 | unless $i == -1 or $i*2+1 > ($#{$self->{t}}); | 
| 625 | 27 |  |  |  |  | 31 | $i++; | 
| 626 | 27 | 100 | 100 |  |  | 37 | if ( $i*2 <= ($#{$self->{t}}) | 
|  | 27 |  |  |  |  | 151 |  | 
| 627 |  |  |  |  |  |  | and $self->{t}[2*$i] =~ m/^m(\d+)/ ) { | 
| 628 | 12 | 100 |  |  |  | 33 | if ( exists $m[$1] ) { | 
| 629 | 9 |  |  |  |  | 19 | $cue = $m[$1]; | 
| 630 |  |  |  |  |  |  | } else { | 
| 631 | 3 |  |  |  |  | 9 | $m[$1] = $cue; | 
| 632 |  |  |  |  |  |  | } | 
| 633 |  |  |  |  |  |  | } | 
| 634 | 3 |  |  |  |  | 22 | }; | 
| 635 |  |  |  |  |  |  |  | 
| 636 |  |  |  |  |  |  | my $iter = sub { | 
| 637 | 79 |  |  | 79 |  | 113 | my $method = shift; | 
| 638 | 79 | 100 |  |  |  | 250 | if ( $method eq "next" ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 639 | 27 |  |  |  |  | 77 | $it->(); | 
| 640 | 27 | 100 |  |  |  | 285 | if ( 2*$i < $#{$self->{t}} ) { | 
|  | 27 | 100 |  |  |  | 69 |  | 
|  | 6 |  |  |  |  | 52 |  | 
| 641 | 21 |  |  |  |  | 138 | return $self->{t}[2*$i]; | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  | elsif ( 2*$i == $#{$self->{t}}+1 ) { | 
| 644 | 3 |  |  |  |  | 15 | return "Z"; | 
| 645 |  |  |  |  |  |  | } | 
| 646 |  |  |  |  |  |  | else { | 
| 647 |  |  |  |  |  |  | } | 
| 648 |  |  |  |  |  |  | } | 
| 649 |  |  |  |  |  |  | elsif ( $method eq "start" ) { | 
| 650 | 9 |  |  |  |  | 25 | return $cue; | 
| 651 |  |  |  |  |  |  | } | 
| 652 |  |  |  |  |  |  | elsif ( $method eq "length" ) { | 
| 653 | 9 | 100 |  |  |  | 25 | return 0 if $i == -1; | 
| 654 | 8 |  |  |  |  | 29 | return scalar getInterval(($self->end - $self->zero) - $cue) | 
| 655 | 8 | 100 |  |  |  | 11 | if 2*$i == $#{$self->{t}}+1; | 
| 656 | 7 |  |  |  |  | 51 | return $self->{t}[2*$i+1]+0; | 
| 657 |  |  |  |  |  |  | } | 
| 658 |  |  |  |  |  |  | elsif ( $method eq "name" ) { | 
| 659 | 34 | 100 |  |  |  | 68 | return 0 if $i == -1; | 
| 660 | 32 | 100 |  |  |  | 36 | return "Z" if 2*$i == $#{$self->{t}}+1; | 
|  | 32 |  |  |  |  | 103 |  | 
| 661 | 28 |  |  |  |  | 145 | return $self->{t}[2*$i]; | 
| 662 |  |  |  |  |  |  | } | 
| 663 | 3 |  |  |  |  | 16 | }; | 
| 664 | 3 |  |  |  |  | 8 | return $iter; | 
| 665 |  |  |  |  |  |  | } | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | =item C<-E<gt>mark_iter([$mark])> | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  | Returns an iterator that iterates exactly once over every delta that | 
| 670 |  |  |  |  |  |  | was timed relative to C<$mark>. | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  | If you don't pass a mark in, it iterates only over items that weren't | 
| 673 |  |  |  |  |  |  | timed relative to C<$mark>. | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | =cut | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | sub mark_iter { | 
| 678 | 2 |  |  | 2 | 1 | 711 | my $self = shift; | 
| 679 | 2 |  | 100 |  |  | 11 | my $mark = shift || 0; | 
| 680 | 2 |  |  |  |  | 3 | my ($t0, $m); | 
| 681 | 2 | 100 |  |  |  | 8 | if ( $mark ne "0" ) { | 
| 682 | 1 |  |  |  |  | 4 | ($m) = (map { m/^m(\d+):/; $1 } | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 48 |  | 
| 683 |  |  |  |  |  |  | grep /^m\d+:\Q$mark\E/, | 
| 684 | 1 |  |  |  |  | 3 | @{ $self->{t} }); | 
| 685 | 0 | 0 |  |  |  | 0 | croak("no such mark '$mark' in Profile::Log object (marks: " | 
| 686 | 1 | 50 |  |  |  | 5 | .join(" ",keys %{ $self->{m}||{} }).")") | 
| 687 |  |  |  |  |  |  | unless defined $m; | 
| 688 |  |  |  |  |  |  | } | 
| 689 |  |  |  |  |  |  |  | 
| 690 | 2 |  |  |  |  | 5 | my $all_iter = $self->iter(); | 
| 691 |  |  |  |  |  |  |  | 
| 692 |  |  |  |  |  |  | my $iter = sub { | 
| 693 | 37 |  |  | 37 |  | 105 | my $method = shift; | 
| 694 | 37 | 100 |  |  |  | 100 | if ( $method eq "next" ) { | 
|  |  | 100 |  |  |  |  |  | 
| 695 | 10 |  |  |  |  | 12 | my $x; | 
| 696 | 10 |  | 100 |  |  | 13 | do { $x = $all_iter->("next") } until | 
|  | 18 |  | 100 |  |  | 32 |  | 
|  |  |  | 66 |  |  |  |  | 
| 697 |  |  |  |  |  |  | (!$x or | 
| 698 |  |  |  |  |  |  | !defined($m) && $all_iter->("name") !~ m/^m\d+:/ | 
| 699 |  |  |  |  |  |  | or | 
| 700 |  |  |  |  |  |  | defined($m) && $all_iter->("name") =~ m/^m(\d+):/); | 
| 701 | 10 |  |  |  |  | 27 | return $x; | 
| 702 |  |  |  |  |  |  | } | 
| 703 |  |  |  |  |  |  | elsif ( $method eq "name" ) { | 
| 704 | 9 |  |  |  |  | 28 | my $name = $all_iter->("name"); | 
| 705 | 9 |  |  |  |  | 22 | $name =~ s{m\d+:}{}; | 
| 706 | 9 |  |  |  |  | 29 | return $name; | 
| 707 |  |  |  |  |  |  | } | 
| 708 |  |  |  |  |  |  | else { | 
| 709 | 18 |  |  |  |  | 28 | return $all_iter->($method); | 
| 710 |  |  |  |  |  |  | } | 
| 711 | 2 |  |  |  |  | 10 | }; | 
| 712 |  |  |  |  |  |  |  | 
| 713 | 2 | 100 |  |  |  | 7 | $iter->("next") if defined($m); | 
| 714 |  |  |  |  |  |  |  | 
| 715 | 2 |  |  |  |  | 8 | return $iter; | 
| 716 |  |  |  |  |  |  | } | 
| 717 |  |  |  |  |  |  |  | 
| 718 |  |  |  |  |  |  |  | 
| 719 |  |  |  |  |  |  | =back | 
| 720 |  |  |  |  |  |  |  | 
| 721 |  |  |  |  |  |  | =head2 TIMESTAMP FORMATTING | 
| 722 |  |  |  |  |  |  |  | 
| 723 |  |  |  |  |  |  | If you don't like the decisions I've made about only displaying | 
| 724 |  |  |  |  |  |  | milliseconds in the log, then you may sub-class C<Profile::Log> and | 
| 725 |  |  |  |  |  |  | provide these functions instead.  These are called as object methods, | 
| 726 |  |  |  |  |  |  | though the object itself is not used to compute the result. | 
| 727 |  |  |  |  |  |  |  | 
| 728 |  |  |  |  |  |  | =over | 
| 729 |  |  |  |  |  |  |  | 
| 730 |  |  |  |  |  |  | =item C<-E<gt>getTimeStamp([$sec, $usec])> | 
| 731 |  |  |  |  |  |  |  | 
| 732 |  |  |  |  |  |  | Formats an absolute timestamp from a C<Time::HiRes> array.  Defaults | 
| 733 |  |  |  |  |  |  | to formatting as: C<HH:MM:SS.SSS> | 
| 734 |  |  |  |  |  |  |  | 
| 735 |  |  |  |  |  |  | =cut | 
| 736 |  |  |  |  |  |  |  | 
| 737 |  |  |  |  |  |  | sub getTimeStamp { | 
| 738 | 4 | 50 |  | 4 | 1 | 29 | shift if blessed $_[0]; | 
| 739 | 4 |  | 50 |  |  | 19 | my $when = shift || [ gettimeofday ]; | 
| 740 | 4 |  |  |  |  | 10 | my ($endSeconds, $endMicroseconds) = @$when; | 
| 741 | 4 |  |  |  |  | 499 | my ($sec, $min, $hour) = localtime($endSeconds); | 
| 742 |  |  |  |  |  |  |  | 
| 743 | 4 |  |  |  |  | 61 | return sprintf "%.2d:%.2d:%.2d.%.3d", $hour,$min,$sec, | 
| 744 |  |  |  |  |  |  | ($endMicroseconds/1e3); | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  |  | 
| 747 |  |  |  |  |  |  | =item C<-E<gt>getInterval($sec | @tv_interval )> | 
| 748 |  |  |  |  |  |  |  | 
| 749 |  |  |  |  |  |  | Formats an interval.  This function accepts either a floating point | 
| 750 |  |  |  |  |  |  | number of seconds, or arguments as accepted by | 
| 751 |  |  |  |  |  |  | C<Time::HiRes::tv_interval>. | 
| 752 |  |  |  |  |  |  |  | 
| 753 |  |  |  |  |  |  | The function returns a string in scalar context, but in list context | 
| 754 |  |  |  |  |  |  | returns any rounding error also, in floating point seconds. | 
| 755 |  |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  | =back | 
| 757 |  |  |  |  |  |  |  | 
| 758 |  |  |  |  |  |  | =cut | 
| 759 |  |  |  |  |  |  |  | 
| 760 |  |  |  |  |  |  | sub getInterval { | 
| 761 | 29 | 100 |  | 29 | 1 | 140 | shift if blessed $_[0]; | 
| 762 | 29 |  |  |  |  | 28 | my $elapsed; | 
| 763 | 29 | 100 | 66 |  |  | 126 | if ( @_ == 2 or ref $_[0] ) { | 
| 764 | 4 |  |  |  |  | 23 | $elapsed = tv_interval(@_); | 
| 765 |  |  |  |  |  |  | } else { | 
| 766 | 25 |  |  |  |  | 36 | $elapsed = shift; | 
| 767 |  |  |  |  |  |  | } | 
| 768 |  |  |  |  |  |  | # only return milliseconds. | 
| 769 | 29 |  |  |  |  | 196 | my $fmt = sprintf("%.3f", $elapsed); | 
| 770 | 29 | 100 |  |  |  | 156 | return ( wantarray ? ($fmt, ($elapsed - $fmt)) : $fmt ); | 
| 771 |  |  |  |  |  |  | } | 
| 772 |  |  |  |  |  |  |  | 
| 773 |  |  |  |  |  |  | =head1 AUTHOR AND LICENSE | 
| 774 |  |  |  |  |  |  |  | 
| 775 |  |  |  |  |  |  | Designed and built by Sam Vilain, L<samv@cpan.org>, brought to you | 
| 776 |  |  |  |  |  |  | courtesy of Catalyst IT Ltd - L<http://www.catalyst.net.nz/>. | 
| 777 |  |  |  |  |  |  |  | 
| 778 |  |  |  |  |  |  | All code and documentation copyright © 2005, Catalyst IT Ltd.  All | 
| 779 |  |  |  |  |  |  | Rights Reserved.  This module is free software; you may use it and/or | 
| 780 |  |  |  |  |  |  | redistribute it under the same terms as Perl itself. | 
| 781 |  |  |  |  |  |  |  | 
| 782 |  |  |  |  |  |  | =cut | 
| 783 |  |  |  |  |  |  |  | 
| 784 |  |  |  |  |  |  | 1; |