Profiling HTTP Requests

Blackfire main use case is to profile HTTP requests like web pages, web service calls, or API calls.

Before starting profiling, check that you have Blackfire installed correctly on the server hosting the website you need to profile.

Profiling an HTTP request mean instrumenting the project's code, which incurs some overhead. Blackfire is apart from many other profilers because of two main differences:

  • Blackfire automatically instruments your code when needed, so you don't need to change your code;
  • Blackfire does nothing when you are not profiling a request (the overhead is then almost zero), so it is safe to deploy Blackfire on production servers, even for heavy-traffic projects.

You can easily profile HTTP requests from Google Chrome. This cookbook describes how to profile from the command line interface.

Profiling Simple HTTP Requests

Profiling an HTTP request can easily be done on the command line thanks to the blackfire utility that is bundled with the Blackfire Agent.

The easiest way to profile an HTTP request is to use the curl sub-command of the blackfire utility. It accepts the same arguments and options as the regular curl utility:

1
blackfire curl http://example.com/

You can get a list of options available for the curl sub-command:

1
blackfire help curl

To get more accurate results, take several samples of the request via the --samples option (we recommend you to use this option only for "safe" HTTP requests like GET requests):

1
blackfire --samples 10 curl http://example.com/

At the end, blackfire outputs the URL where the profile can be found (this can be hidden by passing the -q option.)

Note that you need curl to be installed on your system for blackfire curl to work. If curl is not available or if you prefer to use another tool to make your HTTP requests, use the run sub-command instead:

1
2
3
blackfire run sh -c 'curl -H "X-Blackfire-Query: $BLACKFIRE_QUERY" http://example.com/ > /dev/null'

blackfire run sh -c 'wget --header="X-Blackfire-Query: $BLACKFIRE_QUERY" http://example.com/ > /dev/null'

As you can see, you need to add a custom X-Blackfire-Query HTTP header to trigger the profiling; the value is the content of the $BLACKFIRE_QUERY environment variable, which is automatically set by the blackfire utility. It means that this method works for any tools that accept custom HTTP headers.

References

Create a reference profile by passing the --new-reference option:

1
blackfire --new-reference curl http://example.com/

Compare a profile to a reference profile by passing its id (the id is part of the output of the previous command or available on the web UI) via --reference:

1
blackfire --reference=7 curl http://example.com/

If you want to collaborate on performance, you can send a profile directly to an environment by passing the environment server id (or the environment name) via the --env option (or --team on previous versions of blackfire):

1
blackfire --env=example.com curl http://example.com/

If you don't remember the reference profile you need to use or the environment name, use the --interactive option:

1
blackfire --interactive curl http://example.com/

JSON Output

You can easily integrate Blackfire results into your own tools by using the --json option to get a JSON representation of a profile:

1
blackfire --json curl http://example.com/

The resources consumed are available under the envelope entry; keys are the cost dimensions:

  • pmu: Peak Memory Usage (in bytes);
  • nw: Network (in bytes);
  • wt: Wall Clock Time (in microseconds);
  • cpu: CPU time (in microseconds);
  • io: I/O time (in microseconds).

Profiling non-GET Requests, Ajax, and More

Profiling non-GET requests or requests which need some specific HTTP headers is as easy as any other requests as blackfire curl supports all cURL options:

1
blackfire curl -XPOST http://example.com/

Did you know that browsers like Firefox or Google Chrome can give you the cURL arguments and options to use for any web page like Ajax calls? It's known as "copy-as-cUrl" in both browsers:

/docs/chrome-copy-as-curl.png
/docs/firefox-copy-as-curl.png

Profiling Part of an HTTP Call

Blackfire automatically instruments your code, but sometimes, you might want to focus the profiling on only part of the code. That's possible when opting for manual instrumentation via the PHP SDK.

After instrumenting your code, use the blackfire utility as above to profile your application. When not using Blackfire, all calls are converted to noops.

Profiling Slow Requests

Blackfire profiles requests on-demand when you trigger it. However sometimes you don't know upfront exactly when a slow request may be executed, and still those are the ones you want to look into.

A solution is to profile all requests and stop when a slow request is found.

Warning

Profiling all traffic as described here will slow down your app until a slow request is found. The slow down is limited to one active profiling per concurrent HTTP requests.

First, create a valid signature to profile your website. Those are valid for one hour, so you might need to regenerate one if you run out of time.

1
blackfire --reference='1 Slow request' --new-reference run env | grep BLACKFIRE_QUERY > signature.txt

This command generates a signature for a new reference called "Slow request" and puts it into the signature.txt file.

Then, add the following code before your script. Its role is to instrument your code until the defined threshold is reached. It will deactivate itself by removing the signature file, so that no further request is instrumented. Only the last request's profile will be sent to the Blackfire.io servers.

 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
27
28
29
$signature = __DIR__ . '/signature.txt'; // Signature file previously generated
$threshold = 2.0; // In seconds

if (file_exists($signature)) {
    // If there is a signature, this means we want to profile slow queries
    $probe = new BlackfireProbe(trim(substr(file_get_contents($signature), strlen("BLACKFIRE_QUERY="))));

    if ($probe->enable()) {
        $start = microtime(true);

        register_shutdown_function(function () use ($signature, $threshold, $probe, $start) {
            $end = microtime(true);

            $slowQuery = ($end - $start) > $threshold;

            if (file_exists($signature)) {
                // If we have detected a slow query, we send the profile and delete the signature to stop the profiling on subsequent queries
                if ($slowQuery) {
                    @unlink($signature);
                } else {
                    // If the query is not slow, don't save
                    $probe->discard();
                }
            }
        });
    } elseif ($_SERVER['REQUEST_TIME'] - filemtime($signature) > 3600) {
        @unlink($signature);
    }
}