File Coverage

blib/lib/Plack/Middleware/Profiler/NYTProf.pm
Criterion Covered Total %
statement 110 128 85.9
branch 29 46 63.0
condition 4 9 44.4
subroutine 37 41 90.2
pod 2 11 18.1
total 182 235 77.4


line stmt bran cond sub pod time code
1             package Plack::Middleware::Profiler::NYTProf;
2 5     5   2932 use strict;
  5         8  
  5         113  
3 5     5   23 use warnings;
  5         8  
  5         136  
4 5     5   2772 use parent qw(Plack::Middleware);
  5         1231  
  5         31  
5             our $VERSION = '0.17';
6              
7 5         31 use Plack::Util::Accessor qw(
8             enable_profile
9             enable_reporting
10             env_nytprof
11             generate_profile_id
12             profiling_result_dir
13             report_dir
14             nytprofhtml_path
15             profiling_result_file_name
16             nullfile_name
17             before_profile
18             after_profile
19 5     5   64100 );
  5         10  
20              
21 5     5   479 use File::Spec;
  5         10  
  5         111  
22 5     5   4204 use Time::HiRes qw(gettimeofday);
  5         7296  
  5         22  
23 5     5   3637 use File::Which;
  5         3446  
  5         280  
24              
25 5     5   25 use constant PROFILE_ID => 'psgix.profiler.nytprof.reqid';
  5         9  
  5         247  
26 5     5   22 use constant PROFILER_ENABLED => 'psgix.profiler.nytprof.enabled';
  5         10  
  5         6561  
