PHP SDK

Installation

The Blackfire PHP SDK can be easily added as a requirement to your project via Composer:

1
composer require blackfire/php-sdk

Note

The Client works with PHP 5.3+.

Tip

The Blackfire Client uses the exact same underlying mechanisms as the Blackfire CLI or the Blackfire Companion; so you need to install the Blackfire PHP extension and configure the Blackfire agent properly.

Blackfire Client

The Blackfire Client implements the Blackfire API. It allows you to generate profiles from your own code and it eases the integration with third-party libraries (like PHPUnit). It can also be used to profile HTTP requests from PHP.

The main entry point of the SDK is the Blackfire\Client class:

1
$blackfire = new \Blackfire\Client();

Profiling

The client object allows you to profile any parts of your code:

1
2
3
4
5
$probe = $blackfire->createProbe();

// some PHP code you want to profile

$profile = $blackfire->endProbe($probe);

The createProbe() method takes an optional Blackfire\Profile\Configuration object that allows you to configure the Blackfire Profile more finely:

1
2
$config = new \Blackfire\Profile\Configuration();
$probe = $blackfire->createProbe($config);

When calling endProbe(), the profile is generated and sent to Blackfire.io servers. The $profile variable is an instance of Blackfire\Profile that gives you access to the generated profile information.

You can store the profile UUID to get it back later:

1
2
3
4
5
// store the profile UUID
$uuid = $probe->getRequest()->getUuid();

// retrieve the profile later on
$profile = $blackfire->getProfile($uuid);

The $probe instance can also be used to instrument precisely only part of your code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$blackfire = new \Blackfire\Client();

// false here means that we want to instrument the code ourself
$probe = $blackfire->createProbe(null, false);

// some code that won't be instrumented

// start instrumentation and profiling
$probe->enable();

// some code that is going to be profiled

// stop instrumentation
$probe->disable();

// code here won't be profiled

// do it as many times as you want
// all profiled sections will be aggregated in one graph
$probe->enable();
$probe->disable();

// end the profiling session
$profile = $blackfire->endProbe($probe);

Note that having many enable()/disable() sections might make your call graph very difficult to interpret. You might want to create several profiles instead.

Tip

If you want to profile the same code more than once, use the samples feature.

Tip

Enabling the instrumentation via the Probe object instead of the Client one is very useful when you integrate Blackfire deeply in your code. For instance, you might create the Blackfire Client in one object and do the profiling in another portion of your code.

Profile

Blackfire\Profile instances (as returned by Client::endProbe()) give you access to generated profiles data:

1
$profile = $blackfire->endProbe($probe);

You can also get any profile by UUID:

1
$profile = $blackfire->getProfile($uuid);

Here is a quick summary of available methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// the profile URL
$profile->getUrl();

// get the main costs
$cost = $profile->getMainCost();

// get SQL queries (returns an array of Cost instances)
$queries = $profile->getSqls();

// get HTTP requests (returns an array of Cost instances)
$requests = $profile->getHttpRequests();

// Cost methods
$cost->getWallTime();
$cost->getCpu();
$cost->getIo();
$cost->getNetwork();
$cost->getPeakMemoryUsage();
$cost->getMemoryUsage();

You can also access the Blackfire test results:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// tests were successful (all assertions pass)
$profile->isSuccessful();

// an error occurred when running the tests (different from assertion failures)
// an error can be a syntax error in an assertion for instance
$profile->isErrored();

// get test results (returns an array of Blackfire\Profile\Test instances)
$tests = $profile->getTests();

// display all failures
if (!$profile->isErrored()) {
    foreach ($tests as $test) {
        if ($test->isSuccessful()) {
            continue;
        }

        printf("    %s: %s\n", $test->getState(), $test->getName());

        foreach ($test->getFailures() as $assertion) {
            printf("      - %s\n", $assertion);
        }
    }
}

Profile Basic Configuration

