Hack 74. Trace All Used Modules


See what modules your program usesand what modules those modules use!

Perhaps the most useful feature of Perl 5 is module support, allowing the use of existing, pre-written code. With thousands of modules on the CPAN available for free, it's likely that any code you write will use at least a few other pieces of code.

Of course, all of the modules you use optionally use a few modules of their own, and so on. You could find yourself loading dozens of pieces of code for what looks like a simple program. Alternately, you may just be curious to see the relationships within your code.

Wouldn't it be nice to see which modules your code loaded from where? Now you can.

The Hack

The easiest way to gather the information on what Perl modules any piece of code loads is a little-known feature of @INC, the magic variable that governs where Perl looks to load modules. If @INC contains a code reference, it will execute that reference when attempting to load a module. This is a great place to store code to manage library paths, as PAR and The::Net (http://www.perlmonks.org/?node_id=92473, not on the CPAN) do. It also works well to collect interesting statistics:

package Devel::TraceUse; use Time::HiRes qw( gettimeofday tv_interval ); BEGIN {     unshift @INC, \\&trace_use unless grep { "$_" eq \\&trace_use . '' } @INC; } sub trace_use {     my ($code, $module) = @_;     (my $mod_name       = $module) =~ s{/}{::}g;     $mod_name           =~ s/\\.pm$//;     my ($package, $filename, $line) = caller( );     my $elapsed         = 0;     {         local *INC      = [ @INC[1..$#INC] ];         my $start_time  = [ gettimeofday( ) ];         eval "package $package; require '$mod_name';";         $elapsed        = tv_interval( $start_time );     }     $package            = $filename if $package eq 'main';     push @used,     {         'file'   => $package,         'line'   => $line,         'time'   => $elapsed,         'module' => $mod_name,     };     return; } END {     my $first = $used[0];     my %seen  = ( $first->{file} => 1 );     my $pos   = 1;     warn "Modules used from $first->{file}:\\n";     for my $mod (@used)     {         my $message = '';         if (exists $seen{$mod->{file}})         {             $pos = $seen{$mod->{file}};         }         else         {             $seen{$mod->{file}} = ++$pos;         }         my $indent = '  ' x $pos;         $message  .= "$indent$mod->{module}, line $mod->{line}";         $message  .= " ($mod->{time})" if $mod->{time};         warn "$message\\n";     } } 1;

The code begins by storing a reference to trace_use( ) at the head of @ISA. Whenever Perl encounters a use or require statement for a module it hasn't previously loaded, it will loop through each entry in @ISA, trying to load the module from there. As the first entry is a subroutine reference, Perl will call the subroutine with the name of the module to load (at least, translated into a Unix-style file path).

Devel::TraceUse translates the path name back into a module name, looks up the call stack to find the name of the package and file containing the use or require statement as well as the line number of the statement, and then redispatches the lookup, taking itself temporarily out of @INC. This redispatch allows the module to collect information on how long it took to load the module.

This time isn't absolute; the string eval statement as well as the calls to Time::HiRes take up a near-constant amount of time. However, it's likely consistent, so comparing times to each other is sensible.


The code uses the filename of the caller if there's no explicit package given, stores all of the available information, and pushes that structure into an array of modules used.

At the end of the program, the module prints a report of the modules loaded in the order in which Perl encountered them.

Running the Hack

Perhaps the prove utility from Test::Harness has captured your attention and you want to know what modules it loads. With Devel::TraceUse in your path somewhere, run the command:

$ perl -MDevel::TraceUse /usr/bin/prove Modules used from /usr/bin/prove:   Test::Harness, line 8 (0.000544)     Test::Harness::Straps, line 6 (0.000442)       Test::Harness::Assert, line 9 (0.000464)       Test::Harness::Iterator, line 10 (0.000581)       Test::Harness::Point, line 11 (0.000437)       POSIX, line 313 (0.000483)         XSLoader, line 9 (0.000425)     Benchmark, line 9 (0.000497)       Exporter::Heavy, line 17 (0.000502)   Getopt::Long, line 9 (0.000495)     constant, line 221 (0.000475)   Pod::Usage, line 10 (0.000486)     File::Spec, line 405 (0.000464)       File::Spec::Unix, line 21 (0.000432)     Pod::Text, line 411 (0.000471)       Pod::ParseLink, line 30 (0.000475)       Pod::Select, line 31 (0.000447)         Pod::Parser, line 242 (0.000461)           Pod::InputObjects, line 205 (0.000444)           Symbol, line 210 (0.000469)   File::Glob, line 82 (0.000521)

Thus prove uses Test::Harness, Getopt::Long, Pod::Usage, and File::Glob directly, each of which uses several other modules. If you were adding features to prove and wanted to know if using POSIX would add significantly to the resource footprint, you would now know that you already pay the price for it, so you might as well use it.

Hacking the Hack

What could make this module more useful? Right now, it doesn't report the use of Time::HiRes, because it uses that internally. Making timing information optional would be nice. Furthermore, the report always goes to STDERR, which may mingle badly with other program output.

Perhaps you want to filter out certain packages selectively, or trace all of the uses of require and use. In lieu of reloading every module every time some piece of code wants to use it, Perl tracks loaded modules by caching their filenames in %INC. To have Devel::TraceUse track every attempt to load a module, whether Perl has loaded it, keep the delegation, but clear out %INC. (Be sure to keep your own cache, though, to prevent subroutine redefinitions and initialization code from running over and over again.)



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net