1 package Catalyst::Stats;
4 use Time::HiRes qw/gettimeofday tv_interval/;
5 use Text::SimpleTable ();
6 use Tree::Simple qw/use_weak_refs/;
7 use Tree::Simple::Visitor::FindByUID;
9 has enable => (is => 'rw', required => 1, default => sub{ 1 });
13 default => sub{ Tree::Simple->new({t => [gettimeofday]}) }
19 default => sub { [ shift->tree ] }
25 return unless $self->enable;
29 $params{comment} = shift || "";
32 die "profile() requires a single comment parameter or a list of name-value pairs; found "
33 . (scalar @_) . " values: " . join(", ", @_);
37 $params{comment} ||= "";
42 my $t = [ gettimeofday ];
43 my $stack = $self->stack;
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);
56 # if partner not found, fall through to treat as non-closing call
58 if ($params{parent}) {
59 # parent is explicitly defined
60 $prev = $parent = $self->_get_uid($params{parent});
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;
69 my $node = Tree::Simple->new({
70 action => $params{begin} || "",
72 elapsed => tv_interval($prev->getNodeValue->{t}, $t),
73 comment => $params{comment},
75 $node->setUID($params{uid}) if $params{uid};
77 $parent->addChild($node);
78 push(@{$stack}, $node) if $params{begin};
84 return tv_interval(shift->{tree}->getNodeValue->{t});
90 # close any remaining open nodes
91 map { $self->profile(end => $_->getNodeValue->{action}) }
92 (reverse @{ $self->stack })[1 .. $#{$self->stack}];
94 my $t = Text::SimpleTable->new( [ 62, 'Action' ], [ 9, 'Time' ] );
96 $self->tree->traverse(
99 my $stat = $action->getNodeValue;
100 my @r = ( $action->getDepth,
101 ($stat->{action} || "") .
102 ($stat->{action} && $stat->{comment} ? " " : "") . ($stat->{comment} ? '- ' . $stat->{comment} : ""),
104 $stat->{action} ? 1 : 0,
106 $t->row( ( q{ } x $r[0] ) . $r[1],
107 defined $r[2] ? sprintf("%fs", $r[2]) : '??');
111 return wantarray ? @results : $t->draw;
115 my ($self, $uid) = @_;
117 my $visitor = Tree::Simple::Visitor::FindByUID->new;
118 $visitor->searchForUID($uid);
119 $self->tree->accept($visitor);
120 return $visitor->getResult;
125 $self->{tree}->accept( @_ );
132 my $stat = $node->getNodeValue;
134 # do we need to fake $stat->{ t } ?
135 if( $stat->{ elapsed } ) {
136 # remove the "s" from elapsed time
137 $stat->{ elapsed } =~ s{s$}{};
140 $self->{tree}->addChild( @_ );
147 # do we need to fake $stat->{ t } ?
148 if( $stat->{ elapsed } ) {
149 # remove the "s" from elapsed time
150 $stat->{ elapsed } =~ s{s$}{};
153 $self->{tree}->setNodeValue( @_ );
158 $self->{tree}->getNodeValue( @_ )->{ t };
163 $self->{tree}->traverse( @_ );
167 __PACKAGE__->meta->make_immutable();
175 Catalyst::Stats - Catalyst Timing Statistics Class
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;
191 This module provides the default, simple timing stats collection functionality for Catalyst.
192 If you want something different set C<< MyApp->stats_class >> in your application module,
195 __PACKAGE__->stats_class( "My::Stats" );
197 If you write your own, your stats object is expected to provide the interface described here.
199 Catalyst uses this class to report timings of component actions. You can add
200 profiling points into your own code to get deeper insight. Typical usage might
205 $c->stats->profile(begin => "mysub");
208 $c->stats->profile("starting critical bit");
211 $c->stats->profile("completed first part of critical bit");
214 $c->stats->profile("completed second part of critical bit");
217 $c->stats->profile(end => "mysub");
220 Supposing mysub was called from the action "process" inside a Catalyst
221 Controller called "service", then the reported timings for the above example
222 might look something like this:
224 .----------------------------------------------------------------+-----------.
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 |
233 '----------------------------------------------------------------+-----------'
235 which means mysub took 0.555555s overall, it took 0.111111s to reach the
236 critical bit, the first part of the critical bit took 0.333333s, and the second
246 $stats = Catalyst::Stats->new;
253 Enable or disable stats collection. By default, stats are enabled after object creation.
257 $stats->profile($comment);
258 $stats->profile(begin => $block_name, comment =>$comment);
259 $stats->profile(end => $block_name);
261 Marks a profiling point. These can appear in pairs, to time the block of code
262 between the begin/end pairs, or by themselves, in which case the time of
263 execution to the previous profiling point will be reported.
265 The argument may be either a single comment string or a list of name-value
266 pairs. Thus the following are equivalent:
268 $stats->profile($comment);
269 $stats->profile(comment => $comment);
271 The following key names/values may be used:
275 =item * begin => ACTION
277 Marks the beginning of a block. The value is used in the description in the
280 =item * end => ACTION
282 Marks the end of the block. The name given must match a previous 'begin'.
283 Correct nesting is recommended, although this module is tolerant of blocks that
284 are not correctly nested, and the reported timings should accurately reflect the
285 time taken to execute the block whether properly nested or not.
287 =item * comment => COMMENT
289 Comment string; use this to describe the profiling point. It is combined with
290 the block action (if any) in the timing report description field.
294 Assign a predefined unique ID. This is useful if, for whatever reason, you wish
295 to relate a profiling point to a different parent than in the natural execution
298 =item * parent => UID
300 Explicitly relate the profiling point back to the parent with the specified UID.
301 The profiling point will be ignored if the UID has not been previously defined.
305 Returns the UID of the current point in the profile tree. The UID is
306 automatically assigned if not explicitly given.
310 $elapsed = $stats->elapsed
312 Get the total elapsed time (in seconds) since the object was created.
316 print $stats->report ."\n";
317 $report = $stats->report;
318 @report = $stats->report;
320 In scalar context, generates a textual report. In array context, returns the
321 array of results where each row comprises:
323 [ depth, description, time, rollup ]
325 The depth is the calling stack level of the profiling point.
327 The description is a combination of the block name and comment.
329 The time reported for each block is the total execution time for the block, and
330 the time associated with each intermediate profiling point is the elapsed time
331 from the previous profiling point.
333 The 'rollup' flag indicates whether the reported time is the rolled up time for
334 the block, or the elapsed time from the previous profiling point.
336 =head1 COMPATABILITY METHODS
338 Some components might expect the stats object to be a regular Tree::Simple object.
339 We've added some compatability methods to handle this scenario:
357 Catalyst Contributors, see Catalyst.pm
361 This program is free software, you can redistribute it and/or modify
362 it under the same terms as Perl itself.
366 __PACKAGE__->meta->make_immutable;