The Client::createProbe() method takes a Blackfire\Profile\Configuration instance that allows to configure profile generation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$config = new \Blackfire\Profile\Configuration();
$probe = $blackfire->createProbe($config);

// set the profile title
$config->setTitle('Homepage');

// compare the new profile with a reference
// takes the reference id as an argument
$config->setReference(7);

// mark the profile as being a new reference
$config->setAsReference();

// attach some metadata to the profile
$config->setMetadata('pull-request', 42);

Tip

The Configuration class implements a fluent interface:

1
$config = (new \Blackfire\Profile\Configuration())->setTitle('Homepage')->setReference(7);

Profile Assertions Configuration

Besides basic profile configuration, you can also configure assertions:

1
2
3
// set some assertions
// first argument is the assertion, second one is the assertion/test name
$config->assert('metrics.sql.queries.count > 50', 'I want many SQL requests');

Note

To keep the API simple and unlike tests defined in .blackfire.yml, tests defined via assert() only ever contains one assertion.

If you need to, you can create some custom metrics as well:

1
2
3
4
5
6
7
8
use Blackfire\Profile\Metric;

// define a custom metric
$metric = new Metric('cache.write_calls', '=Cache::write');

// add it to the profile configuration
// to  be able to use it in assertions
$config->defineMetric($metric);

When defining a Metric, the first constructor argument is the metric name (cache.write_calls here) which can used in an assertion by prefixing it with metrics. and appending a dimension (like in metrics.cache.write_calls.count).

The second constructor argument is a method selector or an array of method selectors:

1
$metric = new Metric('cache.write_calls', array('=Cache::write', '=Cache::store'));

Profile Samples

When profiling from the CLI or the Companion, you can set the number of samples you want for a profile (to get more accurate results). You can do the same with the Client, via the setSamples() configuration method:

1
$config->setSamples(10);

Be warned, you need to generate the samples manually in your code as Blackfire has no way to do it automatically:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// define the number of samples
$samples = 10;

$blackfire = new \Blackfire\Client();

$config = (new \Blackfire\Profile\Configuration())->setSamples($samples);

// explicitly pass the configuration
// and disable auto-enable
$probe = $blackfire->createProbe($config, false);

for ($i = 1; $i <= $samples; $i++) {
    // enable instrumentation
    $probe->enable();

    foo();

    // finish the profile
    // so that another one will be taken on the next loop iteration
    $probe->close();
}

// send the results back to Blackfire
$profile = $blackfire->endProbe($probe);

Caution

If you do not call $probe->close() the same number of times as the number of configured samples, you will get a 404 (not found) result when calling $blackfire->endProbe().

HTTP Profiling

Using the Blackfire Client, you can create HTTP scenarios directly from PHP. Enabling code instrumentation for HTTP requests is done via a specific HTTP header (X-Blackfire-Query); the value containing the profile configuration and a signature used for authorization.

1
2
3
4
5
$blackfire = new \Blackfire\Client();

// generate the HTTP header to enable Blackfire
$request = $blackfire->createRequest();
$header = 'X-Blackfire-Query: '.$request->getToken();

Specify the profile title via the first argument:

1
$request = $blackfire->createRequest('Homepage');

Or pass a full Blackfire\Profile\Configuration instance:

1
2
$config = (new \Blackfire\Profile\Configuration())->setTitle('Homepage');
$request = $blackfire->createRequest($config);

Get the generated profile from the profile request:

1
$profile = $blackfire->getProfile($request->getUuid());

Learn more about how to use this feature with Guzzle or Goutte.

Error Management

When an error occurs because of Blackfire, a \Blackfire\Exception\ExceptionInterface exception instance is thrown:

  • \Blackfire\Exception\NotAvailableException: The Blackfire PHP extension is not installed or not enabled.
  • \Blackfire\Exception\OfflineException: You are offline or the Blackfire servers are not reachable (check your proxy configuration).
  • \Blackfire\Exception\ApiException: An error occurred when talking to the Blackfire API.
  • \Blackfire\Exception\ConfigNotFoundException: The Blackfire configuration file cannot be found.
  • \Blackfire\Exception\ReferenceNotFoundException: The configured reference does not exist.
  • \Blackfire\Exception\EnvNotFoundException: The configured environment does not exist.

