File Coverage

blib/lib/Plack/Middleware/Debug/Template.pm
Criterion Covered Total %
statement 21 105 20.0
branch 0 28 0.0
condition 0 3 0.0
subroutine 7 17 41.1
pod 4 4 100.0
total 32 157 20.3


line stmt bran cond sub pod time code
1             package Plack::Middleware::Debug::Template;
2              
3 1     1   50491 use strict;
  1         2  
  1         26  
4 1     1   5 use warnings;
  1         3  
  1         107  
5              
6             =head1 NAME
7              
8             Plack::Middleware::Debug::Template - storing profiling information
9             on template use.
10              
11             =head1 VERSION
12              
13             Version 1.00
14              
15             =cut
16              
17             our $VERSION = '1.00';
18              
19             =head1 SYNOPSIS
20            
21             To activate this panel:
22            
23             plack_middlewares:
24             Debug:
25             - panels
26             -
27             - Template
28            
29             Or in your app.psgi, something like:
30            
31             builder {
32             enable 'Debug', panels => ['Template'];
33             $app;
34             };
35            
36             =head1 DESCRIPTION
37            
38             This middleware adds timers around calls to L
39             to track the time spent rendering the template and the layout for the page.
40            
41             =head1 HOOKS
42              
43             Subclass this module and implement the below functions if you wish to change
44             its behaviour.
45              
46             =head2 show_pathname
47              
48             Return true if the panel should show the path name rather than the template
49             name, or false to have the path name in a title attribute.
50              
51             =cut
52              
53       0 1   sub show_pathname {}
54              
55             =head2 hook_pathname
56              
57             This function can alter the full template path name provided to it for display.
58              
59             =cut
60              
61       0 1   sub hook_pathname {}
62              
63             =head2 ignore_template
64              
65             If you don't want output for any particular template, test for it here.
66             Return true to ignore.
67              
68             =cut
69              
70       0 1   sub ignore_template {}
71              
72             # Main code
73              
74 1     1   250 use parent qw(Plack::Middleware::Debug::Base);
  1         327  
  1         6  
75 1     1   26042 use Class::Method::Modifiers qw/install_modifier/;
  1         1403  
  1         61  
76 1     1   390 use Data::Dumper;
  1         5508  
  1         70  
77 1     1   11 use Text::MicroTemplate;
  1         3  
  1         41  
78 1     1   329 use Time::HiRes qw(gettimeofday tv_interval);
  1         1076  
  1         5  
