File Coverage

blib/lib/Devel/Module/Trace.pm
Criterion Covered Total %
statement 65 122 53.2
branch 11 58 18.9
condition 2 9 22.2
subroutine 15 20 75.0
pod 3 4 75.0
total 96 213 45.0


line stmt bran cond sub pod time code
1             ## no critic
2             package # hide package name from indexer
3             DB;
4             # allow -d:Devel::Module::Trace loading
5 0     0 0 0 sub DB {}
6             ## use critic
7              
8             package Devel::Module::Trace;
9              
10             =head1 NAME
11              
12             Devel::Module::Trace - Trace module origins
13              
14             =head1 DESCRIPTION
15              
16             This module traces use/require statements to print the origins of loaded modules
17              
18             =head1 SYNOPSIS
19              
20             =over 4
21              
22             # load module
23             use Devel::Module::Trace;
24              
25             # load other modules
26             use Some::Other::Modules;
27             require Even::More::Modules;
28              
29             # output results
30             Devel::Module::Trace::print_pretty();
31              
32             # using directly
33             perl -d:Module::Trace=print -M -e exit
34              
35             =back
36              
37             =cut
38              
39 1     1   780 use warnings;
  1         1  
  1         43  
40 1     1   6 use strict;
  1         1  
  1         38  
41 1     1   731 use Data::Dumper;
  1         6398  
  1         111  
42 1     1   708 use POSIX;
  1         6763  
  1         8  
43 1     1   8286 use Devel::OverrideGlobalRequire;
  1         601  
  1         82  
44              
45             our $VERSION = '0.02';
46              
47             ################################################################################
48             $Devel::Module::Trace::modules = [] unless defined $Devel::Module::Trace::modules;
49             my $cur_lvl = $Devel::Module::Trace::modules;
50             BEGIN {
51 1     1   5799 use Time::HiRes qw/gettimeofday tv_interval time/;
  1         1817  
  1         5  
52 1     1   297 $^P = $^P | 0x400; # Save source code lines, see perldoc perlvar
53             };
54              
55             ################################################################################
56             BEGIN {
57 1 50   1   6 $Devel::Module::Trace::print = 0 unless defined $Devel::Module::Trace::print;
58 1 50       4 $Devel::Module::Trace::filter = [] unless defined $Devel::Module::Trace::filter;
59 1 50       6 $Devel::Module::Trace::enabled = 0 unless defined $Devel::Module::Trace::enabled;
60 1 50       2 $Devel::Module::Trace::save = undef unless defined $Devel::Module::Trace::save;
61 1 50       710 $Devel::Module::Trace::autostart = 1 unless defined $Devel::Module::Trace::autostart;
62             }
63             sub import {
64 1     1   15 my(undef, @options) = @_;
65 1         4 for my $option (@options) {
66 0 0       0 if($option eq 'print') {
    0          
    0          
    0          
67 0         0 $Devel::Module::Trace::print = 1;
68             }
69             elsif($option eq 'noautostart') {
70 0         0 $Devel::Module::Trace::autostart = 0;
71             }
72             elsif($option =~ 'filter=(.*)$') {
73 0         0 my $filter = $1;
74 0         0 push @{$Devel::Module::Trace::filter}, $filter;
  0         0  
75             }
76             elsif($option =~ 'save=(.*)$') {
77 0         0 $Devel::Module::Trace::save = $1;
78             } else {
79 0         0 die("unknown option: ".$option);
80             }
81             }
82 1         15 return;
83             }
84              
85             ################################################################################
86              
87             =head1 METHODS
88              
89             =head2 raw_result
90              
91             raw_result()
92              
93             returns an array with the raw result list.
94              
95             =cut
96             sub raw_result {
97 1     1 1 2521 return($Devel::Module::Trace::modules);
98             }
99              
100             ################################################################################
101              
102             =head2 save
103              
104             save()
105              
106             save results to given file
107              
108             =cut
109             sub save {
110 0     0 1 0 my($file) = @_;
111 0 0       0 open(my $fh, '>', $file) or die("cannot write to $file: $!");
112 0         0 print $fh Dumper({
113             result => raw_result(),
114             filter => $Devel::Module::Trace::filter,
115             script => $0,
116             });
117 0         0 close($fh);
118 0         0 print STDERR $file." written\n";
119 0         0 return;
120             }
121              
122             ################################################################################
123              
124             =head2 print_pretty
125              
126             print_pretty()
127              
128             prints the results as ascii table to STDERR.
129              
130             =cut
131             sub print_pretty {
132 0     0 1 0 my($raw, $indent, $max_module, $max_caller, $max_indent) = @_;
133 0 0       0 $raw = raw_result() unless $raw;
134 0 0       0 if(!$indent) {
135 0         0 $indent = 0;
136             # get max caller and module
137 0         0 ($max_module, $max_caller) = _get_max_pp_size(raw_result(), 0, 0, 0);
138 0 0       0 return if $max_module == 0;
139 0 0       0 print " ","-"x($max_module+$max_caller+34), "\n" if $indent == 0;
140             }
141 0         0 for my $mod (@{$raw}) {
  0         0  
142 0 0       0 next if _filtered($mod->{'name'});
143 0         0 my($time, $milliseconds) = split(/\./mx, $mod->{'time'});
144 0         0 printf(STDERR "| %s%08.5f | %-".$indent."s %-".($max_module-$indent)."s | %.6f | %-".$max_caller."s |\n",
145             POSIX::strftime("%H:%M:", localtime($time)),
146             POSIX::strftime("%S", localtime($time)).'.'.$milliseconds,
147             "",
148             $mod->{'name'},
149             $mod->{'elapsed'},
150             $mod->{'caller'},
151             );
152 0 0       0 if($mod->{'sub'}) {
153 0         0 print_pretty($mod->{'sub'}, $indent+4, $max_module, $max_caller, $max_indent);
154             }
155             }
156 0 0       0 print " ","-"x($max_module+$max_caller+34), "\n" if $indent == 0;
157 0         0 return;
158             }
159              
160             ################################################################################
161             sub _enable {
162 1     1   1 $Devel::Module::Trace::enabled = 1;
163 1         8 Devel::OverrideGlobalRequire::override_global_require(\&_trace_use);
164 1         406 return;
165             }
166              
167             ################################################################################
168             sub _trace_use {
169 7     7   3181 my($next_require,$module_name) = @_;
170 7 100       17 if(!$Devel::Module::Trace::enabled) {
171 6         7 return &{$next_require}();
  6         14  
172             }
173 1         6 my($p,$f,$l) = caller(1);
174 1         2 my $code;
175             {
176             ## no critics
177 1     1   8 no strict 'refs';
  1         2  
  1         437  
  1         2  
178 1         1 $code = \@{"::_<$f"};
  1         5  
179             ## use critics
180             }
181 1 50       4 if(!$code->[$l]) {
182 0         0 return &{$next_require}();
  0         0  
183             }
184 1         2 my $code_str = $code->[$l];
185 1         2 my $i = $l-1;
186             # try to concatenate previous lines if statement was multilined
187 1   33     5 while($i > 0 && $code->[$i] && $code->[$i] !~ m/^(.*\}|.*\;|=cut)\s*$/mxo) {
      33        
188 0 0       0 if($code->[$i] !~ m/^\s*$|^\s*\#/mxo) {
189 0         0 $code_str = $code->[$i].$code_str;
190             }
191 0         0 $i--;
192             }
193 1 50       8 if($code_str !~ m/^\s*(use|require)/mxo) {
194 0         0 return &{$next_require}();
  0         0  
195             }
196 1         12 my $mod = {
197             package => $p,
198             name => $module_name,
199             caller => $f.':'.$l,
200             caller_f => $f,
201             caller_l => $l,
202             time => time
203             };
204 1         12 my $t0 = [gettimeofday];
205 1         2 my $old_lvl = $cur_lvl;
206 1         2 $cur_lvl = [];
207 1         2 my $res = &{$next_require}();
  1         3  