To avoid breaking your code when such errors occur, wrap your code and catch the interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
try {
    $probe = $blackfire->createProbe($config);

    // do something

    $profile = $blackfire->endProbe($probe);
} catch (\Blackfire\Exception\ExceptionInterface $e) {
    // Blackfire error occurred during profiling
    // do something
}

Client Configuration

When creating a Client, Blackfire uses your local Blackfire configuration by default (stored in ~/.blackfire.ini). But you can also set the configuration explicitly:

1
2
3
4
5
6
7
8
// all arguments are optional
$config = new \Blackfire\ClientConfiguration($clientId, $clientToken, $defaultEnv);

// or read it from a file
$config = \Blackfire\ClientConfiguration::createFromFile('some-blackfire.ini');

// use the configuration
$blackfire = new \Blackfire\Client($config);

By default, profiles are sent to your personal profiles, but you can change the environment via the setEnv() method:

1
$config->setEnv('mywebsite');

Storing Profiles in a Build

Note

This feature is only available to our Premium and Enterprise users.

When generating more than one profile, like when you profile complex user interactions with your application, you might want to aggregate them in a build. Using a build has the following benefits:

  • A build contains all profiles from a profiling session (individual profiles are not displayed in the dashboard anymore);
  • A build generates a Report that sends notifications;
  • A build consolidates tests results into a build result;
  • A build written in PHP is a powerful alternative to the scenarios defined in a .blackfire.yml file.

Creating a build is not that different from profiling individual pages as described in the previous paragraphs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// create a build
$build = $blackfire->createBuild('Symfony Prod', array(
    'title' => 'Build from PHP',
    'trigger_name' => 'PHP',
));

// create a configuration
$config = new \Blackfire\Profile\Configuration();
$config->setBuild($build);

// create as many profiles as you need
$probe = $blackfire->createProbe($config);

// some PHP code you want to profile

$blackfire->endProbe($probe);

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

To create a build, call createBuild() and pass it the environment name (or UUID). Optionally pass an array of options:

  • title: A build title;
  • metadata: An array of metadata to associated with the build;
  • external_id: A unique identifier for the build; commonly, a unique reference from a third party service like the Git commit sha1 related to the build;
  • external_parent_id: The unique identifier of the parent build.
  • trigger_name: A trigger name (displayed in the dashboard);

Tip

The external_id and external_parent_id options can be used to

integrate Blackfire with GitHub pull request statuses

To store a profile in the build, call createProbe() and pass it a Configuration object that has been tied to the build ($config->setBuild($build)).

Stop the build by calling endBuild(). The call returns the Report when ready.

Calling the generateBuilds() method automatically configures LoopClient to generate a build for each profile:

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

If you need more flexibility, like setting a different title for each build, pass a Build factory as the second argument:

1
2
3
4
5
use Blackfire\Client;

$this->generateBuilds('ENV_NAME_OR_UUID', function (Client $blackfire, $env) {
    return $blackfire->createBuild($env, array('title' => 'Some generated title'));
});

You can store the build UUID to get report back later:

1
2
3
4
5
// store the build UUID
$uuid = $build->getUuid();

// retrieve the report later on
$report = $blackfire->getReport($uuid);

Tip

HTTP builds can be created with Goutte or Guzzle.

Report

Blackfire\\Report instances (as returned by Client::endBuild()) give you access to builds data:

1
$report = $blackfire->endBuild($build);

You can also get any report by UUID:

1
$report = $blackfire->getReport($uuid);

Here is a summary of available methods:

1
2
3
4
5
6
7
8
9
// the report URL
$report->getUrl();

// tests were successful (all assertions pass)
$report->isSuccessful();

