2

I would like to invoke a function every time a function is run and another when the original function called is finished

Here are the functions I want to run:

$start = null;
$stack_trace = [];

// Function that runs when another function is called.
public function start_tracking(&$start) {
    $start = microtime(true);
}

// Function that runs when another function is ended.
public function end_tracking($start, &$stack_trace, $function_name) {
    $stack_trace[$function_name] = gmdate('H:i:s', microtime(true) - $start);
}

Why?

I want to track time between functions, and while I could do this manually:

$start = microtime(true);
DB::statement('REFRESH MATERIALIZED VIEW CONCURRENTLY mvw_ruptura_acumulada');
Log::debug('refresh mvw_ruptura_acumulada -> '. gmdate('H:i:s', ceil(microtime(true) - $start)));

It would be far better to do this automatically so its easier, doesn't make the code dirty with lots of calls for microtime and also to track internal function calls that my framework might call as well.

I tried to search for this functionality but couldn't find anything, so I am trying here.

Mathias Hillmann
  • 1,537
  • 2
  • 13
  • 25

1 Answers1

2

Measuring execution time is the domain of profilers. In PHP, you could use Xdebug to do so. See Simplest way to profile a PHP script for other suggestions. In any case, a profiler is what you want to use. It's a professional tool for the purpose.

As an alternative, you could invest into an observability tool, like Instana or Tideways. There are others. While observability is somewhat different from profiling, both tools come with code profiling capabilities to complement their observability features. Full disclosure: I work for Instana and am friends with the owner of Tideways.

I am not aware of a good userland no-code approach to adding interceptors to function calls. There are libraries like Componere, runkit or uopz but I don't think you'll get very far with them and you will need to install an extension. If given the choice between installing Xdebug or one of these, I'd go with Xdebug.

A low code approach might be to use attributes as pointed out in the comments to your question. That will still require you to heavily touch up your code because every line of code you want measured needs to have the attribute.

Apart from that, you could hack something together in a namespace:

<?php

namespace My;

foreach (get_defined_functions()['internal'] as $function) {
    if ($function === 'assert') continue;
    $declaration = sprintf(
        'namespace My; function %s() {
            $start = \microtime(true);
            $retval = \call_user_func_array("%s", \func_get_args());
            echo \microtime(true) - $start, PHP_EOL;
            return $retval;
        }', 
        $function,
        $function,
        $function,
        $function
    );
    eval($declaration);
};

echo strtotime('today');

This will output something like

6.9141387939453E-5
1624485600

Demo: https://3v4l.org/r9Rpd

This will create a function for every internal PHP function in the namespace My, wrapping that internal function. You can insert any code you want to run before and after the function in the function declaration.

PHP will first check the current namespace for a defined function before falling back to looking into the global namespace. Thus, when calling strtotime, it will invoke My\strtotime.

There are probably plenty of caveats I didn't think through with this approach. I am not recommending to use that. As I said in the beginning: use a profiler.

Gordon
  • 312,688
  • 75
  • 539
  • 559