208 1         210 my $elapsed = tv_interval($t0);
209 1         18 $mod->{'elapsed'} = $elapsed;
210 1 50       19 $mod->{'sub'} = $cur_lvl if scalar @{$cur_lvl};
  1         5  
211 1         1 $cur_lvl = $old_lvl;
212 1         1 push(@{$cur_lvl}, $mod);
  1         3  
213 1         21 return $res;
214             }
215              
216             ################################################################################
217             sub _disable {
218 1     1   5 $Devel::Module::Trace::enabled = 0;
219 1         2 return;
220             }
221              
222             ################################################################################
223             BEGIN {
224 1 50   1   8 _enable() if $Devel::Module::Trace::autostart;
225             };
226              
227             ################################################################################
228             sub _filtered {
229 0     0     my($mod) = @_;
230 0           for my $f (@{$Devel::Module::Trace::filter}) {
  0            
231 0 0         if($mod =~ m|$f|mx) {
232 0           return(1);
233             }
234 0 0 0       if($f eq 'perl' && $mod =~ m|^[\d\.]+$|mx) {
235 0           return(1);
236             }
237             }
238 0           return;
239             }
240              
241             ################################################################################
242             sub _get_max_pp_size {
243 0     0     my($mods, $max_module, $max_caller, $cur_indent) = @_;
244 0           for my $mod (@{$mods}) {
  0            
245 0 0         next if _filtered($mod);
246 0           my $l1 = length($mod->{'name'}) + $cur_indent;
247 0           my $l2 = length($mod->{'caller'});
248 0 0         $max_module = $l1 if $max_module < $l1;
249 0 0         $max_caller = $l2 if $max_caller < $l2;
250 0 0         if($mod->{'sub'}) {
251 0           ($max_module, $max_caller) = _get_max_pp_size($mod->{'sub'}, $max_module, $max_caller, $cur_indent+4);
252             }
253             }
254 0           return($max_module, $max_caller);
255             }
256              
257             ################################################################################
258             END {
259             print_pretty() if $Devel::Module::Trace::print;
260             save($Devel::Module::Trace::save) if $Devel::Module::Trace::save;
261             };
262              
263             ################################################################################
264              
265             1;
266              
267             =head1 TODO
268              
269             * add waterfall charts output
270              
271             =head1 REPOSITORY
272              
273             Git: http://github.com/sni/perl-devel-module-trace
274              
275             =head1 SEE ALSO
276              
277             L
278              
279             =head1 AUTHOR
280              
281             Sven Nierlein, C<< >>
282              
283             =head1 COPYRIGHT & LICENSE
284              
285             Copyright 2015 Sven Nierlein.
286              
287             This program is free software; you can redistribute it and/or modify it
288             under the terms of either: the GNU General Public License as published
289             by the Free Software Foundation; or the Artistic License.
290              
291             See http://dev.perl.org/licenses/ for more information.
292              
293             =cut