Feature Focus: “Metrics Resurrections”, Episode 4

By Jérôme Vieilledent, on Jan 03, 2022

Blog post series index:

The Metrics, Episode 1
Metrics Reloaded, Episode 2
Metrics Revolutions, Episode 3
Metrics Resurrections, Episode 4 (you are here)

Getting More Precision with Arguments Capturing

In the Call Graph view of a Blackfire profile, function calls are aggregated into nodes. These nodes hold information about the resources consumed by their related functions, regardless of their callers or their arguments. This representation makes the understanding of the application flow easier, answering a common question for a developer: “How my application is behaving for a given task?”.

The counterpart of this approach is that you get little information on how each function is called as you’re missing the arguments. This can be problematic in some cases where you need to differentiate similar function calls.

A classic example is the Symfony Event Dispatcher. The EventDispatcher::dispatch() function can be called with many different values as the first argument, which represents the name of the dispatched event. Another example is PDOStatement::execute() which receives the SQL query to run. In such cases, it is critical to discriminate function calls with their arguments in order to have the best insight of your application behavior. This is where Arguments Capturing comes in.

Arguments Capturing: A Primer

Arguments Capturing enables you to discriminate function calls depending on their arguments. Blackfire will display as many nodes as variations exist, based on the captured arguments.

Let’s illustrate with an example. Consider you have a Greetings class with a phrase() method where the first argument is the greeting type (e.g. Hello, Bonsoir, …) and the second argument the actual phrase.

namespace App\Utils;

class Greetings
{
    public function phrase(string $greeting, string $extra): string
    {
        usleep(500000);
        return sprintf('%s %s', $greeting, $extra);
    }
}
$greetings = new App\Utils\Greetings();
$greeting1 = $greetings->phrase('Hello', 'there...');
$greeting2 = $greetings->phrase('Hi', 'there...');

We can discriminate these calls in order to get 2 nodes, by adding the following configuration in .blackfire.yaml file

metrics:
    greetings:
        label: Greetings phrases
        matching_calls:
            php:
                -
                    callee:
                        selector: "=App\\Utils\\Greetings::phrase"
                        argument:
                            1: "*"

The “*” character is a “catch-all” selector, which means that Blackfire will create as many nodes as different values are passed during the application flow. Other selectors exist, such as “Starts-with” or “RegExp” selectors.

The result will be:

Icing on the cake, you will be able to search function calls by captured argument in the Functions list on the left of the call graph.

Multiple arguments capturing

Now, what if we wanted to discriminate on the 2 arguments of Greetings::phrase()? Add a new line in the .blackfire.yaml file:

metrics:
    greetings:
        label: Greetings phrases
        matching_calls:
            php:
                -
                    callee:
                        selector: "=App\\Utils\\Greetings::phrase"
                        argument:
                            # RegExp selector.
                            # Discrimitates "Hello" and "Hi" values for 1st argument.
                            # Any other value will be aggregated in a regular node.
                            1: "/^(Hello|Hi)/"
                            2: "*"
$greetings = new App\Utils\Greetings();
$greeting1 = $greetings->phrase('Hello', 'there...');
$greeting2 = $greetings->phrase('Hello', 'world!');
$greeting3 = $greetings->phrase('Hi', 'there...');
$greeting4 = $greetings->phrase('Hi', 'everyone!');

One more thing…

Yes, there is always one more thing 🤓.

Arguments to be captured can also be associative arrays (aka hashes)!

namespace App\Utils;

class Greetings
{
    public function phrase(array $greeting): string
    {
        usleep(500000);
        return sprintf('%s %s', $greeting['greetings'], $greeting['phrase']);
    }
}
metrics:
    greetings:
        label: Greetings phrases
        matching_calls:
            php:
                -
                    callee:
                        selector: "=App\\Utils\\Greetings::phrase"
                        argument:
                            # Capture is based on hash map keys.
                            1.greetings: "/^(Hello|Hi)/"
                            1.phrase: "*"

Happy profiling!

Jérôme Vieilledent

As a Developer Advocate, Jérôme is all about spreading the love! His technical/development background enable him to talk as a peer to peers with our developer users. You’ll read his tips and advices on performance management with Blackfire. And he’ll support you as a day-to-day user.