PHP Profiler
Spoiler: 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. 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).
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…
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.
RAII to Take Measurements
The solution I found to solve this problem takes advantage of the RAII pattern:
- The first measurement is taken in the constructor;
- The second measure and the sending of the message in the destructor.
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
$size) ;
consumeMemory(// 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.