File Coverage

blib/lib/Devel/Module/Trace.pm
Criterion Covered Total %
statement 59 109 54.1
branch 9 44 20.4
condition 3 6 50.0
subroutine 12 16 75.0
pod 2 3 66.6
total 85 178 47.7


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   621 use warnings;
  1         2  
  1         35  
40 1     1   3 use strict;
  1         2  
  1         28  
41 1     1   458 use Devel::OverrideGlobalRequire;
  1         394  
  1         38  
42              
43             our $VERSION = '0.01';
44              
45             ################################################################################
46             my $modules = [];
47             my $cur_lvl = $modules;
48             BEGIN {
49 1     1   465 use Time::HiRes qw/gettimeofday tv_interval time/;
  1         1167  
  1         3  
50 1     1   537 $^P = $^P | 0x400; # Save source code lines, see perldoc perlvar
51             };
52              
53             ################################################################################
54             $Devel::Module::Trace::print = 0;
55             $Devel::Module::Trace::filter = [];
56             $Devel::Module::Trace::enabled = 0 unless defined $Devel::Module::Trace::enabled;
57             sub import {
58 1     1   9 my(undef, @options) = @_;
59 1         2 for my $option (@options) {
60 0 0       0 if($option eq 'print') {
    0          
61 0         0 $Devel::Module::Trace::print = 1;
62             }
63             elsif($option =~ 'filter=(.*)$') {
64 0         0 my $filter = $1;
65 0         0 push @{$Devel::Module::Trace::filter}, $filter;
  0         0  
66             } else {
67 0         0 die("unknown option: ".$option);
68             }
69             }
70 1         8 return;
71             }
72              
73             ################################################################################
74              
75             =head1 METHODS
76              
77             =head2 raw_result
78              
79             raw_result()
80              
81             returns an array with the raw result list.
82              
83             =cut
84             sub raw_result {
85 1     1 1 1766 return($modules);
86             }
87              
88             ################################################################################
89              
90             =head2 print_pretty
91              
92             print_pretty()
93              
94             prints the results as ascii table to STDERR.
95              
96             =cut
97             sub print_pretty {
98 0     0 1 0 my $reenable = 0;
99 0 0       0 if($Devel::Module::Trace::enabled) {
100 0         0 _disable();
101 0         0 $reenable = 1;
102             }
103 0         0 my($raw, $indent, $max_module, $max_caller, $max_indent) = @_;
104 0 0       0 $raw = $modules unless $raw;
105 0 0       0 if(!$indent) {
106 0         0 require POSIX;
107 0         0 $indent = 0;
108             # get max caller and module
109 0         0 ($max_module, $max_caller) = _get_max_pp_size($modules, 0, 0, 0);
110 0 0       0 return if $max_module == 0;
111 0 0       0 print " ","-"x($max_module+$max_caller+34), "\n" if $indent == 0;
112             }
113 0         0 for my $mod (@{$raw}) {
  0         0  
114 0 0       0 next if _filtered($mod->{'name'});
115 0         0 my($time, $milliseconds) = split(/\./mx, $mod->{'time'});
116 0         0 printf(STDERR "| %s%08.5f | %-".$indent."s %-".($max_module-$indent)."s | %.6f | %-".$max_caller."s |\n",
117             POSIX::strftime("%H:%M:", localtime($time)),
118             POSIX::strftime("%S", localtime($time)).'.'.$milliseconds,
119             "",
120             $mod->{'name'},
121             $mod->{'elapsed'},
122             $mod->{'caller'},
123             );
124 0 0       0 if($mod->{'sub'}) {
125 0         0 print_pretty($mod->{'sub'}, $indent+4, $max_module, $max_caller, $max_indent);
126             }
127             }
128 0 0       0 print " ","-"x($max_module+$max_caller+34), "\n" if $indent == 0;
129 0 0       0 _enable() if $reenable;
130 0         0 return;
131             }
132              
133             ################################################################################
134             sub _enable {
135 1     1   1 $Devel::Module::Trace::enabled = 1;
136 1         4 Devel::OverrideGlobalRequire::override_global_require(\&_trace_use);
137 1         206 return;
138             }
139              
140             ################################################################################
141             sub _trace_use {
142 16     16   8206 my($next_require,$module_name) = @_;
143 16 100       30 if(!$Devel::Module::Trace::enabled) {
144 6         6 return &{$next_require}();
  6         10  
145             }
146 10         37 my($p,$f,$l) = caller(1);
147 10         11 my $code;
148             {
149             ## no critics
150 1     1   4 no strict 'refs';
  1         1  
  1         235  
  10         5  
151 10         8 $code = \@{"::_<$f"};
  10         22  
152             ## use critics
153             }
154 10 50       19 if(!$code->[$l]) {
155 0         0 return &{$next_require}();
  0         0  
156             }
157 10         7 my $code_str = $code->[$l];
158 10         12 my $i = $l-1;
159             # try to concatenate previous lines if statement was multilined
160 10   100     67 while($i > 0 && $code->[$i] !~ m/^(.*\}|.*\;|=cut)\s*$/mxo) {
161 11 100       48 if($code->[$i] !~ m/^\s*$|^\s*\#/mxo) {
162 6         9 $code_str = $code->[$i].$code_str;
163             }
164 11         55 $i--;
165             }
166 10 100       26 if($code_str !~ m/^\s*(use|require)/mxo) {
167 1         2 return &{$next_require}();
  1         2  
168             }
169 9         38 my $mod = {
170             package => $p,
171             name => $module_name,
172             caller => $f.':'.$l,
173             time => time
174             };
175 9         21 my $t0 = [gettimeofday];
176 9         7 my $old_lvl = $cur_lvl;
177 9         9 $cur_lvl = [];
178 9         4 my $res = &{$next_require}();
  9         16  
