NAME
Devel::Agent - Agent like debugger interface
SYNOPSIS
perl -d:Agent -MDevel::Agent::EveryThing myscript.pl
DESCRIPTION
For years people in the perl commnity have been asking for a way to do performance monitoring and tracing of runtime production code. This module attempts to fill this role by implementing a stripped down debugger that is intended to provide an agent or agent like interface for perl 5.34.0+ that is simlilar in nature to the agent interface in other langagues Such as python or java.
This is accomplished by running the script or code in debug mode, "perl -d:Agent" and then turning the debugger on only as needed to record traching and performance metrics. That said this module just provides an agent interface, it does not act on code directly until something turns it on.
Agent interface
The Agent interface is implemented via an on demand debugger that can be turned on/off on the fly. Also it is possible to run multiple different debugger instances with diffrent configurations on different blocks of code. This was done intentionally as perl is fairly complex in its nature and an agent interface isn't very useful unless it is flexible.
The agent interface itself is activated by setting $DB::Agent to an instance of itself.
To turn tracing on:
Make sure you either start your perl script -d:Agent or set PERL5OPT="-d:Agent" before launching your script
Inside you script you can issue the following
use Data::Dumper;
my $db=DB->new(
save_to_stack=>1
);
$db->start_trace;
# run some code
...
# and we are done wathcing
$db->stop_trace;
# to dump a very human readable full trace
print Dumper($db->trace);
But altering you code is far from ideal.
Another option is to load the agent and a module that pre-configures it for use such as the Devel::Trace::EveryThing module, wich provides a stack trace to STDERR in real time as frames begin and exit.
Example using: Devel::Trace::EveryThing
perl -Ilib -d:Agent -MDevel::Agent::EveryThing examples/everything.pl
Classes that are Agent Aware
Any class that implements the $instance->___db_stack_filter($agent,$frame,$args,$raw_caller) can filter its own current frame prior to execution, or even prevent the frame from being traced at all.
The ___db_stack_filter method is expected to return true, if the call returns false, then the frame should not be traced. Since the frame passed in before it's runtime execution, the duration value will not be set.
A basic implementation that exposes only the top level calls is defined in Devel::Agent::AwareRole. Loading this role into your class will hide all calls made by your class, but not calls made directly to it, this includes child classes that make calls to other classes.
Example:
package My::Class::That::IS::Mostly::Hidden;
use Role::Tiny::With; # you can also use Moo Moose or other role implementations
with 'Devel::Agent::AwareRole';
1;
If you want to force a class to not show its internals.. say a class like LWP::UserAgent.
use LWP::UserAgent;
reuqire Devel::Agent::AwareRole;
# now only the top level calls to LWP::UserAgent will show up
*LWP::UserAgent::___db_stack_filter=\&Devel::Agent::AwareRole::___db_stack_filter;
Or if you need to disable filtering on a class that has filtering then you can do the opposite
*LWP::UserAgent::___db_stack_filter=sub { 1}
To be fully ignored
*LWP::UserAgent::___db_stack_filter=sub { 0}
Frame information
The following hash represents what is provided as a representation of a frame
{
caller_class=>'main', # class that called this class
calls=>[], # child frames, empty unless $self->save_to_stack is true
class_method=>'main::test_a', # the resolved class::method
depth=>1, # stack depth, 1 is considered the root
duration=>undef|Float, # how long the frame took to execute, only defined when the frame has executed
end_id=>undef|Int, # frame final execution order where in the stack it ended
line=>2, # line number the frame was called from
no_frame=>0|1, # when true, this frame would have been filtered but was included for completeness
order_id=>1, # inital frame execution order, where in the stack it started
owner_id=>0, # which order_id frame triggered the execution of this frame
raw_method=>'main::test_a', # un-resolved method name
source=>'test.pl', # the source file
t0=>[0,0], # Frame Start timestamp in: epoch, microseconds
}
DB Constructor options
This section documents the %args the be passed to the new DB(%args) or DB->new(%args) call. For each option documented in this section, there is an accesor by that given name that can be called by $self->$name($new_value) or my $current_value=$self->$name.
level=>ArrayRef
This is an auto generated array ref that is use by the internals to track current stack level state information.
resolve_constructor=>Bool
This option is used to turn on or off the resolution of a class name when being constructed and other situations. By default this option is set to true.
trace=>[]
When the object instance is constructed with save_to_stack=>1 ( default is: 0 ) then the stack trace will be saved into a single multi tier data structrure represented by $self->trace.
ignore_calling_class_re=>ArrayRef[RegexpRef]
This option allows a list of calling calsses to be ignored when they match the regular expression.
Example ignore_calling_class_re being set to [qr{^Do::Not::Track::Me::}] will prevent the debugger for trying to trace or record calls made by any methods within "Do::Not::Track::Me::". This does not prevent this class from showing up fully ina stack trace. If this class calls a class that calls another class that calls a class unlisted in ignore_calling_class_re, then Do::Not::Track::Me::xxx will show up as the owner frame of the calls as a biproduct of correctness in stack tracing.
excludes=>HashRef[Int]
This is a collection of classes that should be ignored when they make calls. The defaults are defined in @DB::EXCLUDE_DEFAULTS and include classes like Data::Dumper and Time::HiRes to name a few. For a full list of the current defaults just perl -MDevel::Agent -MData::Dumper -e 'print Dumper(\@DB::EXCLUDE_DEFAULTS)'
last_depth=>Int
This value is used at runtime to determine the previous point in the stack trace.
depths=>ArrayRef
This is used at runtime to determin the current frame stack depth. Each currently executing frame is kept in order from top to the bottom of the stack.
order_id=>Int
This option acts as the sequence or order of execution counter for frames. When a frame starts $self->order_id is incremented by 1 and set to the frame's oder_id when the frame has completed execution the current $self->order_id is incremented again and set to the frame's end_id.
save_to_stack=>Bool
This option is used to turn on or of the saving of frames details in to a layered structure inside of $self->trace. The default is 0 or false.
on_frame_end=>CodeRef
This code ref is called when a frame is closed. This should act as the default data streaming hook callback. All tracing operations are halted durriong this callback.
Example:
sub { my ($self,$last)=@_; # $self: An instance of DB # $last: The most currently closed frame }
trace_id=>Int
This method provides the current agent tracing pass. This number is incremented at the start of each call to $self->start_trace.
ignore_blocks=>HasRef[Int]
This hashref reprents what perl phazed blocks to ignore, the defaults are.
{ BEGIN=>1, END=>1, INIT=>1, CHECK=>1, UNITCHECK=>1, }
The default values used to generate the hashref contained in in @DB::@PHAZES
constructor_methods=>HashRef[Int]
This is a hash of method names we consider object constructors
Default:
{ # we assume new is an object constructor new=>1 }
max_depth=>Int
When the value is set to something other than -1, the number represents the maxium stack depth to trace too. Once the frame of max_depth exits, then max_depth will again be set to -1.
tid=>Int
This is mostly here for completeness, retuns the tid id this debugger was created in.
Note Note Note:
The code was not orginally develpoed without threading in mind, if you wish to trace elemetns within a thread make sure you start an instance of the debugger within that thread.
agent_aware=>Bool
This enables or disables the use of $self->_agent_aware(...) method. See: $self->_agent_aware. Default is true.
existing_trace=>Maybe[InstanceOf['DB']]
This acts as a save point for another existing trace to be exected.
process_result=>CodeRef
This callback can be used to evaluate/modify the results of a traced method in this callback.
Example callback
sub { my ($self,$type,$frame)=@_; # $self: Instance of Devel::Agent # $type: -1,0,1 # $frame: The current frame }
Notes on $type and where the current return values are stored
When $type is: -1 This is in a call to DESTROY( the envy of the programming world! ) Return value is in $DB::ret
0 This method was called in a scalar context Return value is in $DB::ret 1 This method was called in a list context Return value is in @DB::ret
filter_on_args=>CodeRef
This allows a code ref to be passed in to filter arguments passed into a method. If the method returns false, the frame is ignored.
Default always returns true
sub { my ($self,$frame,$args,$caller)=@_; # $self: Instance of Devel::Agent # $frame: Current frame hashref # $args: The @_ contents # $caller: The contents of caller($depth) return 1; }
This is more or less a global frame filter, see: Classes that are Agent Aware
pid=>Int
By default this is set to $$
API Methods
This section documents general api methods.
Bool=$self->_filter($caller,$args)
This method is the core interface used to decide if a method should show up in the debug/stack trace.
This is where the following constructor options are applied:
ignore_calling_class_re
The $self->ignore_calling_class_re is applied to each calling or caller->[0], if it matches the frame is ignored.
excludes
If $caller->[0] matches an element of excludes, then this frame is ignored.
resolve_constructor
This converts the "class" portion of the frame value class_method to the first argument of a method that matches anything found in resolve_constructor.
excludes
The updated class value is applied to excludes again, if it matches, then the frame is not traced.
$self->resolve_class($class,$method,$caller,$args)
Changes $class to the constructor class if $self->resolve_constructor && exists $self->constructor_methods->{$method}
$self->close_depth($depth)
After a frame ahs finished execution, this method is called. This removes the frame from $self->depths
Sets the following frame option:
error the $@ state
end_id the order_id this frame ended on
duration Execution time in micro seconds
my $id=$self->next_order_id
Returns the next order_id.
$self->close_to($depth,$frame|undef)
Closes down the stack trace to $depth, performs addtional actions if $frame is defiend.
$self->filter($caller,$args)
This method is called by the DB method after the sub method and is used to place a frame on the stack for tracing.
$self->_agent_aware($frame,$args,$raw_caller)
This method is called before a frame is traced if $self->agent_aware is set to true( the default ). The objective of this method is see of the first argument being passed to this method is a blessed instance of that class. When the first argument passed to the class is a blessed object that DOES this class then a call to $args->[0]->___db_stack_filter($agent,$frame,$args,$raw_caller) is made. This allows classes to modify or inspect the frame that will be used in the reace. If the call to $args->[0]->___db_stack_filter($agent,$frame,$args,$raw_caller) returns false, the frame is skipped durring the trace period.
Note note Note:
The call to $args->[0]->___db_stack_filter($agent,$frame,$args,$raw_caller) is never wrapped in a eval and should never do anything heavy or it will impact the metrics provided by the debugger.
Self->save_to($frame)
Saves the frame to the stack trace at the proper depth.
$self->push_to_stack($frame)
This method handles the saving, closing and backfilling logic for a given frame.
my $depth=$self->get_depth
Returns undef or a number representing the stack depth. When the value is undef, the trace would exceed $self->max_depth;
my $frame=$self->caller_to_ref($caller,$depth|undef,$raw_method,$no_frame)
Returns a frame hashref. If $depth is not defined a call to $depth is made, if it exceeds a set value of $self->max_depth undef is returned.
$self->reset
Rsets the internals of the object for starting a new stack trace.
$self->start_trace
Begins the stack trace process.
$self->stop_trace
Ends the current stack trace process.
$self->pause_trace
Turns tracing off until a call to $self->restore_trace is made
$self->restore_trace
Turns tracking back on.
$self->close_sub($res)
This method is called by the sub method. It is used to send notice that a frame has finished execution.
my $frames=$self->grab_missing($depth,$frame);
Returns any skiped frames between $depth and $frame.
$depth is expected to be a number and $frame expected to be a frame hash.
DB(@_)
This method is manditory for the implemntation of a debugger. With the current perl internals it is called every time a breakable point of code is reached. Unfortunatly this overhead is unavoidable.
Example:
foreach(1,3,4) { # Debugger would normally stop here
someMethod($_) # agent tracing only happens when your function is called
}
# total calls would be 4 + 3 * 2
# 1 call when foreach is reached
# 1 call for each element in the loop
# 2 times for every method
The as an optimization the agent ensures that tracing only happens on user defined functions. All other operations should be ignored.
The optimization reduces the number of calls to 3 or just once per method.
sub(@_)
THis is a manditory for implementation, this method is called twice per user defined method. Once before the execution once to actually run the function.
Example:
foreach(1,3,4) {
someMethod($_) # sub is called 2 times per function
}
# calls to sub 6
If $AGENT is undef, this method does nothing.
Compile time notes
For perl 5.34.0+
When loading this moduel All features of the debugger are disabled aside from: ( 0x01, 0x02, and 0x20 ) which are requried to force the execution of DB::DB. Please see the perldoc perlvar and the $PERLDB section.
Which means: $^P==35
RUNTIME
At runtime, this modue tries to exectue $Devel::Agent::AGENT->filter($caller,$args). If $Devel::Agent::AGENT is not defined, then nothing happens.
$caller: is the caller information
$args: contains an array reference that represents the arguments passed to a given method
TODO
1. Add Dancer2 trace implementation/example
AUTHOR
Michael Shipper AKALINUX@CPAN.ORG
Silly stuff
Please forgive the typos, this was written on holiday in my spare time.
LICENSE
This code is released under the terms of the perl5 licence itself. Please see LICENSE.md for more details.
See Also
Lots of these internals are based on the following: