The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Devel::Probe - Quick & dirty code probes for Perl

VERSION

Version 0.000006

SYNOPSIS

    use Devel::Probe;
    ...
    Devel::Probe::trigger(sub {
        my ($file, $line, $argument) = @_;
        # probe logic
    });
    Devel::Probe::config(\%config);
    ...
    Devel::Probe::enable();
    ...
    Devel::Probe::disable();

DESCRIPTION

Use this module to allow the possibility of creating probes for some lines in your code. These probes can be used to build things like debuggers, fault injection tests, or production observability tooling.

The probing code is installed when you import the module, but it is disabled. In these conditions, the probe code is light enough that it should cause no impact at all in your CPU usage; an impact might be noticed when you enable the module and configure some probes, particularly depending on the frequency with which those probes will be triggered, and how heavy the trigger callback turns out to be.

FUNCTIONS

  • trigger(\&coderef)

    Specify a piece of Perl code that will be called for every probe that triggers.

  • config(\%config)

    Specify a configuration for the module, including what lines in your code will cause probes to be triggered. This call will always disable the module as a first action, so you always need to explicitly enable it again, either from the configuration itself or in a further call to enable().

  • add_probe($file, $line, $type, $callback_argument)

    Manually add a probe. This is what gets called from config() when adding probes; please see the CONFIGURATION example for more information.

  • enable() / disable() / is_enabled()

    Dynamically activate and deactivate probing, and check this status. When disabled, Devel::Probe will have minimal overhead, and probes will fire again as soon as enable() is called.

  • install() / remove() / is_installed()

    Install or remove the probe handling code, and check this status. When you import the module, install() is called automatically for you.

    When uninstalled, Devel::Probe will have zero overhead, and all probe state is cleared. Probes will not fire again until install() is called and both trigger and probes are redefined.

  • clear()

    Remove all probes.

  • dump()

    Return all probes as a hash.

CONFIGURATION

An example configuration hash looks like this:

    my %config = (
        actions => [
            { action => 'disable' },
            { action => 'clear' },
            { action => 'define' ... },
            { action => 'enable' },
        ],
    );

Possible actions are:

  • disable: disable probing.

  • clear: clear current list of probes.

  • enable: enable probing.

  • define: define a new probe. A full define action looks like:

        my %define = (
            action => 'define',
            type => PROBE_TYPE,
            file => 'file_name',
            lines => [ 10, 245, 333 ],
            argument => $my_callback_argument,
        );

    The type field is optional and its default value is Devel::Probe::ONCE. Possible values are:

    • Devel::Probe::ONCE: the probe will trigger once and then will be destroyed right after that. This default makes it more difficult to overwhelm your system with too much probing, unless you explicitly request a different type of probe.

    • Devel::Probe::PERMANENT: the probe will trigger every time that line of code is executed.

    The argument field is optional and its default value is undefined. Possible values are any Perl scalar. If present, it will be passed to the trigger callback as the third argument.

EXAMPLE

This will invoke the callback defined with the call to trigger(), the first time line 21 executes, taking advantage of PadWalker to dump the local variables. After that first execution, that particular probe will not be triggered anymore. For line 22, every time that line is executed the probe will be triggered.

    # line 1
    use 5.18.0;
    use Data::Dumper qw(Dumper);
    use PadWalker qw(peek_my);
    use Devel::Probe;

    Devel::Probe::trigger(sub {
        my ($file, $line) = @_;
        say Dumper(peek_my(1)); # 1 to jump up one level in the stack;
    });

    my %config = (
        actions => [
            { action => 'define', file => __FILE__, lines => [ 22 ] },
            { action => 'define', file => __FILE__, type => Devel::Probe::PERMANENT, lines => [ 23 ] },
        ],
    );
    Devel::Probe::config(\%config);
    Devel::Probe::enable();
    my $count;
    while (1) {
        $count++;                                   # line 22
        my $something_inside_the_loop = $count * 2; # line 23
        sleep 5;
    }
    Devel::Probe::disable();

As another example, you can pass a custom argument to the trigger callback:

    # line 1
    use 5.18.0;
    use PadWalker qw(peek_my);
    use Devel::Probe;

    Devel::Probe::trigger(sub {
        my ($file, $line, $interesting_var_name) = @_;
        say "$interesting_var_name: " . ${ peek_my(1)->{$interesting_var_name} };
    });

    my %config = (
        actions => [
            { action => 'enable' },
            { action => 'define',
              file => __FILE__,
              type => Devel::Probe::PERMANENT,
              lines => [ 26 ],
              argument => '$squared'
            },
        ],
    );
    Devel::Probe::config(\%config);
    my $count = 0;
    my $squared = 0;
    while (1) {
        $count++;
        $squared = $count * $count; # line 26
        sleep 5;
    }

SUGGESTIONS

For files found directly by the Perl interpreter, the file name in the probe definition will usually be a relative path name; for files that are found through the PERL5LIB environment variable, the file name in the probe definition will usually be a full path name.

One typical use case would be to have a signal handler associated with a specific signal, which when triggered would disable the module, read the configuration from a given place, reconfigure the module accordingly and then enable it. Similarly, this kind of control can be implemented using remote endpoints to deal with reconfiguring, disabling and enabling the module.

TODO

  • Probes are stored in a hash of file names; per file name, there is a hash of line numbers (with the probe type as a value). It is likely this can be made more performant with a better data structure, but that needs profiling.

AUTHORS

  • Gonzalo Diethelm gonzus AT cpan DOT org

  • Ben Tyler btyler AT cpan DOT org