179 9         1645 my $elapsed = tv_interval($t0);
180 9         78 $mod->{'elapsed'} = $elapsed;
181 9 100       7 $mod->{'sub'} = $cur_lvl if scalar @{$cur_lvl};
  9         18  
182 9         6 $cur_lvl = $old_lvl;
183 9         7 push(@{$cur_lvl}, $mod);
  9         12  
184 9         31 return $res;
185             }
186              
187             ################################################################################
188             sub _disable {
189 1     1   4 $Devel::Module::Trace::enabled = 0;
190 1         1 return;
191             }
192              
193             ################################################################################
194             BEGIN {
195 1     1   2 _enable();
196             };
197              
198             ################################################################################
199             sub _filtered {
200 0     0     my($mod) = @_;
201 0           for my $f (@{$Devel::Module::Trace::filter}) {
  0            
202 0 0         if($mod =~ m|$f|mx) {
203 0           return(1);
204             }
205 0 0 0       if($f eq 'perl' && $mod =~ m|^[\d\.]+$|mx) {
206 0           return(1);
207             }
208             }
209 0           return;
210             }
211              
212             ################################################################################
213             sub _get_max_pp_size {
214 0     0     my($mods, $max_module, $max_caller, $cur_indent) = @_;
215 0           for my $mod (@{$mods}) {
  0            
216 0 0         next if _filtered($mod);
217 0           my $l1 = length($mod->{'name'}) + $cur_indent;
218 0           my $l2 = length($mod->{'caller'});
219 0 0         $max_module = $l1 if $max_module < $l1;
220 0 0         $max_caller = $l2 if $max_caller < $l2;
221 0 0         if($mod->{'sub'}) {
222 0           ($max_module, $max_caller) = _get_max_pp_size($mod->{'sub'}, $max_module, $max_caller, $cur_indent+4);
223             }
224             }
225 0           return($max_module, $max_caller);
226             }
227              
228             ################################################################################
229             END {
230             print_pretty() if $Devel::Module::Trace::print;
231             };
232              
233             ################################################################################
234              
235             1;
236              
237             =head1 TODO
238              
239             * add waterfall charts output
240              
241             =head1 REPOSITORY
242              
243             Git: http://github.com/sni/perl-devel-module-trace
244              
245             =head1 SEE ALSO
246              
247             L
248              
249             =head1 AUTHOR
250              
251             Sven Nierlein, C<< >>
252              
253             =head1 COPYRIGHT & LICENSE
254              
255             Copyright 2015 Sven Nierlein.
256              
257             This program is free software; you can redistribute it and/or modify it
258             under the terms of either: the GNU General Public License as published
259             by the Free Software Foundation; or the Artistic License.
260              
261             See http://dev.perl.org/licenses/ for more information.
262              
263             =cut