File Coverage

blib/lib/Log/Timer.pm
Criterion Covered Total %
statement 61 61 100.0
branch 6 8 75.0
condition 13 17 76.4
subroutine 14 14 100.0
pod 4 4 100.0
total 98 104 94.2


line stmt bran cond sub pod time code
1             package Log::Timer;
2              
3 1     1   108815 use strict;
  1         9  
  1         23  
4 1     1   3 use warnings;
  1         2  
  1         20  
5 1     1   5 use Exporter 'import';
  1         1  
  1         61  
6             our $VERSION = '1.0.0'; # VERSION
7             # ABSTRACT: track nested timing information
8              
9              
10             our @EXPORT = our @EXPORT_OK = qw/ timer subroutine_timer /;
11              
12 1     1   299 use Guard::Timer;
  1         2  
  1         40  
13 1     1   435 use List::AllUtils qw/ max /;
  1         13687  
  1         67  
14 1     1   399 use Statistics::Descriptive;
  1         22363  
  1         31  
15 1     1   8 use Log::Any qw/ $log /;
  1         1  
  1         33  
16              
17              
18             my $name__context = {
19             "" => {
20             indent => 1,
21             },
22             };
23             my $message_stats = {};
24              
25              
26             sub timer {
27 5     5 1 5017 my ($message, $options) = @_;
28 5   100     19 $options //= {};
29              
30 5   100     15 my $log_prefix = $options->{prefix} // "";
31              
32             # If you run async code, you need to distinguish the context this
33             # is running in. Human readable.
34 5   100     15 my $context_name = $options->{context} //= "";
35              
36             # Create context at the same level we're at, assuming we're always
37             # branching off the default "process wide" context
38             my $context = $name__context->{ $context_name } //= {
39             start_indent => $name__context->{ "" }->{indent},
40 5   100     13 %{ $name__context->{ "" } },
  1         6  
41             };
42              
43 5   50     12 my $indent_increase = $options->{indent_increase} // 4;
44              
45 5         6 $context->{indent} += $indent_increase;
46              
47 5 100       11 my $context_prefix = $context_name eq ""
48             ? ""
49             : "$context_name: ";
50              
51             return timer_guard(
52             sub {
53 5     5   9 my $duration = shift;
54 5         14 _collect_timing($message, $duration);
55              
56             # Un-nest indentation level
57 5         523 my $indent = $context->{indent} -= $indent_increase;
58              
59 5         18 my $indentation = " " x max(1, $indent);
60 5         25 $log->trace("${log_prefix}duration($duration)$indentation$context_prefix$message");
61              
62             # Clean up context if we're done with it
63 5 100       283 if(my $start_indent = $context->{start_indent}) {
64 1 50       3 if( $indent == $start_indent ) {
65 1         11 delete $name__context->{ $context_name };
66             }
67             }
68             },
69 5         22 4, # decimal points
70             );
71             }
72              
73              
74             sub subroutine_timer {
75 1     1 1 1927 my ($message, $options) = @_;
76 1   50     7 $options //= {};
77              
78 1   50     5 my $depth = $options->{depth} || 1;
79 1         4 my (undef, undef, undef, $subroutine) = caller($depth);
80 1         26 $subroutine =~ s/::(\w+)$/->$1/;
81 1 50       4 $message = defined($message) ? ": $message" : "";
82              
83 1         13 return timer( "$subroutine$message", $options );
84             }
85              
86             sub _collect_timing {
87 5     5   23 my ($message, $duration) = @_;
88 5   66     27 my $stats = $message_stats->{$message} ||= Statistics::Descriptive::Sparse->new();
89 5         102 $stats->add_data($duration);
90             }
91              
92              
93             sub report_timing_stats {
94 2     2 1 1937 my @what = ( "mean", "sum", "min", "max", "standard_deviation" );
95              
96             my @messages =
97 4         7 map { $_->{message} }
98             reverse
99 5         40 sort { $a->{order} <=> $b->{order} }
100             map {
101 2         7 my $stats = $message_stats->{$_};
  4         103  
102             +{
103             message => "Stats: "
104             . sprintf("%-12s", "count(" . $stats->count . "), ")
105             . join(
106             ", ",
107 4         8 map { sprintf("$_(%.3f)", ($stats->$_)) } @what
  20         2707  
108             ) . " for ($_)",
109             order => $stats->sum,
110             };
111             }
112             keys %$message_stats;
113              
114 2         8 for my $message (@messages) {
115 4         125 $log->info($message);
116             }
117              
118 2         33 clear_timing_stats();
119             }
120              
121              
122             sub clear_timing_stats {
123 2     2 1 16 $message_stats = {};
124             }
125              
126             END {
127 1     1   24306 report_timing_stats();
128             }
129              
130             1;
131              
132             __END__