passing tests again
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Stats.pm
CommitLineData
dc5f035e 1package Catalyst::Stats;
2
e5ecd5bc 3use Moose;
dc5f035e 4use Time::HiRes qw/gettimeofday tv_interval/;
5use Text::SimpleTable ();
39fc2ce1 6use Catalyst::Utils;
dc5f035e 7use Tree::Simple qw/use_weak_refs/;
8use Tree::Simple::Visitor::FindByUID;
9
01b865ce 10use namespace::clean -except => 'meta';
11
e5ecd5bc 12has enable => (is => 'rw', required => 1, default => sub{ 1 });
13has tree => (
14 is => 'ro',
15 required => 1,
02570318 16 default => sub{ Tree::Simple->new({t => [gettimeofday]}) },
17 handles => [qw/ accept traverse /],
e5ecd5bc 18 );
19has stack => (
20 is => 'ro',
21 required => 1,
22 lazy => 1,
23 default => sub { [ shift->tree ] }
24 );
dc5f035e 25
26sub profile {
27 my $self = shift;
28
e5ecd5bc 29 return unless $self->enable;
dc5f035e 30
31 my %params;
32 if (@_ <= 1) {
e5ecd5bc 33 $params{comment} = shift || "";
dc5f035e 34 }
35 elsif (@_ % 2 != 0) {
e5ecd5bc 36 die "profile() requires a single comment parameter or a list of name-value pairs; found "
37 . (scalar @_) . " values: " . join(", ", @_);
dc5f035e 38 }
39 else {
e5ecd5bc 40 (%params) = @_;
41 $params{comment} ||= "";
dc5f035e 42 }
43
44 my $parent;
45 my $prev;
46 my $t = [ gettimeofday ];
e5ecd5bc 47 my $stack = $self->stack;
dc5f035e 48
49 if ($params{end}) {
e5ecd5bc 50 # parent is on stack; search for matching block and splice out
51 for (my $i = $#{$stack}; $i > 0; $i--) {
52 if ($stack->[$i]->getNodeValue->{action} eq $params{end}) {
53 my ($node) = splice(@{$stack}, $i, 1);
54 # Adjust elapsed on partner node
55 my $v = $node->getNodeValue;
56 $v->{elapsed} = tv_interval($v->{t}, $t);
57 return $node->getUID;
58 }
b01f0c69 59 }
b01f0c69 60 # if partner not found, fall through to treat as non-closing call
dc5f035e 61 }
62 if ($params{parent}) {
e5ecd5bc 63 # parent is explicitly defined
64 $prev = $parent = $self->_get_uid($params{parent});
dc5f035e 65 }
66 if (!$parent) {
e5ecd5bc 67 # Find previous node, which is either previous sibling or parent, for ref time.
68 $prev = $parent = $stack->[-1] or return undef;
69 my $n = $parent->getChildCount;
70 $prev = $parent->getChild($n - 1) if $n > 0;
dc5f035e 71 }
72
73 my $node = Tree::Simple->new({
e5ecd5bc 74 action => $params{begin} || "",
75 t => $t,
76 elapsed => tv_interval($prev->getNodeValue->{t}, $t),
77 comment => $params{comment},
dc5f035e 78 });
79 $node->setUID($params{uid}) if $params{uid};
80
81 $parent->addChild($node);
e5ecd5bc 82 push(@{$stack}, $node) if $params{begin};
dc5f035e 83
84 return $node->getUID;
85}
86
221f7e77 87sub created {
88 return @{ shift->{tree}->getNodeValue->{t} };
89}
90
dc5f035e 91sub elapsed {
92 return tv_interval(shift->{tree}->getNodeValue->{t});
93}
94
95sub report {
96 my $self = shift;
97
39fc2ce1 98 my $column_width = Catalyst::Utils::term_width() - 9 - 13;
99 my $t = Text::SimpleTable->new( [ $column_width, 'Action' ], [ 9, 'Time' ] );
dc5f035e 100 my @results;
02570318 101 $self->traverse(
b01f0c69 102 sub {
103 my $action = shift;
104 my $stat = $action->getNodeValue;
105 my @r = ( $action->getDepth,
106 ($stat->{action} || "") .
107 ($stat->{action} && $stat->{comment} ? " " : "") . ($stat->{comment} ? '- ' . $stat->{comment} : ""),
108 $stat->{elapsed},
109 $stat->{action} ? 1 : 0,
110 );
3295c7db 111 # Trim down any times >= 10 to avoid ugly Text::Simple line wrapping
112 my $elapsed = substr(sprintf("%f", $stat->{elapsed}), 0, 8) . "s";
e5ecd5bc 113 $t->row( ( q{ } x $r[0] ) . $r[1],
3295c7db 114 defined $r[2] ? $elapsed : '??');
b01f0c69 115 push(@results, \@r);
116 }
117 );
dc5f035e 118 return wantarray ? @results : $t->draw;
119}
120
121sub _get_uid {
122 my ($self, $uid) = @_;
123
124 my $visitor = Tree::Simple::Visitor::FindByUID->new;
125 $visitor->searchForUID($uid);
02570318 126 $self->accept($visitor);
dc5f035e 127 return $visitor->getResult;
b0ad47c1 128}
dc5f035e 129
2f381252 130sub addChild {
131 my $self = shift;
132 my $node = $_[ 0 ];
133
134 my $stat = $node->getNodeValue;
135
136 # do we need to fake $stat->{ t } ?
137 if( $stat->{ elapsed } ) {
138 # remove the "s" from elapsed time
139 $stat->{ elapsed } =~ s{s$}{};
140 }
141
02570318 142 $self->tree->addChild( @_ );
2f381252 143}
144
145sub setNodeValue {
146 my $self = shift;
147 my $stat = $_[ 0 ];
148
149 # do we need to fake $stat->{ t } ?
150 if( $stat->{ elapsed } ) {
151 # remove the "s" from elapsed time
152 $stat->{ elapsed } =~ s{s$}{};
153 }
154
02570318 155 $self->tree->setNodeValue( @_ );
2f381252 156}
157
158sub getNodeValue {
159 my $self = shift;
02570318 160 $self->tree->getNodeValue( @_ )->{ t };
2f381252 161}
162
6680c772 163__PACKAGE__->meta->make_immutable();
164
dc5f035e 1651;
166
167__END__
168
f4dda4a8 169=for stopwords addChild getNodeValue mysub rollup setNodeValue
170
dc5f035e 171=head1 NAME
172
173Catalyst::Stats - Catalyst Timing Statistics Class
174
175=head1 SYNOPSIS
176
177 $stats = $c->stats;
178 $stats->enable(1);
179 $stats->profile($comment);
180 $stats->profile(begin => $block_name, comment =>$comment);
181 $stats->profile(end => $block_name);
182 $elapsed = $stats->elapsed;
183 $report = $stats->report;
184
185See L<Catalyst>.
186
187=head1 DESCRIPTION
188
189This module provides the default, simple timing stats collection functionality for Catalyst.
190If you want something different set C<< MyApp->stats_class >> in your application module,
191e.g.:
192
193 __PACKAGE__->stats_class( "My::Stats" );
194
195If you write your own, your stats object is expected to provide the interface described here.
196
197Catalyst uses this class to report timings of component actions. You can add
198profiling points into your own code to get deeper insight. Typical usage might
199be like this:
200
201 sub mysub {
202 my ($c, ...) = @_;
203 $c->stats->profile(begin => "mysub");
204 # code goes here
205 ...
206 $c->stats->profile("starting critical bit");
207 # code here too
208 ...
209 $c->stats->profile("completed first part of critical bit");
210 # more code
211 ...
212 $c->stats->profile("completed second part of critical bit");
213 # more code
214 ...
b0ad47c1 215 $c->stats->profile(end => "mysub");
dc5f035e 216 }
217
218Supposing mysub was called from the action "process" inside a Catalyst
219Controller called "service", then the reported timings for the above example
220might look something like this:
221
222 .----------------------------------------------------------------+-----------.
223 | Action | Time |
224 +----------------------------------------------------------------+-----------+
225 | /service/process | 1.327702s |
226 | mysub | 0.555555s |
227 | - starting critical bit | 0.111111s |
228 | - completed first part of critical bit | 0.333333s |
229 | - completed second part of critical bit | 0.111000s |
230 | /end | 0.000160s |
231 '----------------------------------------------------------------+-----------'
232
233which means mysub took 0.555555s overall, it took 0.111111s to reach the
234critical bit, the first part of the critical bit took 0.333333s, and the second
235part 0.111s.
236
237
238=head1 METHODS
239
240=head2 new
241
b0ad47c1 242Constructor.
dc5f035e 243
244 $stats = Catalyst::Stats->new;
245
246=head2 enable
247
248 $stats->enable(0);
249 $stats->enable(1);
250
251Enable or disable stats collection. By default, stats are enabled after object creation.
252
253=head2 profile
254
255 $stats->profile($comment);
256 $stats->profile(begin => $block_name, comment =>$comment);
257 $stats->profile(end => $block_name);
258
259Marks a profiling point. These can appear in pairs, to time the block of code
260between the begin/end pairs, or by themselves, in which case the time of
b0ad47c1 261execution to the previous profiling point will be reported.
dc5f035e 262
263The argument may be either a single comment string or a list of name-value
264pairs. Thus the following are equivalent:
265
266 $stats->profile($comment);
267 $stats->profile(comment => $comment);
268
269The following key names/values may be used:
270
271=over 4
272
273=item * begin => ACTION
274
275Marks the beginning of a block. The value is used in the description in the
276timing report.
277
278=item * end => ACTION
279
280Marks the end of the block. The name given must match a previous 'begin'.
281Correct nesting is recommended, although this module is tolerant of blocks that
282are not correctly nested, and the reported timings should accurately reflect the
283time taken to execute the block whether properly nested or not.
284
285=item * comment => COMMENT
286
287Comment string; use this to describe the profiling point. It is combined with
288the block action (if any) in the timing report description field.
289
290=item * uid => UID
291
292Assign a predefined unique ID. This is useful if, for whatever reason, you wish
293to relate a profiling point to a different parent than in the natural execution
294sequence.
295
296=item * parent => UID
297
298Explicitly relate the profiling point back to the parent with the specified UID.
299The profiling point will be ignored if the UID has not been previously defined.
300
301=back
302
303Returns the UID of the current point in the profile tree. The UID is
304automatically assigned if not explicitly given.
305
221f7e77 306=head2 created
307
308 ($seconds, $microseconds) = $stats->created;
309
310Returns the time the object was created, in C<gettimeofday> format, with
311Unix epoch seconds followed by microseconds.
312
dc5f035e 313=head2 elapsed
314
315 $elapsed = $stats->elapsed
316
317Get the total elapsed time (in seconds) since the object was created.
318
319=head2 report
320
321 print $stats->report ."\n";
322 $report = $stats->report;
323 @report = $stats->report;
324
325In scalar context, generates a textual report. In array context, returns the
326array of results where each row comprises:
327
328 [ depth, description, time, rollup ]
329
330The depth is the calling stack level of the profiling point.
331
332The description is a combination of the block name and comment.
333
334The time reported for each block is the total execution time for the block, and
335the time associated with each intermediate profiling point is the elapsed time
336from the previous profiling point.
337
338The 'rollup' flag indicates whether the reported time is the rolled up time for
339the block, or the elapsed time from the previous profiling point.
340
10011c19 341=head1 COMPATIBILITY METHODS
2f381252 342
343Some components might expect the stats object to be a regular Tree::Simple object.
10011c19 344We've added some compatibility methods to handle this scenario:
2f381252 345
346=head2 accept
347
348=head2 addChild
349
350=head2 setNodeValue
351
352=head2 getNodeValue
353
354=head2 traverse
dc5f035e 355
356=head1 SEE ALSO
357
2f381252 358L<Catalyst>
dc5f035e 359
2f381252 360=head1 AUTHORS
dc5f035e 361
2f381252 362Catalyst Contributors, see Catalyst.pm
dc5f035e 363
364=head1 COPYRIGHT
365
536bee89 366This library is free software. You can redistribute it and/or modify
dc5f035e 367it under the same terms as Perl itself.
368
369=cut
370
e5ecd5bc 371__PACKAGE__->meta->make_immutable;
372
dc5f035e 3731;