File Coverage

blib/lib/Dancer/Plugin/TimeRequests.pm
Criterion Covered Total %
statement 21 39 53.8
branch 0 8 0.0
condition n/a
subroutine 7 8 87.5
pod n/a
total 28 55 50.9


line stmt bran cond sub pod time code
1             package Dancer::Plugin::TimeRequests;
2              
3 1     1   41174 use strict;
  1         2  
  1         40  
4 1     1   930 use Dancer::Plugin;
  1         209366  
  1         107  
5 1     1   4728 use Dancer qw(:syntax);
  1         252038  
  1         7  
6 1     1   3190 use HTML::Table;
  1         27538  
  1         253  
7 1     1   17 use List::Util;
  1         26  
  1         85  
8 1     1   1090 use List::MoreUtils;
  1         1393  
  1         52  
9 1     1   6 use Time::HiRes;
  1         18  
  1         11  
10              
11             our $VERSION = '0.06';
12              
13             =head1 NAME
14              
15             Dancer::Plugin::TimeRequests - log how long requests take and which routes are slow
16              
17             =head1 DESCRIPTION
18              
19             A simple Dancer plugin to log how long each request took to process, and also to
20             gather stats on the average response time for each route - so you can see at a
21             glance which routes are taking longer than you'd like, therefore where you ought
22             to start looking to improve performance.
23              
24             Provides a statistics page giving you a list of your routes, along with their
25             response times.
26              
27              
28             =head1 SYNOPSIS
29              
30             In your Dancer app, load this module:
31              
32             use Dancer::Plugin::TimeRequests;
33              
34             Then, when your app is logging in debug mode, log messages will be generated
35             showing how logn each request took:
36              
37             Request to /foo completed in 4.0011 seconds in ....
38              
39             To see which routes are slow, hit the URL C.
40              
41             =cut
42              
43             my %request_times;
44              
45             hook before => sub {
46             my $route_handler = shift;
47             var current_handler => $route_handler;
48             var request_start_time => Time::HiRes::time();
49             };
50              
51             hook after => sub {
52             Dancer::Logger::debug(sprintf "Request to %s completed in %.4f seconds",
53             request->path,
54             Time::HiRes::time() - vars->{request_start_time}
55             );
56             push @{ $request_times{ vars->{current_handler} } },
57             Time::HiRes::time() - vars->{request_start_time};
58             };
59              
60             get '/plugin-timerequests' => sub {
61             # Get the list of routes, and for each one, match up the coderef with our
62             # recorded times, and add the timing info, so we can then sort routes by
63             # average execution time to produce the output
64             my $routes = _get_routes();
65             for my $route (@$routes) {
66             my $route_times = $request_times{ $route->{route} };
67             next unless defined $route_times && scalar @$route_times;
68              
69             my ($min, $max) = List::MoreUtils::minmax(@$route_times);
70             $route->{times} = {
71             avg => List::Util::sum(@$route_times) / @$route_times,
72             min => $min,
73             max => $max,
74             };
75             }
76              
77             # Now, we can loop through all routes, slowest first, and output the timing
78             # info
79             my $table = HTML::Table->new;
80             $table->addRow('Route pattern', 'Average', 'Best', 'Worst');
81             $table->setRowHead(1);
82             for my $route (
83             sort { $b->{times}{avg} <=> $a->{times}{avg} }
84             grep { exists $_->{times} } @$routes
85             ) {
86             next unless exists $route->{times};
87             my $times = $route->{times};
88             $table->addRow(
89             $route->{pattern},
90             map { sprintf '%.3f', $_ || 0 } @$times{qw(avg min max)},
91             );
92             }
93             return $table->getTable;
94              
95             };
96              
97             # Fetch all routes defined. (Loosely based on code lovingly stolen with no
98             # shame from Dancer::Plugin::SiteMap - cheers James Ronan (JNRONAN)
99             # Returns an arrayref of hashrefs describing all routes (with keys pattern
100             # and handler)
101             sub _get_routes {
102 0 0   0     my $version = (exists &dancer_version) ? int( dancer_version() ) : 1;
103 0 0         my @apps = ($version == 2) ? @{ runner->server->apps }
  0            
104             : Dancer::App->applications;
105            
106 0           my @routes;
107 0           for my $app ( @apps ) {
108 0 0         my $app_routes = ($version == 2) ? $app->routes
109             : $app->{registry}->{routes};
110            
111 0           for my $route_type (keys %$app_routes) {
112 0           for my $route (@{ $app_routes->{$route_type} }) {
  0            
113 0           my ($pattern, $handler);
114 0 0         if ($version == 2) {
115 0           $pattern = $route->spec_route;
116 0           $handler = $route->handler;
117             } else {
118 0           $pattern = $route->pattern;
119 0           $handler = $route->code;
120             }
121 0           push @routes, {
122             route => $route,
123             pattern => $pattern,
124             handler => $handler,
125             };
126             }
127             }
128             }
129 0           debug "list of routes being returned:", \@routes;
130 0           return \@routes;
131             }
132              
133              
134             =head1 AUTHOR
135              
136             David Precious, C<< >>
137              
138             =head1 BUGS
139              
140             Please report any bugs or feature requests to C, or through
141             the web interface at L. I will be notified, and then you'll
142             automatically be notified of progress on your bug as I make changes.
143              
144              
145              
146              
147             =head1 SUPPORT
148              
149             You can find documentation for this module with the perldoc command.
150              
151             perldoc Dancer::Plugin::TimeRequests
152              
153              
154             You can also look for information at:
155              
156             =over 4
157              
158             =item * RT: CPAN's request tracker
159              
160             L
161              
162             =item * AnnoCPAN: Annotated CPAN documentation
163              
164             L
165              
166             =item * CPAN Ratings
167              
168             L
169              
170             =item * Search CPAN
171              
172             L
173              
174             =back
175              
176              
177             =head1 ACKNOWLEDGEMENTS
178              
179              
180             =head1 LICENSE AND COPYRIGHT
181              
182             Copyright 2011 David Precious.
183              
184             This program is free software; you can redistribute it and/or modify it
185             under the terms of either: the GNU General Public License as published
186             by the Free Software Foundation; or the Artistic License.
187              
188             See http://dev.perl.org/licenses/ for more information.
189              
190              
191             =cut
192              
193             1; # End of Dancer::Plugin::TimeRequests