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