Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Tool to record subroutine calls & return values

by Yary (Pilgrim)
on Apr 09, 2017 at 18:14 UTC ( [id://1187528]=perlquestion: print w/replies, xml ) Need Help??

Yary has asked for the wisdom of the Perl Monks concerning the following question:

Is there a profiler, debugger, coverage tool or other, which can record sub calls with their arguments and the return values of those calls? From what I've browsed on MetaCPAN, there are a few modules that will tally and show all the subroutines a program calls during a run, but I haven't yet found one with an option to show the calling parameters & return values.
  • Comment on Tool to record subroutine calls & return values

Replies are listed 'Best First'.
Re: Tool to record subroutine calls & return values
by haukex (Archbishop) on Apr 09, 2017 at 18:37 UTC

    A quick search on CPAN brought me to Devel::CallTrace, which ties into the Perl debugger mechanism, but doesn't provide arguments and return values (although the code seems short enough that I guess it could probably be extended). Then there's Debug::Trace, which works by simply wrapping the requested subs with code that prints their arguments and return values:

    $ cat foo.pl #!/usr/bin/env perl use warnings; use strict; sub foo { bar($_[0] x 3)."-".quz(ord($_[0])) } sub bar { $_[0].length($_[0]) } sub quz { $_[0].$_[0] } print foo($_), "\n" for 'a'..'b'; $ perl -MDebug::Trace=foo,bar,quz foo.pl TRACE: main::foo("a") called at foo.pl line 11 package main TRACE: main::bar("aaa") called at foo.pl line 5 sub main::foo TRACE: main::bar() returned: "aaa3" TRACE: main::quz(97) called at foo.pl line 5 sub main::foo TRACE: main::quz() returned: 9797 TRACE: main::foo() returned: ("aaa3-9797") aaa3-9797 TRACE: main::foo("b") called at foo.pl line 11 package main TRACE: main::bar("bbb") called at foo.pl line 5 sub main::foo TRACE: main::bar() returned: "bbb3" TRACE: main::quz(98) called at foo.pl line 5 sub main::foo TRACE: main::quz() returned: 9898 TRACE: main::foo() returned: ("bbb3-9898") bbb3-9898

    At the bottom of the Devel::CallTrace doc, there are a few more references to other modules.

    Update: Minor updates to wording.

Re: Tool to record subroutine calls & return values
by stevieb (Canon) on Apr 09, 2017 at 18:48 UTC

    Not the most versatile option, but thought I'd throw it out there. I wrote Wrap::Sub quite a while ago as a learning experience that can do this kind of thing. It works by running code before the actual sub is called where you can access the incoming parameters then do work before the actual sub is called, then runs another chunk of code after the real sub is called, where the return value(s) of the sub are received (which again, you can do more work on if desired).

    You must list all of the subs you want to wrap (if subs in other packages are desired, specify the full package name of the sub (eg 'Foo::Bar::sub_name').

    use warnings; use strict; use Data::Dumper; use Wrap::Sub; my $ws = Wrap::Sub->new( pre => sub { print "\n$Wrap::Sub::name\n"; print "args:\n"; print Dumper \@_; }, post => sub { print "return:\n"; print Dumper $_[1][0]; } ); my @wrapped; for (qw(foo bar)){ push @wrapped, $ws->wrap($_); } foo(a => 1, b => 2); bar(10); sub foo { my %hash = @_; return [1, 2, 3]; } sub bar { my $x = shift; return $x ** 2; }

    Output:

    main::foo args: $VAR1 = [ 'a', 1, 'b', 2 ]; return: $VAR1 = [ 1, 2, 3 ]; main::bar args: $VAR1 = [ 10 ]; return: $VAR1 = 100;

    Of course, the formatting of the output isn't very creative, but that's simple to fix to your liking within the pre and post subs (which are simple code references, so you can define them anywhere, not necessarily within the init call). Note that you can also wrap all subs within a module in one pass as well: my $module_subs = $ws->wrap('My::Module');, so you can selectively wrap entire modules in a loop as opposed to individual subs like I did in the above example.

Re: Tool to record subroutine calls & return values
by LanX (Saint) on Apr 09, 2017 at 19:51 UTC
Re: Tool to record subroutine calls & return values
by AnomalousMonk (Archbishop) on Apr 09, 2017 at 18:31 UTC

    First thought: This sounds like an XY Problem. For any but a very small script, the torrent of parameter and return values from every subroutine call executed would be enormous and likely unusable. Typically, one is interested in the behavior of only a few critical subroutines. For this, print points or use of the Perl debugger (see perldebug and perldebtut) would usually be the tool of choice. Can you tell us more about why you want all call/return info?


    Give a man a fish:  <%-{-{-{-<

      Indeed it is an X/Y problem.

      I'm thinking about recording runs of a large legacy codebase - before and after making changes to it - as an aid to writing tests- which don't exist for this codebase. A record of which subs are actually being called, with which arguments & return values, is a good starting place showing what's in use = a starting place showing what to test.

        A record of which subs are actually being called ...

        This sounds like a job for Devel::NYTProf or another of its ilk. Other monks than I will be better able to advise on the proper module and its employment.

        ... recording runs of a large legacy codebase - before and after making changes to it - as an aid to writing tests- which don't exist for this codebase. A record of ... which arguments & return values, is a good starting place showing what's in use = a starting place showing what to test.

        This seems a bit bass-ackwards to me. The critical starting point for testing code is understanding the code (update: and its specification). This will be a huge task for a large, unfamiliar codebase. Writing tests for such a codebase will be an even hugerer task because you have to understand all the boundary cases of the invocation of each function. (Much of the time and effort of such a project may be spent cursing the name of the original coder who paid no attention to testing.)

        And here's another problem: How do you know that the application is running properly right now? Do you have a way to evaluate an application run that tells you its output is "correct," or are you just depending on the fact that the program didn't blow up or your company go out of business to assure you that, well, everything's probably OK? Generating a huge database of input arguments/return values for each subroutine may be useless or worse if it captures and enshrines unrecognized incorrect behavior.

        Refactoring a large application in the absence of a thorough test suite is a big job, and you're quite right to think that the first task is to create such a test suite. Unfortunately, there are no easy answers, and in particular, a database of assumed normal operation for each subroutine can be no more than an adjunct to a deep understanding of the application. Good luck.

        Update: Looking back over this post, it just seems like a very verbose way of saying "You're gonna need a bigger boat!" and perhaps isn't very helpful in consequence, which I regret. (But I still wish you luck!)


        Give a man a fish:  <%-{-{-{-<

        You do not need to know what's going in or coming out of each sub to begin writing a test suite.

        Knowing the code flow/stack trace is way enough to begin, and you don't even need to know that really.

        Start from the very top of your legacy code base. Write tests for the very first function encountered, with expected in and check the out. If there are subs called within the sub, mock them out to return what you *believe* it currently should so that the flow goes ok. Trickery may be required in the tests if your software does side-effects within its subs, but I digress. This is one of the reasons side-effects can be a pain in the ass.

        Do this for each sub in the entire process flow. You do not need to change absolutely anything until every single current sub is tested. At the point you've tested them all, knowing what to mock and what to allow to run through, you'll have a full test suite that will allow you to start a re-write. Write the whole shebang anew, or set up a dev environment to start making modifications. Your test suite will catch any problems now, and this is where you start expanding on the test suite you've already started.

        Most of my Perl early years was spent writing software to deal with issues exactly like this, and since then, I've never stopped. Writing code to help other developers develop is my favourite thing to do with Perl, and a good number of my own modules on the CPAN are geared around development aids and test/build work.

Re: Tool to record subroutine calls & return values
by clueless newbie (Curate) on Apr 10, 2017 at 13:42 UTC

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1187528]
Approved by haukex
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (6)
As of 2024-04-24 07:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found