PHP Profile Class

September 24, 2008

Profiling is a important part of software development, allowing you to find bottlenecks in your application and discover areas that would benefit from optimisation. In PHP a common technique is to calculate the total time to render a page. This can be achieved using the microtime function:

<?php
$startTime = microtime(true);

// normal page code here

$time = microtime(true) - $startTime;
echo "<!-- Page generation time: {$time} seconds -->";
?>

This simple approach is a a great way to find out how long certain pages are taking to load. Sometimes, however, you want more granular profiling information. That’s why I created a Profile class, which records the execution time of a given method. Here’s how to use it:

$p = new Profile();
$time = $p->profile("myClassName", "methodName", array("arg1", "arg2"));

You can also pass in an optional fourth argument, which specifies how many times to run the given method. After running it multiple times you can call the Profile::printDetails() method to view the total time, average invocation time, and the maximum single invocation time. Below is a simple example:

<?php
include("Profile.php");

/**
 * Test class just for the purposes of demonstration
 */
class Test {
	public function add($a, $b) {
		// add the numbers together 100 times
		for($i = 0; $i < 100; $i++) {
			$c = $a + $b;
		}
	}
}

$p = new Profile();
$p->profile("Test", "add", array(1, 2), 1000);
$p->printDetails();
?>

If you run the above code you’ll see the following output:

Test::add(1, 2) was invoked 1000 times
Total duration: 0.0447s
Average duration: 0s
Worst duration: 0.002s

The code listing for the Profile class is below, with some of the comments removed for the sake of brevity. A fully commented version is available for download.

<?php
/**
 * Class to time the execution of method calls.
 *
 * @author Ben Dowling - www.coderholic.com
 */
class Profile {
	/**
	 * Stores details about the last profiled method
	 */
	private $details;

    public function __construct() {}

	/**
	 * @param classname string
	 * @param methodname string
	 * @param methodargs array
	 * @param invocations int The number of times to call the method
	 * @return float average invocation duration in seconds
	 */
	public function profile($classname, $methodname, $methodargs, $invocations = 1) {
		if(class_exists($classname) != TRUE) {
			throw new Exception("{$classname} doesn't exist");
		}

		$method = new ReflectionMethod($classname, $methodname);

		$instance = NULL;
		if(!$method->isStatic()) 		{
			$class = new ReflectionClass($classname);
			$instance = $class->newInstance();
		}

		$durations = array();
		for($i = 0; $i < $invocations; $i++) {
			$start = microtime(true);
			$method->invokeArgs($instance, $methodargs);
			$durations[] = microtime(true) - $start;
		}

		$duration["total"] = round(array_sum($durations), 4);
		$duration["average"] = round($duration["total"] / count($durations), 4);
		$duration["worst"] = round(max($durations), 4);	

		$this->details = array(	"class" => $classname,
							   	"method" => $methodname,
							   	"arguments" => $methodargs,
						 		"duration" => $duration,
								"invocations" => $invocations);

		return $duration["average"];
	}

	/**
	 * @return string
	 */
	private function invokedMethod() {
		return "{$this->details["class"]}::{$this->details["method"]}(" .
			 join(", ", $this->details["arguments"]) . ")";
	}

	/**
	 * Prints out details about the last profiled method
	 */
	public function printDetails() {
		$methodString = $this->invokedMethod();
		$numInvoked = $this->details["invocations"];

		if($numInvoked == 1) {
			echo "{$methodString} took {$this->details["duration"]["average"]}s\n";
		}

		else {
			echo "{$methodString} was invoked {$numInvoked} times\n";
			echo "Total duration:   {$this->details["duration"]["total"]}s\n";
			echo "Average duration: {$this->details["duration"]["average"]}s\n";
			echo "Worst duration:   {$this->details["duration"]["worst"]}s\n";
		}
	}
}

?>

5 Comments »

  1. A better way to profile PHP apps is to use Xdebug http://xdebug.org/

    Comment by Tarique Sani — September 27, 2008 @ 12:57 pm

  2. XDebug is a great way to profile PHP applications, and also helps with debugging too.

    My Profile class isn’t meant as a replacement for something like XDebug. I’ve been using it to compare several slightly different blocks of code. Using the Profile class this is relatively simple. Using XDebug it’d be a bit more longwinded, as you’d have to look through logs etc.

    Comment by Ben — September 28, 2008 @ 9:54 pm

  3. [...] result from both functions, but you’ll suffer a slight performance hit with split. Using my profile class I worked out that explode is just over twice as fast as [...]

    Pingback by Coderholic » Blog Archive » Beware PHP’s split() — January 29, 2009 @ 10:40 pm

  4. Hey Boss… you have an error on line 13 above in the profile class….

    it reads…:

    public __construct()

    should be …:

    public function __construct()

    Comment by Anthony — March 2, 2009 @ 2:07 pm

  5. Thanks! I’ve just fixed it.

    Comment by Ben — March 2, 2009 @ 2:10 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment