About 10 years ago, I wrote a project that contained a dozen modules and a couple of hundred subs. Following the flow was a nightmare until I added trace code to each sub. I then set out on a journey to develop a module that will automatically inject this tracing, and after 10 years of off-and-on Perl programming, these objectives are now a reality in my new Devel::Trace::Subs module.
This module will install the appropriate use statement, along with the appropriate tracing call to all subs (functions or methods) within a file, within all files in a directory (selective by file extension) or within production modules live-time by using a Module::Name. Of course, you can back this automation out simply with a different call.
The typical SYNOPSIS will work, using the traditional use Devel::Trace::Subs qw(trace); and then adding the trace(); call to every single sub you want to support, but automation is what programming is for.
We'll start with the most basic example, a single script file with multiple subs:
use warnings;
use strict;
one();
exit(0);
sub one {
my $str = 'hello, world!';
two($str);
}
sub two {
my $str = shift;
$str =~ s/hello/goodbye/;
three($str);
}
sub three {
my $str = shift;
print "$str\n";
}
Which does this when run...
goodbye, world!
Now we'll install tracing into it (easiest from the command line, but you can of course script it as well):
perl -wMstrict -MDevel::Trace::Subs=install_trace -e 'install_trace(fi
+le => "trace.pl");'
Let's check to see what happened to our original file:
use Devel::Trace::Subs qw(trace trace_dump); # injected by Devel::Trac
+e::Subs
use warnings;
use strict;
one();
exit(0);
sub one {
trace() if $ENV{DTS_ENABLE}; # injected by Devel::Trace::Subs
my $str = 'hello, world!';
two($str);
}
sub two {
trace() if $ENV{DTS_ENABLE}; # injected by Devel::Trace::Subs
my $str = shift;
$str =~ s/hello/goodbye/;
three($str);
}
sub three {
trace() if $ENV{DTS_ENABLE}; # injected by Devel::Trace::Subs
my $str = shift;
print "$str\n";
}
We have to add a couple of lines of code to the calling script manually (in this case, the calling script is the same file that contains the subs we're tracing, so we add them there).
$ENV{DTS_ENABLE} = 1; # add this line before the first sub call
trace_dump(); # add this line after the last sub call
NOTE: To disable all tracing ability globally, simply set the single environment variable to a false value (or comment it out, etc).
Now let's see what the output is:
goodbye, world!
Code flow:
1: main::one
2: main::two
3: main::three
Stack trace:
in: main::one
sub: -
file: ./trace.pl
line: 8
package: main
in: main::two
sub: main::one
file: ./trace.pl
line: 19
package: main
in: main::three
sub: main::two
file: ./trace.pl
line: 27
package: main
To remove all traces of the auto install feature, simply:
perl -wMstrict -MDevel::Trace::Subs=remove_trace -e 'remove_trace(file
+ => "trace.pl");'
...and then manually remove the $ENV{DTS_ENABLE} = 1; and trace_dump(); lines from the calling script (again, in this case, it was all done in a single file).
This was the most basic example. I have tested it on my projects that have numerous modules/packages, as well as live files by specifying a directory or Module::Name to the 'file' parameter in the install_trace() CLI call.
install_trace() parameters (only file is mandatory):
- file => 'filename' or dir, or Module::Name
- extensions => [qw(pl pm)] which is the default for dirs
trace_dump() parameters (all are optional):
- want => 'string' where 'string' is either 'flow' or 'stack', which will dump only that portion. Default is both
- type => 'html' dumps the output in HTML table format instead of plain text table format
- file => 'file.ext' the dump output will be placed in this file, regardless of format
TESTING WITH A LIVE MODULE:
Ever wanted to see what a module you use frequently does internally? Let's take Data::Dump:
sudo perl -MDevel::Trace::Subs=install_trace -e 'install_trace(file=>"
+Data::Dump");'
perl -MData::Dump -MDevel::Trace::Subs=trace_dump -e '$ENV{DTS_ENABLE}
+=1; dd {a => 1}; trace_dump'
{ a => 1 }
Code flow:
1: Data::Dump::dd
2: Data::Dump::dump
3: Data::Dump::_dump
4: Data::Dump::tied_str
5: Data::Dump::_dump
6: Data::Dump::format_list
Stack trace:
in: Data::Dump::dd
sub: -
file: -e
line: 1
package: main
in: Data::Dump::dump
sub: Data::Dump::dd
file: /usr/lib/perl5/site_perl/5.22.0/Data/Dump.pm
line: 84
package: Data::Dump
in: Data::Dump::_dump
sub: Data::Dump::dump
file: /usr/lib/perl5/site_perl/5.22.0/Data/Dump.pm
line: 36
package: Data::Dump
in: Data::Dump::tied_str
sub: Data::Dump::_dump
file: /usr/lib/perl5/site_perl/5.22.0/Data/Dump.pm
line: 292
package: Data::Dump
in: Data::Dump::_dump
sub: Data::Dump::_dump
file: /usr/lib/perl5/site_perl/5.22.0/Data/Dump.pm
line: 331
package: Data::Dump
in: Data::Dump::format_list
sub: Data::Dump::dump
file: /usr/lib/perl5/site_perl/5.22.0/Data/Dump.pm
line: 65
package: Data::Dump
sudo perl -MDevel::Trace::Subs=remove_trace -e 'remove_trace(file=>"Da
+ta::Dump");'
CAVEATS:
does not yet catch subs that have an opening brace that is after a newline on the line a sub is defined fixed in v0.06
- this module places a Storable file inside of the directory the base calling script is called from. I'm still trying to figure out a way to make this non-root and cross-platform for scripts that are in root-only writable dirs
- although it does inject into AnonySubs, if the anon sub is a one-liner, it will currently be overlooked (due to not having implemented PPI insertion here yet... next version)
- I'm sure there are other subtle bugs as this is pretty well first version
There are too many todo's to list here as this is first incarnation. I'm hoping some others will find interest and do some real-world testing and tell me how bad the code is, so I can fix those issues while I continue to try to better my coding practice. That said, my biggest two are encompassing more within my PPI regime, and related to that, fixing the insertions/deletions to *all* subs that use all declarative structures, and the removal of such, including newlines added.
DEPENDENCIES:
There are quite a few. The most important are Devel::Examine::Subs v1.43+, Template, HTML::Template, and dependencies on those modules: PPI, Data::Compare and a couple of other small ones.
-stevieb
-
Are you posting in the right place? Check out Where do I post X? to know for sure.
-
Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
<code> <a> <b> <big>
<blockquote> <br /> <dd>
<dl> <dt> <em> <font>
<h1> <h2> <h3> <h4>
<h5> <h6> <hr /> <i>
<li> <nbsp> <ol> <p>
<small> <strike> <strong>
<sub> <sup> <table>
<td> <th> <tr> <tt>
<u> <ul>
-
Snippets of code should be wrapped in
<code> tags not
<pre> tags. In fact, <pre>
tags should generally be avoided. If they must
be used, extreme care should be
taken to ensure that their contents do not
have long lines (<70 chars), in order to prevent
horizontal scrolling (and possible janitor
intervention).
-
Want more info? How to link
or How to display code and escape characters
are good places to start.