79              
80             # A quasi-dump, that expands arrayrefs and hashrefs but doesn't try and
81             # include any object contents
82              
83             sub _pp {
84 0     0     my $t = shift;
85 0           my $r = ref $t;
86 0 0         if ($r eq 'ARRAY') {
    0          
    0          
87 0           return [ map { _pp($_) } @$t ];
  0            
88             } elsif ($r eq 'HASH') {
89 0           return { map { $_ => _pp($t->{$_}) } keys %$t };
  0            
90             } elsif ($r) {
91 0           return $r;
92             } else {
93 0           return $t;
94             }
95             }
96              
97             # Convert the given stash into a representation that can be output
98             # in the debug panel
99              
100             sub _stash {
101 0     0     my ($self, $stash) = @_;
102 0           local $Data::Dumper::Terse = 1;
103             return {
104             map {
105 0           my $p = _pp($stash->{$_});
106 0 0         $_ => ref $p ? Dumper($p) : $p
107             }
108             grep {
109 0           ref $stash->{$_} ne 'CODE'
  0            
110             }
111             keys %$stash
112             };
113             }
114              
115             sub _diff_disp {
116 0     0     my $starting_point = shift;
117 0           return sprintf( '%.3f', tv_interval($starting_point) * 1000 );
118             }
119              
120             my $list_template = __PACKAGE__->build_template(<<'EOTMPL');
121            
128             % foreach my $tmpl (@{$_[0]}) {
129            

<%= $tmpl->{title} %>

130             % my $i;
131            
132            
133            
134             Template
135             Time (ms)
136            
137            
138            
139             % foreach my $line (@{$tmpl->{list}}) {
140            
141            
142             % if (defined $line->{offset_pc}) {
143            
144             % }
145            
146             <%= Text::MicroTemplate::encoded_string(' ' x 4 x $line->{depth}) %>
147             % if ($line->{path}) {
148            
149             % }
150             <%= $line->{name} %>
151             % if ($line->{path}) {
152            
153             % }
154             <%= $line->{vars} || "" %>
155            
156            
157            
158             <%= $line->{duration} || "" %>
159            
160            
161             % }
162            
163            
164            

Stash

165            
166            
167            
168             Key
169             Value
170            
171            
172            
173             % foreach my $key (sort keys %{$tmpl->{stash}}) {
174            
175             <%= $key %>
176             <%= $tmpl->{stash}->{$key} || "" %>
177            
178             % }
179            
180            
181             % }
182             EOTMPL
183              
184             my $env_key = 'psgi.middleware.template';
185              
186             our $depth = 0;
187             our $epoch = undef;
188              
189             my %template_to_path;
190              
191             # Sets up a wrapper to Template::Context's process and Template::Provider's
192             # _fetch, to record start/end times, variables, and the stash, and to get the
193             # full file path.
194             sub run {
195 0     0 1   my ($pmd, $env, $panel) = @_;
196              
197 0           $env->{$env_key} = [];
198              
199             install_modifier 'Template::Context', 'around', 'process', sub {
200 0     0     my $orig = shift;
201 0           my $self = shift;
202 0           my $what = shift;
203              
204             my $template =
205             ref($what) eq 'ARRAY'
206 0 0         ? join( ' + ', @{$what} )
  0 0          
207             : ref($what)
208             ? $what->name
209             : $what;
210              
211 0 0         return $orig->($self, $what, @_) if $pmd->ignore_template($template);
212              
213 0           my $processed_data;
214             my $epoch_elapsed_start;
215 0           my $epoch_elapsed_end;
216 0           my $now = [gettimeofday];
217 0           my $start = [@{$now}];
  0            
218 0           my $env = $env->{$env_key};
219              
220 0           my $entry;
221 0 0         if ($depth == 0) {
222 0           $entry = { title => $template, stash => {}, list => [], total => 0 };
223 0           push @$env, $entry;
224             } else {
225 0           $entry = $env->[-1];
226             }
227              
228 0           my $results = { depth => $depth, name => $template };
229 0           push @{$entry->{list}}, $results;
  0            
230             DOIT: {
231 0 0         local $epoch = $epoch ? $epoch : [@{$now}];
  0            
  0            
232 0           local $depth = $depth + 1;
233 0           $epoch_elapsed_start = _diff_disp($epoch);
234 0           $processed_data = $orig->($self, $what, @_);
235 0           $epoch_elapsed_end = _diff_disp($epoch);
236             }
237 0           my $level_elapsed = _diff_disp($start);
238 0           my $vars = join ", ", map { "$_=" . $_[0]->{$_} } keys %{$_[0]};
  0            
  0            
239 0           $results->{start} = $epoch_elapsed_start;
240 0           $results->{end} = $epoch_elapsed_end;
241 0           $results->{duration} = $level_elapsed;
242 0           $results->{vars} = $vars;
243              
244 0 0         return $processed_data if $depth > 0;
245              
246             # Okay, we've finished our tree of templates now
247              
248 0           $entry->{total} = $results->{duration};
249 0           my $main_start = $results->{start};
250 0           foreach (@{$entry->{list}}) {
  0            
251 0 0         next unless $_->{start};
252 0           $_->{offset_pc} = ($_->{start} - $main_start) / $entry->{total} * 100;
253 0           $_->{duration_pc} = ($_->{end} - $_->{start}) / $entry->{total} * 100;
254 0 0         if ($pmd->show_pathname) {
255 0   0       $_->{name} = $template_to_path{$_->{name}} || $_->{name};
256             } else {
257 0           $_->{path} = $template_to_path{$_->{name}};
258             }
259             }
260              
261 0           $entry->{stash} = $pmd->_stash($self->stash);
262              
263 0           return $processed_data;
264 0           };
265             install_modifier 'Template::Provider', 'around', '_fetch', sub {
266 0     0     my $orig = shift;
267 0           my ($tp_self, $name, $t_name) = @_;
268 0 0         if (my $hooked = $pmd->hook_pathname($name)) {
269 0           $name = $hooked;
270             }
271 0           $template_to_path{$t_name} = $name;
272 0           return $orig->(@_);
273 0           };
274              
275             return sub {
276 0     0     my $res = shift;
277 0           $panel->title('Templates');
278 0           my $total = 0;
279 0           foreach (@{$env->{$env_key}}) {
  0            
280 0           $total += $_->{total};
281             }
282 0 0         $panel->nav_subtitle("$total ms") if $total;
283 0           $panel->content($pmd->render($list_template, $env->{$env_key}));
284 0           };
285             }
286              
287             =head1 SUPPORT
288              
289             You can look for information on GitHub at
290             L.
291              
292             =head1 ACKNOWLEDGEMENTS
293              
294             This module is based on a combination of
295             Plack::Middleware::Debug::Dancer::TemplateTimer and Template::Timer.
296              
297             =head1 AUTHOR
298              
299             Matthew Somerville, C<< >>
300              
301             =head1 LICENSE AND COPYRIGHT
302            
303             Copyright 2017 Matthew Somerville.
304            
305             This library is free software; you can redistribute it and/or modify
306             it under the terms of either the GNU Public License v3, or the Artistic
307             License 2.0. See L for more information.
308              
309             =cut
310              
311             1;