// an error occurred when running the tests (different from assertion failures)
// an error can be a syntax error in an assertion for instance
$report->isErrored();

Controlling Automatic Instrumentation

When using the Blackfire CLI or the companion, Blackfire automatically instruments your code. If you want to control which part of the code is instrumented, you need to enable and disable instrumentation manually.

You can manually instrument some code by using the BlackfireProbe class that comes bundled with the Blackfire's probe:

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

Starts gathering profiling data by calling enable():

1
2
// start profiling the code
$probe->enable();

Stops the profiling by calling disable():

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

You can call enable() and disable() as many times as needed in your code. You can also discard any collected data by calling discard().

Calling close() instead of disable() stops the profiling and forces the collected data to be sent to Blackfire:

1
2
3
// stop the profiling
// send the result to Blackfire
$probe->close();

Caution

As with auto-instrumentation, profiling is only active when the code is run through the Companion or the blackfire CLI utility. If not, all calls are converted to noops.

Profiling Consumers and Daemons

The PHP SDK provides an easy way to profile consumers and daemons via the LoopClient class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
require_once __DIR__.'/vendor/autoload.php';

use Blackfire\LoopClient;
use Blackfire\Client;
use Blackfire\Profile\Configuration as ProfileConfiguration;

function consume()
{
    echo "Message consumed!\n";
}

$blackfire = new LoopClient(new Client(), 10);
$profileConfig = new ProfileConfiguration();
$profileConfig->setTitle('Consumer');

for (;;) {
    $blackfire->startLoop($profileConfig);

    consume();

    if ($profile = $blackfire->endLoop()) {
        print $profile->getUrl()."\n";
    }

    usleep(400000);
}

The LoopClient constructor takes a Blackfire Client instance and the number of iterations for each profile. Call startLoop() when entering a new loop iteration (before consuming a message for instance), and call endLoop() at the end of the message processing.

If you want to associate all profiles with a reference, call attachReference() with the reference profile's ID.

1
$blackfire->attachReference(7);

Using Signals

Profiling a consumer continuously is never a good idea. Instead, you should profile your consumers on demand or on a regular basis.

LoopClient supports this use case via signals. Register the signal you want to use for profiling and the code will only profile when signaled:

1
2
$blackfire = new LoopClient(new Client(), 10);
$blackfire->setSignal(SIGUSR1);

With this code in place, generating a profile of your consumer is as simple as sending the SIGUSR1 signal to the process:

1
pkill -SIGUSR1 -f consumer.php

You can also configure another signal to generate a new reference profile:

1
2
$blackfire->attachReference(7);
$blackfire->promoteReferenceSignal(SIGUSR2);

Signal the consumer with SIGUSR1 to generate a regular profile, or SIGUSR2 to replace the reference profile with a new one.

Note

Triggering profiles on a pre-defined scheduled is as easy as adding an entry in the crontab that signals the consumer process.

Enabling the Update of Git Commit Statuses

When using the SDK to automatically run test scenarios on git branches, for instance with a GitHub integration or GitLab integration, you will need to update the commit status in order to ease the validation and merge decision.

Commit statuses are associated to builds via the corresponding Git commit sha1. After enabling the notification channel (GitHub, Gitlab or webhook) for an environment, pass the Git sha1 to the createBuild() method options:

1
2
3
4
$build = $blackfire->createBuild('env-name-or-uuid', array(
    'title' => 'Build from PHP',
    'external_id' => '178f05003a095eb6b3a838d403544962262b7ed4',
));

Note

You must pass the full 40-character sha1 or GitHub won't accept the commit statuses.

To activate the comparison assertions, pass a parent_external_id. This is typically the sha1 of the base branch of the pull request.:

1
2
3
4
5
$build = $blackfire->createBuild('env-name-or-uuid', array(
    'title' => 'Build from PHP',
    'external_id' => '178f05003a095eb6b3a838d403544962262b7ed4',
    'external_parent_id' => 'f72aa774dd33cfc5eac43e1bfaf67e4028acca8b',
));