File Coverage

blib/lib/Profile/Log.pm
Criterion Covered Total %
statement 255 278 91.7
branch 86 116 74.1
condition 41 54 75.9
subroutine 34 37 91.8
pod 13 21 61.9
total 429 506 84.7


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;