Building a PHP Profiler

What is a PHP Profiler?

A profiler quickly becomes an absolute requirement for any large PHP project. Profiling provides the tools to look at your apps performance at different points within your code. By analyzing the time it takes to run through the code you can quickly identify bottlenecks in processing and speed up your application. The following is a profiler class meant to handle the time element of profiling only.

 

<?php
class Profiler{
o
oconst PROFILER_START_GLOBAL = 'profiler_start';
o
ostatic protected $all_profilers;
o
ostatic protected $current_index = 0;
oprotected $index;
oprotected $name;
oprotected $start;
oprotected $checkpoints;
o
ofunction __construct(){
o++self::$current_index;
o$this->set_index(self::$current_index);
oself::$all_profilers[self::$current_index] = &$this;
o}
o
ostatic function init_profiler($name='Main'){
o$prof = new Profiler();
o$prof->set_start(microtime(true));
oif($prof->get_index() == 0 && self::global_start_exists()){//check for the start time global since this is the main profiler
o$prof->set_global_start();
o}
o$prof->set_name($name);
oreturn $prof;
o}
o
ofunction get_index(){return $this->index;}
oprivate function set_index($index){$this->index = $index;}
o
oprivate function get_start_time(){
oreturn $this->start;
o}
oprivate function set_start($time){$this->start = $time;}
o
ostatic private function global_start_exists(){
oreturn ($GLOBALS[self::PROFILER_START_GLOBAL]);
o}
ofunction set_global_start(){
oif(self::global_start_exists()){
o$this->set_start($GLOBALS[self::PROFILER_START_GLOBAL]);
o}
o}
o
ofunction get_name(){return $this->name;}
ofunction set_name($name){$this->name = $name;}
o
ofunction get_elapsed_time(){
o$current_time = microtime(true);
o$elapsed = $current_time - $this->get_start_time();
oreturn $elapsed;
o}
o
ostatic function add_profiler_checkpoint($checkpoint_name,$index=1){
o$profiler = self::$all_profilers[$index];
oif(!$profiler){
o$profiler = Profiler::init_profiler($checkpoint_name.'_profiler');
o}
o$profiler->add_checkpoint($checkpoint_name);
o}
o
ofunction add_checkpoint($checkpoint_name){
o$checkpoint = array();
o$checkpoint['name'] = $checkpoint_name;
o$checkpoint['time'] = $this->get_elapsed_time();
o$this->checkpoints[] = $checkpoint;
o}
ofunction get_checkpoints(){return $this->checkpoints;}
o
ofunction get_result($newline='<br>'){
o$profiler_text = '';
o$profiler_text .= 'Profiler '.$this->get_index().': '.$this->get_name().$newline;
oif(count($this->get_checkpoints()) > 0){
o$profiler_text .= 'Checkpoints: '.count($this->get_checkpoints()).$newline;
o$profiler_text .= '--Checkpoint Name-- --Elapsed Time--'.$newline;
oforeach($this->get_checkpoints() as $checkpoint){
o$profiler_text .= $checkpoint['name'].' '.$checkpoint['time'].$newline;
o}
o}
o$profiler_text .= '--Total Elapsed Time--'.$newline;
o$profiler_text .= $this->get_elapsed_time().$newline;
o
oreturn $profiler_text;
o}
o
ostatic function get_all_results($newline='<br>'){
o$text = 'Profilers'.$newline;
oforeach(self::$all_profilers as $profiler){
o$text .= $profiler->get_result($newline);
o}
o$text .= '----------'.$newline;
oreturn $text;
o}
}

The basic idea is that we are getting a start time and comparing it to the current time as the code executes. Let’s take a look at how this works:

 

PHP ProfilerProperties

    o

  • $all_profilers is a static array of all instantiated profilers. This allows us to create multiple profilers in different sections of code and display them all at the end.
  • o

  • $current_index keeps track of the last index used so that each profiler has a unique index when it is created.
  • o

  • $index is the index of the current profiler. It is like the profilers id number
  • o

  • $name allows you to add names to any number of extra profilers that you want to add in for different sections of code. The default profiler is ‘Main’.
  • o

  • $start is the timestamp at the time that the profiler is started.
  • o

  • $checkpoints is an array of names and the corresponding time at which they are called. Leaving checkpoints within your code is key to finding bottlenecks.

Methods

    o

  • __contruct() adds the index to the profiler and increments the current_index
  • o

  • init_profiler($name) is the primary function used to begin profiling. This should generally be called shortly after your autoloader is included. You are using an autoloader, right?
  • o

  • some basic getters and setters
  • o

  • global_start_exists() looks for the ‘profiler_start’ global to be set. This global allows you to set the start teim as early as possible and run init_profiler() later.
  • o

  • set_global_start() gets the time from the ‘profiler_start’ global and sets it as the start time.
  • o

  • get_elapsed_time() gets the time that has elapsed since the profiler started.
  • o

  • add_profiler_checkpoint($checkpoint_name,$index) is a static function allowing named checkpoints to be set anywhere in the codebase without having the profiler object on hand. It sets checkpoints to the main index by default but it can be used with another index or to create a new index.
  • o

  • add_checkpoint() adds a named checkpoint and the current elapsed time to the profiler.
  • o

  • get_result($newline) returns a simple text table of results separated by the newline given.
  • o

  • get_all_results($newline) is a static function that will find all profilers and return the results from all of them. This is typically run at the end of script execution.

Usage

The most basic usage of this class is to create a performance output in the html comments or as a hidden element. First we need to run init_profiler. Typically this is done early in your script. Again, I recommend running it after your autoloader so you don’t need to include it manually. To make the timing more accurate, set the ‘profiler_start’ global at the very beginning of your script. After everything is done you can echo Profiler::get_all_results(). See the example below.

<?php
//index.php

$GLOBALS['profiler_start'] = microtime(true);

include_once('Autoloader.php');

Profiler::init_profiler();

do_some_work();

Profiler::add_profiler_checkpoint('done with do_some_work()');

do_some_more_work();

echo '<div style="display:none;">'.Profiler::get_all_results("<br>").'</div>';

This will output:

Profilers
Profiler 1: Main
Checkpoints: 1
--Checkpoint Name-- --Elapsed Time--
done with do_some_work() 0.030006170272827
--Total Elapsed Time--
0.033650159835815
----------

Next week we will expand on this class and add more features.

2 thoughts on “Building a PHP Profiler

Leave a Reply

Your email address will not be published. Required fields are marked *