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.
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();
|
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.
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);
}
}
}
|
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);
|
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'));
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().
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.
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
}
|
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');
|
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:
.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
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);
|
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();
|
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.
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);
|
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.
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',
));
|