27              
28             my %PROFILER_SETUPED;
29              
30             my $NYTPROF_LOADED = 0;
31              
32             # Devel::NYTProf requires to be loaded in a compile phase.
33             # So you should call this method in the BEGIN of your app.psgi with the NYTPROF environment variable.
34             sub preload {
35 1   33 1 0 54 $ENV{NYTPROF} ||= _default_env();
36 1         1288 require Devel::NYTProf;
37 1         2226 DB::disable_profile();
38 1         2235 $NYTPROF_LOADED = 1;
39             }
40              
41 1     1   21 sub _default_env { 'start=no:sigexit=int' }
42              
43             sub prepare_app {
44 4     4 1 294 my $self = shift;
45              
46 4         19 $self->_setup_profile_id;
47 4         38 $self->_setup_profiling_file_paths;
48 4         57 $self->_setup_profiling_hooks;
49 4         32 $self->_setup_enable_profile;
50 4         52 $self->_setup_enable_reporting;
51 4         38 $self->_setup_report_dir;
52 4         30 $self->_setup_nytprofhtml_path;
53             }
54              
55             sub _setup_profiling_file_paths {
56 4     4   11 my $self = shift;
57 4         31 $self->_setup_profiling_result_dir;
58 4         30 $self->_setup_profiling_result_file_name;
59 4         28 $self->_setup_nullfile_name;
60             }
61              
62             sub _setup_enable_reporting {
63 4     4   9 my $self = shift;
64 4 50       17 $self->enable_reporting(1) unless defined $self->enable_reporting;
65             }
66              
67             sub _setup_enable_profile {
68 4     4   8 my $self = shift;
69 4 100   6   17 $self->enable_profile( sub {1} ) unless $self->enable_profile;
  6         58  
70             }
71              
72             sub _setup_profiling_result_dir {
73 4     4   141 my $self = shift;
74 2     2   23 $self->profiling_result_dir( sub {'.'} )
75 4 100       20 unless is_code_ref( $self->profiling_result_dir );
76             }
77              
78             sub _setup_report_dir {
79 4     4   8 my $self = shift;
80 0     0   0 $self->report_dir( sub {'report'} )
81 4 50       17 unless is_code_ref( $self->report_dir );
82             }
83              
84             sub _setup_nytprofhtml_path {
85 4     4   8 my $self = shift;
86 4 50       14 return if $self->nytprofhtml_path;
87 4 50       34 my $nytprofhtml_path = File::Which::which('nytprofhtml')
88             or die "Could not find nytprofhtml script. Ensure it's in your path";
89 4         527 $self->nytprofhtml_path($nytprofhtml_path);
90             }
91              
92             sub _setup_profile_id {
93 4     4   7 my $self = shift;
94 3     3   108 $self->generate_profile_id( sub { return $$ . "-" . gettimeofday; } )
95 4 50       24 unless is_code_ref( $self->generate_profile_id );
96             }
97              
98             sub _setup_profiling_result_file_name {
99 4     4   8 my $self = shift;
100             $self->profiling_result_file_name(
101 2     2   17 sub { my $id = $_[1]->{PROFILE_ID}; return "nytprof.$id.out"; } )
  2         1268  
102 4 100       19 unless is_code_ref( $self->profiling_result_file_name );
103             }
104              
105             sub _setup_nullfile_name {
106 4     4   8 my $self = shift;
107              
108 4 50       15 $self->nullfile_name('nytprof.null.out') unless $self->nullfile_name;
109             }
110              
111             sub _setup_profiling_hooks {
112 4     4   8 my $self = shift;
113       3     $self->before_profile( sub { } )
114 4 50       25 unless is_code_ref( $self->before_profile );
115       3     $self->after_profile( sub { } )
116 4 50       34 unless is_code_ref( $self->after_profile );
117              
118             }
119              
120             sub call {
121 4     4 1 98185 my ( $self, $env ) = @_;
122              
123 4         23 $self->_setup_profiler($env);
124 4         22 $self->start_profiling_if_needed($env);
125              
126 4         683 my $res = $self->app->($env);
127              
128 4 50 33     309 if ( ref($res) && ref($res) eq 'ARRAY' ) {
129 4         21 $self->stop_profiling_and_report_if_needed($env);
130 4         40 return $res;
131             }
132              
133             Plack::Util::response_cb(
134             $res,
135             sub {
136 0     0   0 my $res = shift;
137             sub {
138 0         0 my $chunk = shift;
139 0 0       0 if ( !defined $chunk ) {
140 0         0 $self->stop_profiling_and_report_if_needed($env);
141 0         0 return;
142             }
143 0         0 return $chunk;
144             }
145 0         0 }
146 0         0 );
147             }
148              
149             sub start_profiling_if_needed {
150 4     4 0 11 my ( $self, $env ) = @_;
151 4         21 my $is_profiler_enabled = $self->enable_profile->($env);
152 4 100       25 return unless $is_profiler_enabled;
153              
154 3         12 $env->{PROFILER_ENABLED} = 1;
155 3         113 $self->before_profile->( $self, $env );
156 3         21 $self->start_profiling($env);
157             }
158              
159             sub stop_profiling_and_report_if_needed {
160 4     4 0 13 my ( $self, $env ) = @_;
161 4         13 my $is_profiler_enabled = $env->{PROFILER_ENABLED};
162 4 100       22 return unless $is_profiler_enabled;
163              
164 3         16 $self->stop_profiling($env);
165 3 50       17 $self->report($env) if $self->enable_reporting;
166 3         36 $self->after_profile->( $self, $env );
167             }
168              
169             sub _setup_profiler {
170 4     4   10 my ( $self, $env ) = @_;
171              
172 4         18 my $pid = $$;
173 4 50       27 return if $PROFILER_SETUPED{$pid};
174 4         14 $PROFILER_SETUPED{$pid} = 1;
175              
176 4         29 my $is_profiler_enabled = $self->enable_profile->($env);
177 4 100       27 return unless $is_profiler_enabled;
178              
179 3 100       16 return if $NYTPROF_LOADED;
180              
181 2   66     20 $ENV{NYTPROF} = $ENV{NYTPROF} || $self->env_nytprof || _default_env();
182 2         2256 require Devel::NYTProf;
183 2         3899 DB::disable_profile();
184 2         6 $NYTPROF_LOADED = 1;
185             }
186              
187             sub start_profiling {
188 3     3 0 10 my ( $self, $env ) = @_;
189              
190 3         16 $env->{PROFILE_ID} = $self->generate_profile_id->( $self, $env );
191 3         19 DB::enable_profile( $self->profiling_result_file_path($env) );
192             }
193              
194             sub stop_profiling {
195 3     3 0 120 DB::disable_profile();
196             }
197              
198             sub report {
199 0     0 0 0 my ( $self, $env ) = @_;
200              
201 0 0       0 return unless $env->{PROFILE_ID};
202              
203 0         0 DB::enable_profile( $self->nullfile_path );
204 0         0 DB::disable_profile();
205 0         0 my $profiling_result_file = $self->profiling_result_file_path($env);
206 0 0       0 return unless ( -f $profiling_result_file );
207 0         0 system $self->nytprofhtml_path, "-f", $profiling_result_file,
208             '-o', $self->report_dir->();
209             }
210              
211             sub profiling_result_file_path {
212 3     3 0 9 my ( $self, $env ) = @_;
213              
214 3         16 return File::Spec->catfile(
215             $self->profiling_result_dir->( $self, $env ),
216             $self->profiling_result_file_name->( $self, $env )
217             );
218             }
219              
220             sub nullfile_path {
221 0     0 0 0 my ( $self, $env ) = @_;
222              
223 0         0 return File::Spec->catfile( $self->profiling_result_dir->( $self, $env ),
224             $self->nullfile_name );
225             }
226              
227             sub is_code_ref {
228 24     24 0 397 my $ref = shift;
229 24 100       198 return ( ref($ref) eq 'CODE' ) ? 1 : 0;
230             }
231              
232             sub DESTROY {
233 4 100   4   637 DB::finish_profile() if defined &{"DB::finish_profile"};
  4         751  
234             }
235              
236             1;
237              
238             __END__