PHP Profiler

When you need to measure the resources taken by some parts of your scripts, rather than taking out the big guns with xDebug, I offer you a handy little class.

Sometimes, or rather often in fact, you find yourself wanting to measure the resources used by your PHP scripts. Most of the time you have your application responding [too] slowly, you have some idea of the problematic part, but you want to measure it a bit just to be sure. There are then roughly two solutions...

PublicDomainPicture @ pixabay
PublicDomainPicture @ pixabay

Use Xdebug. Bring it all out using Xdebug, then you will know exactly which chunks of code take how long, where you are going,... I found this solution a little heavy and not very suitable for production applications. It is certainly very precise and practical, but sometimes you just want to have the time or the memory used by two poor functions.

Use patches. Make a small patch, which will then have to be canceled; a call to microtime () before/after and we write the difference somewhere. But this time around, I still find it a bit overwhelming to have to write the same lines every time. First, because a single line would be much cleaner. Then, because adding those lines mess up the code wich need to be cleaned afterwards.

I suggest here a third halfway solution that has helped me a lot on many occasions.

We are going to create a small class that measures the time between constructor and destructors and emits its message at that time. The advantage is that you can measure a function with a single line of code (easier to insert, and to clean up).

RAII to Take Measurements

The solution I found to solve this problem takes advantage of the RAII pattern:

Since PHP deletes objects as soon as they are no longer referenced, you can create an object at the start of a function knowing that at the end of its execution, the destructor will be called and you will have your time. All in a single line of code which reduces fatigue (and especially problems).

When you want to delete your lines, you can find them easily. Either by deleting the class and looking at which lines cause problems in your tests (but for that, you need tests ...). Either by using sed to automatically delete the lines, adapting the following example:

sed -i '/new Profiler/d' $(find ./src -name "*.php")

The Code

The goal being to have a light and easy to use system, the class that I propose to you is intentionally reduced to a minimum. Rather than developing something very generic (with callbacks for the emission of the log for example), I generally prefer to add this class when I need it by adapting it to the situations.

class Profiler
{

    private $label ;
    private $time ;

    public function __construct($label = "")
    {
        $this->label    = $label ;
        $this->time     = microtime(true) ;
    }

    public function __destruct()
    {
        $msg = sprintf("%s : %f %f",
            $this->label,
            microtime(true) - $this->time,
            memory_get_peak_usage()
            ) ;

        error_log($msg) ;
    }

}

Using it

As you may have anticipated, using the profiler to get the measurements in your logs is just to create an object at the start of the function.

Here is an example of a function, the purpose of which is only to illustrate the use of the Profiler by consuming unnecessary resources. You can see the line that creates the object, enough to do all the work.

function consumeMemory($size)
{
    $profiler = new Profiler("test alloc");
    $t = [] ;
    for ($i = 0; $i < $size; $i++) {
        $t[] = $t ;
    }
    // $profiler is deleted here auto-magically
}

Restriction

As opposed to C++ where objects are destroyed when you are leaving a block, PHP destroys them when they are no longer referenced. That is to say at the end of functions and no longer of any block, which introduces a subtle difference.

The following example, still for illustrative purposes, shows a case where the behavior is different between the two languages.

function consume($time, $size)
{
    if ($time > 0) {
        $profiler = new Profiler("Time") ;
        // Profiler profiler("time") ; in C++
        sleep($time) ;
        // in C++, the object is destroyed here
    }
    // in PHP, it still exists here

    if ($size > 0) {
        // Profiler profiler("Memory") ; in C++
        $profiler = new Profiler("Memory") ;
        // in PHP, this affectation have detroyed the previous object
        consumeMemory($size) ;
        // in C++, the object is destroyed here
    }

    sleep($time + $size) ; // or any other heavy computation

    // in PHP, the profiler is destroyed here
}

If you thought you were measuring each if block individually, as in C++, you'd be wrong. The $profilers survive the end of blocks and the last one created will also measure everything else in the function and therefore give you not intuitive measurements.

To remedy the problem, you could add unset $profiler; but that requires writing two lines and above all, it negates the usefulness of the class. On that way, you might as well do a close() method on the Profiler class and stop these RAII stories.

Personally, I think that is the wrong problem. If you have reached the point of measuring complex portions of a function like this, it is not the Profiler that is in question but the function that must be refactored. By restricting myself to the rule "a function does one thing", this problem never arises.

And Next?

The RAII pattern is a bit like breathing, once you get started, you can't live without it. Very practical for resources and mutexes, it can also provide a lot of services when it comes to behavior to be performed at the end of a function.

However, you have to be careful because PHP has some subtle differences from C++ that you have to keep in mind when using RAIIed objects if you don't want to have (bad) surprises.