Day 17 - Doing more with the SDK

The PHP SDK makes it easy to programmatically control Blackfire. We have already covered how to leverage the SDK in unit tests and how to use the SDK to profile consumers and daemons in previous chapters. This chapter outlines several advanced usages of the PHP SDK. Hopefully these examples will inspire you to create your own implementations. Your imagination is really the only limit here.

Monitoring Consumers Performance

In a previous chapter, we used the LoopClient class to instrument consumers:

1
2
3
4
5
6
7
8
9
for (;;) {
    $blackfire->startLoop($profileConfig);

    consume();

    $blackfire->endLoop();

    usleep(400000);
}

If you read the documentation about how to profile consumers, you should be familiar with using signals to instrument your production code. It works like this:

1
2
3
4
5
6
use Blackfire\LoopClient;

$blackfire = new LoopClient(new Client(), 10);
$blackfire->setSignal(SIGUSR1);
$blackfire->attachReference(7);
$blackfire->promoteReferenceSignal(SIGUSR2);
  • When you start the consumer process, it behaves as usual with no overhead: Blackfire does not instrument the code and the calls to startLoop() and endLoop() are effectively no-ops. This is perfect for production.

  • When the process receives the configured signal (SIGUSR1 in the example), Blackfire instruments the code for the next 10 iterations (as configured above), generates a profile, and disables instrumentation again.

    When Blackfire's servers receive the profile, associated tests are run.

    The generated profiles will also be automatically linked to the reference (7) and comparison tests are run as well.

  • When signaling the process with SIGUSR2, a profile is generated like with the main signal, but the new profile becomes the new reference profile.

    Signal the consumer with SIGUSR2 whenever you deploy a new version of the code. Add it as a step in your deployment script and get consumer profiling automation for free.

Whenever you want to better understand the status of one of your consumers, signal it once and be done. That gives you on-demand profiles, but how can you achieve scheduled builds for consumers? As you might have guessed, this is just a matter of configuring a cron job:

1
2
# generate a profile every hour
0 * * * * pkill -SIGUSR1 -f consumer.php

Of course you want to be notified whenever some tests fail. This can be easily achieved by creating builds and configuring a notification channel to receive build statuses and reports.

The PHP SDK provides everything you need to start builds programmatically (see below), but LoopClient makes it even easier. Simply call generateBuilds() and pass it the environment name or UUID:

1
$blackfire->generateBuilds('ENV_NAME_OR_UUID');

And you have it: a fully automated way to continuously profile consumers, monitor their intrinsic performance, and their performance evolution over time.

Instrumenting Code Manually

When using the Companion or the Blackfire CLI tool to trigger a profile, there is no need to change the code. Blackfire takes care of everything for you. But what if you want to profile only part of the executed code? That's possible by explicitly marking the code you want to profile.

Caution

Do not confuse using the PHP SDK to manually create profiles (which does not need the Companion or the CLI tool) with manually instrumenting the code with the SDK, which helps Blackfire generate precise profiles when triggered by the Companion or CLI tool.

First, retrieve the current probe:

1
2
// Get the probe main instance
$probe = \BlackfireProbe::getMainInstance();

Then, call enable() to start the profiling and disable() to stop it:

1
2
3
4
5
6
7
// start profiling the code
$probe->enable();

// code that you want to profile

// stop the profiling
$probe->disable();

Tip

You do not need to install the Blackfire SDK to benefit from this feature as the BlackfireProbe class is part of the Blackfire C extension.

You can mark as many sections as you want, but be warned that all sections are going to be part of the same call graph. Including too many unrelated sections will generate a convoluted, useless profile.

As soon as you explicitly call enable() in your code, Blackfire understands that you want to control what to instrument and disables auto-instrumentation.

As with auto-instrumentation, profiling is only active when the code is run through the Companion or the Blackfire CLI utility. If a profile has not been triggered, calls to the probe are just ignored.

Web frameworks provide a common abstraction to handle HTTP requests that can be quite noisy on a call graph. When debugging a performance issue, you might want to focus on the executed controller code only. Using manual instrumentation can be useful; here is an example of a PSR-7 middleware that you can use with Zend Expressive:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Stratigility\MiddlewareInterface;

class BlackfireMiddleware implements MiddlewareInterface
{
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null)
    {
        $probe = null;
        if (class_exists('BlackfireProbe', false)) {
            $probe = BlackfireProbe::getMainInstance();
        }

        if (null !== $out) {
            $probe and $probe->enable();
            $response = $out($request, $response);
            $probe and $probe->close();
        }

        return $response;
    }
}

The same technique can be used to profile HTTP requests handled by PHP servers like ReactPHP. Blackfire's PHP SDK provides a helper class to ease the integration, which is detailed in this blog post.

Instrumenting outgoing API Calls

Some projects consist of a set of micro services written in PHP, all communicating via HTTP. When profiling a user-facing HTTP request for such a project, the call graph contains many HTTP calls for which Blackfire only displays the URL and the wall time. If one of the requests is slow, you need to manually trigger another profile against that microservice in order to understand the bottlenecks.

Wouldn't it be great if you could automatically generate profiles for these API calls? The implementation depends on the HTTP library you are using to make your API calls, but the general idea looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$probe = \BlackfireProbe::getMainInstance();

if ($probe->getResponseLine()) {
    $client = new \Blackfire\Client();

    $request = $client->createRequest();
    $header = 'X-Blackfire-Query: '.$request->getToken();

    // add the header to the external HTTP request call
}
  • Line 1: Get the probe for the current HTTP request;
  • Line 3: Make sure that a profile was actually requested - if not, there is nothing to do;
  • Line 6: Create a new profile request;
  • Line 7: Generate an HTTP header containing the signature needed to trigger a new profile;
  • Line 9: Finally, add this header to the outgoing request to trigger an additional profile for the sub-request.

Tip

If you are using Guzzle, you can simplify the code as Blackfire provides a native integration:

1
2
3
4
5
6
7
8
$options = array();

$probe = \BlackfireProbe::getMainInstance();
if ($probe->getResponseLine()) {
    $options['blackfire'] = true;
}

$response = $guzzle->request('GET', $url, $options);

You can find more information about profiling HTTP requests with the PHP SDK in the documentation.

Generating Builds programmatically

Creating builds via the SDK is another very powerful feature, which looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// create a build
$build = $blackfire->createBuild('example_env', array('title' => 'Build from PHP'));

// add some profiles to the build, see below

// end the build and fetch the report
$report = $blackfire->endBuild($build);

// print the report URL
print $report->getUrl();

Attaching profiles to a build can be done via the profile configuration:

1
2
3
4
5
// create a configuration
$config = new \Blackfire\Profile\Configuration();

// attach the build
$config->setBuild($build);

How do you generate profiles now? There are so many ways that it is up to you!

Use the SDK to generate profiles for the current executed code:

1
2
3
4
5
6
7
8
// generate a profile via the SDK
$probe = $blackfire->createProbe($config);

// some PHP code you want to profile

$blackfire->endProbe($probe);

// generate some other profiles if that makes sense

Or use Guzzle for HTTP requests:

1
2
3
4
// generate a profile with Guzzle
$response = $guzzle->request('GET', $url, array(
    'blackfire' => $config,
));

Conclusion

Blackfire exposes a lot of features through the PHP SDK. This chapter showed you various ways to use the SDK to solve advanced use cases.

The next logical step for the last recipe would be to generate dynamic scenarios and store the results in build, taking flexibility to the next level. Of course, this is a great topic for the next chapter.