File Coverage

blib/lib/Plack/Middleware/Debug/Catalyst/Template.pm
Criterion Covered Total %
statement 21 107 19.6
branch 0 30 0.0
condition 0 3 0.0
subroutine 7 18 38.8
pod 5 5 100.0
total 33 163 20.2


line stmt bran cond sub pod time code
1             package Plack::Middleware::Debug::Catalyst::Template;
2              
3 1     1   49166 use strict;
  1         2  
  1         28  
4 1     1   5 use warnings;
  1         2  
  1         109  
5              
6             =head1 NAME
7              
8             Plack::Middleware::Debug::Catalyst::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             - Catalyst::Template
28            
29             Or in your app.psgi, something like:
30            
31             builder {
32             enable 'Debug', panels => ['Catalyst::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   314 use parent qw(Plack::Middleware::Debug::Base);
  1         240  
  1         5  
75 1     1   25892 use Class::Method::Modifiers qw/install_modifier/;
  1         1296  
  1         63  
76 1     1   406 use Data::Dumper;
  1         5788  
  1         76  
77 1     1   9 use Text::MicroTemplate;
  1         1  
  1         34  
78 1     1   300 use Time::HiRes qw(gettimeofday tv_interval);
  1         1118  
  1         4  
79              
80             my $env_key = 'psgi.middleware.catalyst.template';
81              
82             our $depth = 0;
83             our $epoch = undef;
84              
85             my %template_to_path;
86              
87             # A quasi-dump, that expands arrayrefs and hashrefs but doesn't try and
88             # include any object contents
89              
90             sub _pp {
91 0     0     my $t = shift;
92 0           my $r = ref $t;
93 0 0         if ($r eq 'ARRAY') {
    0          
    0          
94 0           return [ map { _pp($_) } @$t ];
  0            
95             } elsif ($r eq 'HASH') {
96 0           return { map { $_ => _pp($t->{$_}) } keys %$t };
  0            
97             } elsif ($r) {
98 0           return $r;
99             } else {
100 0           return $t;
101             }
102             }
103              
104             # Convert the given stash into a representation that can be output
105             # in the debug panel
106              
107             sub _stash {
108 0     0     my ($self, $stash) = @_;
109 0           local $Data::Dumper::Terse = 1;
110             return {
111             map {
112 0           my $p = _pp($stash->{$_});
113 0 0         $_ => ref $p ? Dumper($p) : $p
114             }
115             grep {
116 0           ref $stash->{$_} ne 'CODE'
  0            
117             }
118             keys %$stash
119             };
120             }
121              
122             # Sets up a wrapper to Template::Context's process, to
123             # record start/end times, variables, and the stash.
124             sub prepare_app {
125 0     0 1   my $pmd = shift;
126              
127             install_modifier 'Template::Context', 'around', 'process', sub {
128 0     0     my $orig = shift;
129 0           my $self = shift;
130 0           my $what = shift;
131              
132             my $template =
133             ref($what) eq 'ARRAY'
134 0 0         ? join( ' + ', @{$what} )
  0 0          
135             : ref($what)
136             ? $what->name
137             : $what;
138              
139 0 0         return $orig->($self, $what, @_) if $pmd->ignore_template($template);
140 0 0         return $orig->($self, $what, @_) unless $self->stash->{c};
141              
142 0           my $processed_data;
143             my $epoch_elapsed_start;
144 0           my $epoch_elapsed_end;
145 0           my $now = [gettimeofday];
146 0           my $start = [@{$now}];
  0            
147 0           my $env = $self->stash->{c}->request->env->{$env_key};
148              
149 0           my $entry;
150 0 0         if ($depth == 0) {
151 0           $entry = { title => $template, stash => {}, list => [], total => 0 };
152 0           push @$env, $entry;
153             } else {
154 0           $entry = $env->[-1];
155             }
156              
157 0           my $results = { depth => $depth, name => $template };
158 0           push @{$entry->{list}}, $results;
  0            
159             DOIT: {
160 0 0         local $epoch = $epoch ? $epoch : [@{$now}];
  0            
  0            
161 0           local $depth = $depth + 1;
162 0           $epoch_elapsed_start = _diff_disp($epoch);
163 0           $processed_data = $orig->($self, $what, @_);
164 0           $epoch_elapsed_end = _diff_disp($epoch);
165             }
166 0           my $level_elapsed = _diff_disp($start);
167 0           my $vars = join ", ", map { "$_=" . $_[0]->{$_} } keys %{$_[0]};
  0            
  0            
168 0           $results->{start} = $epoch_elapsed_start;
169 0           $results->{end} = $epoch_elapsed_end;
170 0           $results->{duration} = $level_elapsed;
171 0           $results->{vars} = $vars;
172              
173 0 0         return $processed_data if $depth > 0;
174              
175             # Okay, we've finished our tree of templates now
176              
177 0           $entry->{total} = $results->{duration};
178 0           my $main_start = $results->{start};
179 0           foreach (@{$entry->{list}}) {
  0            
180 0 0         next unless $_->{start};
181 0           $_->{offset_pc} = ($_->{start} - $main_start) / $entry->{total} * 100;
182 0           $_->{duration_pc} = ($_->{end} - $_->{start}) / $entry->{total} * 100;
183 0 0         if ($pmd->show_pathname) {
184 0   0       $_->{name} = $template_to_path{$_->{name}} || $_->{name};
185             } else {
186 0           $_->{path} = $template_to_path{$_->{name}};
187             }
188             }
189              
190 0           $entry->{stash} = $pmd->_stash($self->stash);
191              
192 0           return $processed_data;
193 0           };
194             }
195              
196             sub _diff_disp {
197 0     0     my $starting_point = shift;
198 0           return sprintf( '%.3f', tv_interval($starting_point) * 1000 );
199             }
200              
201             my $list_template = __PACKAGE__->build_template(<<'EOTMPL');
202            
209             % foreach my $tmpl (@{$_[0]}) {
210            

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

211             % my $i;
212            
213            
214            
215             Template
216             Time (ms)
217            
218            
219            
220             % foreach my $line (@{$tmpl->{list}}) {
221            
222            
223             % if (defined $line->{offset_pc}) {
224            
225             % }
226            
227             <%= Text::MicroTemplate::encoded_string(' ' x 4 x $line->{depth}) %>
228             % if ($line->{path}) {
229            
230             % }
231             <%= $line->{name} %>
232             % if ($line->{path}) {
233            
234             % }
235             <%= $line->{vars} || "" %>
236            
237            
238            
239             <%= $line->{duration} || "" %>
240            
241            
242             % }
243            
244            
245            

Stash

246            
247            
248            
249             Key
250             Value
251            
252            
253            
254             % foreach my $key (sort keys %{$tmpl->{stash}}) {
255            
256             <%= $key %>
257             <%= $tmpl->{stash}->{$key} || "" %>
258            
259             % }
260            
261            
262             % }
263             EOTMPL
264              
265             # Installs a wrapper around Template::Provider's _fetch, as
266             # that's where we can get the full file path from.
267             sub run {
268 0     0 1   my ($self, $env, $panel) = @_;
269              
270 0           $env->{$env_key} = [];
271              
272             install_modifier 'Template::Provider', 'around', '_fetch', sub {
273 0     0     my $orig = shift;
274 0           my ($tp_self, $name, $t_name) = @_;
275 0 0         if (my $hooked = $self->hook_pathname($name)) {
276 0           $name = $hooked;
277             }
278 0           $template_to_path{$t_name} = $name;
279 0           return $orig->(@_);
280 0           };
281              
282             return sub {
283 0     0     my $res = shift;
284 0           $panel->title('Templates');
285 0           my $total = 0;
286 0           foreach (@{$env->{$env_key}}) {
  0            
287 0           $total += $_->{total};
288             }
289 0 0         $panel->nav_subtitle("$total ms") if $total;
290 0           $panel->content($self->render($list_template, $env->{$env_key}));
291 0           };
292             }
293              
294             =head1 SUPPORT
295              
296             You can look for information on GitHub at
297             L.
298              
299             =head1 ACKNOWLEDGEMENTS
300              
301             This module is based on a combination of
302             Plack::Middleware::Debug::Dancer::TemplateTimer and Template::Timer.
303              
304             =head1 AUTHOR
305              
306             Matthew Somerville, C<< >>
307              
308             =head1 LICENSE AND COPYRIGHT
309            
310             Copyright 2017 Matthew Somerville.
311            
312             This library is free software; you can redistribute it and/or modify
313             it under the terms of either the GNU Public License v3, or the Artistic
314             License 2.0. See L for more information.
315              
316             =cut
317              
318             1;