Programming is a wonderful mix of art and science; source code is both a poem and a math problem. It should be as simple and elegant as it is functional and fast. This blog is about that (along with whatever else I feel like writing about).

Sunday, October 14, 2007

PHP Profiler

At ZedZone, we found we're having a few problems with performance on a few of our pages. So I needed to find a way to determine what parts of our pages needed optimization so we could speed everything up without wasting our time optimizing functions and queries that don't need it.

There are a few profiling packages available for PHP, but they require installing modules or PEAR packages, and it seemed to me that that's not necessary to profile your performance. So instead, I decided to write my own class.

What I wanted from my profiling class was the ability to measure the entire execution time of the script, as well as the execution time between any given points in the script. I wanted there to be any arbitrary number of measurable points in the script; it should be up to me to decide what parts of the script I want to measure, as well as how many such parts.


class Profiler {
var $startTime;
var $endTime;
var $marks;
var $report;

function profiler() {
//initialize the object
$this->marks = array();
}

function getMicroTime() {
$t = microtime();
$time = explode(' ', $t);

return doubleval($time[0]) + $time[1];
}

function start() {
//start the profiling
$this->startTime = $this->getMicroTime();

$this->mark('Start');
}

function mark($title) {
//mark the current spot
$this->marks[] = array(
'title' => $title,
'time' => $this->getMicroTime()
);
}

function finish() {
//shut it down
$this->endTime = $this->getMicroTime();

$this->mark('Finish');
}

function generateReport() {
$report = array();

$lastTime = $this->startTime;
$totalTime = $this->endTime - $this->startTime;
foreach($this->marks as $mark) {
$mark['diff'] = $mark['time'] - $lastTime;
$mark['percentage'] = ($mark['diff'] / $totalTime) * 100;

$lastTime = $mark['time'];

$report[] = $mark;
}

$report = sortArray($report, 'percentage');
$report = array_reverse($report);

$this->report = $report;
}
}

So the first thing to notice about the script is that when you initialize it, it only sets up the array for the profiling marks. In general use of the script, you call the start() method right after initializing the Profiler object, and the finish() method after all the code on the page.

Then after every function call, query, or section of code that you want to measure, call the mark() method, passing a string that identifies what's being measured between this mark and the previous mark. At the end of the script, call generateReport(), and you can either var_dump/print_r the report instance variable, or you can create another method called printReport() that builds a table to display the results of the profiling.

Note that the sortArray() function is not included in this script. In my code, it points to a quicksort that sorts an associative array by the values in the given key (as opposed to the sort functions that simply sort an array of strings or numbers, rather than associative arrays). I might post that later, but at this point the Profiler class only does this to sort the report by the sections of code that affect performance the most.

This class has worked pretty well for me so far, and hopefully it can be helpful to you too. If I find I need to add to it, I'll post further. Happy coding!