Fixed run-on sentence in COPYRIGHT and s/program/library/
[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
87sub elapsed {
88 return tv_interval(shift->{tree}->getNodeValue->{t});
89}
90
91sub report {
92 my $self = shift;
93
39fc2ce1 94 my $column_width = Catalyst::Utils::term_width() - 9 - 13;
95 my $t = Text::SimpleTable->new( [ $column_width, 'Action' ], [ 9, 'Time' ] );
dc5f035e 96 my @results;
02570318 97 $self->traverse(
b01f0c69 98 sub {
99 my $action = shift;
100 my $stat = $action->getNodeValue;
101 my @r = ( $action->getDepth,
102 ($stat->{action} || "") .
103 ($stat->{action} && $stat->{comment} ? " " : "") . ($stat->{comment} ? '- ' . $stat->{comment} : ""),
104 $stat->{elapsed},
105 $stat->{action} ? 1 : 0,
106 );
3295c7db 107 # Trim down any times >= 10 to avoid ugly Text::Simple line wrapping
108 my $elapsed = substr(sprintf("%f", $stat->{elapsed}), 0, 8) . "s";
e5ecd5bc 109 $t->row( ( q{ } x $r[0] ) . $r[1],
3295c7db 110 defined $r[2] ? $elapsed : '??');
b01f0c69 111 push(@results, \@r);
112 }
113 );
dc5f035e 114 return wantarray ? @results : $t->draw;
115}
116
117sub _get_uid {
118 my ($self, $uid) = @_;
119
120 my $visitor = Tree::Simple::Visitor::FindByUID->new;
121 $visitor->searchForUID($uid);
02570318 122 $self->accept($visitor);
dc5f035e 123 return $visitor->getResult;
ac5c933b 124}
dc5f035e 125
2f381252 126sub addChild {
127 my $self = shift;
128 my $node = $_[ 0 ];
129
130 my $stat = $node->getNodeValue;
131
132 # do we need to fake $stat->{ t } ?
133 if( $stat->{ elapsed } ) {
134 # remove the "s" from elapsed time
135 $stat->{ elapsed } =~ s{s$}{};
136 }
137
02570318 138 $self->tree->addChild( @_ );
2f381252 139}
140
141sub setNodeValue {
142 my $self = shift;
143 my $stat = $_[ 0 ];
144
145 # do we need to fake $stat->{ t } ?
146 if( $stat->{ elapsed } ) {
147 # remove the "s" from elapsed time
148 $stat->{ elapsed } =~ s{s$}{};
149 }
150
02570318 151 $self->tree->setNodeValue( @_ );
2f381252 152}
153
154sub getNodeValue {
155 my $self = shift;
02570318 156 $self->tree->getNodeValue( @_ )->{ t };
2f381252 157}
158
6680c772 159__PACKAGE__->meta->make_immutable();
160
dc5f035e 1611;
162
163__END__
164
165=head1 NAME
166
167Catalyst::Stats - Catalyst Timing Statistics Class
168
169=head1 SYNOPSIS
170
171 $stats = $c->stats;
172 $stats->enable(1);
173 $stats->profile($comment);
174 $stats->profile(begin => $block_name, comment =>$comment);
175 $stats->profile(end => $block_name);
176 $elapsed = $stats->elapsed;
177 $report = $stats->report;
178
179See L<Catalyst>.
180
181=head1 DESCRIPTION
182
183This module provides the default, simple timing stats collection functionality for Catalyst.
184If you want something different set C<< MyApp->stats_class >> in your application module,
185e.g.:
186
187 __PACKAGE__->stats_class( "My::Stats" );
188
189If you write your own, your stats object is expected to provide the interface described here.
190
191Catalyst uses this class to report timings of component actions. You can add
192profiling points into your own code to get deeper insight. Typical usage might
193be like this:
194
195 sub mysub {
196 my ($c, ...) = @_;
197 $c->stats->profile(begin => "mysub");
198 # code goes here
199 ...
200 $c->stats->profile("starting critical bit");
201 # code here too
202 ...
203 $c->stats->profile("completed first part of critical bit");
204 # more code
205 ...
206 $c->stats->profile("completed second part of critical bit");
207 # more code
208 ...
ac5c933b 209 $c->stats->profile(end => "mysub");
dc5f035e 210 }
211
212Supposing mysub was called from the action "process" inside a Catalyst
213Controller called "service", then the reported timings for the above example
214might look something like this:
215
216 .----------------------------------------------------------------+-----------.
217 | Action | Time |
218 +----------------------------------------------------------------+-----------+
219 | /service/process | 1.327702s |
220 | mysub | 0.555555s |
221 | - starting critical bit | 0.111111s |
222 | - completed first part of critical bit | 0.333333s |
223 | - completed second part of critical bit | 0.111000s |
224 | /end | 0.000160s |
225 '----------------------------------------------------------------+-----------'
226
227which means mysub took 0.555555s overall, it took 0.111111s to reach the
228critical bit, the first part of the critical bit took 0.333333s, and the second
229part 0.111s.
230
231
232=head1 METHODS
233
234=head2 new
235
ac5c933b 236Constructor.
dc5f035e 237
238 $stats = Catalyst::Stats->new;
239
240=head2 enable
241
242 $stats->enable(0);
243 $stats->enable(1);
244
245Enable or disable stats collection. By default, stats are enabled after object creation.
246
247=head2 profile
248
249 $stats->profile($comment);
250 $stats->profile(begin => $block_name, comment =>$comment);
251 $stats->profile(end => $block_name);
252
253Marks a profiling point. These can appear in pairs, to time the block of code
254between the begin/end pairs, or by themselves, in which case the time of
ac5c933b 255execution to the previous profiling point will be reported.
dc5f035e 256
257The argument may be either a single comment string or a list of name-value
258pairs. Thus the following are equivalent:
259
260 $stats->profile($comment);
261 $stats->profile(comment => $comment);
262
263The following key names/values may be used:
264
265=over 4
266
267=item * begin => ACTION
268
269Marks the beginning of a block. The value is used in the description in the
270timing report.
271
272=item * end => ACTION
273
274Marks the end of the block. The name given must match a previous 'begin'.
275Correct nesting is recommended, although this module is tolerant of blocks that
276are not correctly nested, and the reported timings should accurately reflect the
277time taken to execute the block whether properly nested or not.
278
279=item * comment => COMMENT
280
281Comment string; use this to describe the profiling point. It is combined with
282the block action (if any) in the timing report description field.
283
284=item * uid => UID
285
286Assign a predefined unique ID. This is useful if, for whatever reason, you wish
287to relate a profiling point to a different parent than in the natural execution
288sequence.
289
290=item * parent => UID
291
292Explicitly relate the profiling point back to the parent with the specified UID.
293The profiling point will be ignored if the UID has not been previously defined.
294
295=back
296
297Returns the UID of the current point in the profile tree. The UID is
298automatically assigned if not explicitly given.
299
300=head2 elapsed
301
302 $elapsed = $stats->elapsed
303
304Get the total elapsed time (in seconds) since the object was created.
305
306=head2 report
307
308 print $stats->report ."\n";
309 $report = $stats->report;
310 @report = $stats->report;
311
312In scalar context, generates a textual report. In array context, returns the
313array of results where each row comprises:
314
315 [ depth, description, time, rollup ]
316
317The depth is the calling stack level of the profiling point.
318
319The description is a combination of the block name and comment.
320
321The time reported for each block is the total execution time for the block, and
322the time associated with each intermediate profiling point is the elapsed time
323from the previous profiling point.
324
325The 'rollup' flag indicates whether the reported time is the rolled up time for
326the block, or the elapsed time from the previous profiling point.
327
10011c19 328=head1 COMPATIBILITY METHODS
2f381252 329
330Some components might expect the stats object to be a regular Tree::Simple object.
10011c19 331We've added some compatibility methods to handle this scenario:
2f381252 332
333=head2 accept
334
335=head2 addChild
336
337=head2 setNodeValue
338
339=head2 getNodeValue
340
341=head2 traverse
dc5f035e 342
343=head1 SEE ALSO
344
2f381252 345L<Catalyst>
dc5f035e 346
2f381252 347=head1 AUTHORS
dc5f035e 348
2f381252 349Catalyst Contributors, see Catalyst.pm
dc5f035e 350
351=head1 COPYRIGHT
352
536bee89 353This library is free software. You can redistribute it and/or modify
dc5f035e 354it under the same terms as Perl itself.
355
356=cut
357
e5ecd5bc 358__PACKAGE__->meta->make_immutable;
359
dc5f035e 3601;