<?php
/**
* phpbench
* Simple PHP5 class to perform advanced benchmarks.
* Copyright (C) 2009, 2010 sexyprout <hide@address.com>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* @version 2.0
* @link https://github.com/sexyprout/phpbench
*/
class phpbench {
/**
* Number of iterations using method loop() (defaults to 1,000)
*
* @var integer
* @access protected
*/
protected $iterations = 1000;
/**
* Number of decimal places in formatted numbers (defaults to 4)
*
* @var integer
* @access protected
*/
protected $decimals = 4;
/**
* Stores loops durations
*
* @var array
* @access protected
*/
protected $loops = array();
/**
* HTML results table
*
* @var string
* @access protected
*/
protected $html;
/**
* Headers of the results table
*
* @var array
* @access protected
*/
protected $table_headers = array(
'phpbench',
'Total execution time',
array(
'Loops',
array(
'No',
'Time',
'Diff. w/ the prev. loop',
'Pct'
)
),
'Number of iterations'
);
/**
* Processed bench data that will be displayed in the table
*
* @var array
* @access
*/
protected $data = array();
/**
* Class constructor
* Defines options
*
* @param integer $iterations Set number of iterations
* @param integer $decimals Set number of decimal places in
formatted numbers (see <http://www.php.net/manual/en/ini.core.php#ini.precision>);
does NOT affect percentages and faster/slower comparisons.
* @param integer $rendering Set rendering mode
* @return void
* @access public
*/
function __construct($iterations = 1000, $decimals = 4) {
if(!is_int($iterations)) {
trigger_error(__METHOD__ . '() expects parameter 1 to be long, ' . gettype($iterations) . ' given', E_USER_ERROR);
} else {
$this->iterations = intval($iterations);
}
if(!is_int($decimals)) {
trigger_error(__METHOD__ . '() expects parameter 2 to be long, ' . gettype($decimals) . ' given', E_USER_ERROR);
} else {
$this->decimals = $decimals;
}
}
/**
* Class destructor
* Displays the results table
*
* @since 2.0
* @return void
* @access public
*/
function __destruct() {
$this->computeData();
$this->processData();
$this->buildTable();
echo $this->html;
}
/**
* Loop
*
* @param closure $callback Callback function to execute
* @return void
* @access public
*/
function loop($callback) {
$start = microtime(true);
ob_start();
for($i = 0; $i < $this->iterations; $i++)
$callback();
ob_end_clean();
$this->loops[] = microtime(true) - $start;
}
/**
* Computes the data
* This sets the basic data array for further process
*
* @return void
* @access protected
*/
protected function computeData() {
$data = array(); // new data
$data['exec_time'] = array_sum($this->loops);
$data['iterations'] = $this->iterations;
$data['loops'] = array();
foreach($this->loops as $i => $exec_time) {
$prev_loop = $i == 0 ? array() : $data['loops'][$i - 1]; // don't worry, there shouldn't be any errors if you're a good developer
$ldata = array(); // new data
$ldata['nbr'] = $i + 1;
$ldata['exec_time'] = $exec_time;
if($i == 0) {
$ldata['_faster'] = -1;
} else {
$ldata['_faster'] = $prev_loop['exec_time'] > $ldata['exec_time'];
$ldata['diff'] = abs($ldata['exec_time'] - $prev_loop['exec_time']); // if you still don't understand the first comment of this method then you're a fag
$ldata['comparison'] = $ldata['exec_time'] / $prev_loop['exec_time'];
}
if(count($this->loops) == 0) {
$ldata['_fastest'] = -1;
$ldata['_slowest'] = -1;
} else {
$ldata['_fastest'] = $ldata['exec_time'] == min($this->loops);
$ldata['_slowest'] = $ldata['exec_time'] == max($this->loops);
}
$ldata['pct'] = $ldata['exec_time'] * 100 / $data['exec_time'];
$data['loops'][] = $ldata;
}
$this->data = $data;
}
/**
* Processes the data
* This correctly formats the data to be displayed in the results table
*
* @return void
* @access protected
*/
protected function processData() {
$data = array(); // new data
$data['exec_time'] = round($this->data['exec_time'], $this->decimals);
$data['exec_time'] .= ' s';
$data['iterations'] = number_format($this->data['iterations']);
$data['loops'] = array();
foreach($this->data['loops'] as $i => $loop) {
$ldata = array(); // new loop data
$ldata['nbr'] = '#';
$ldata['nbr'] .= $loop['nbr'];
$ldata['exec_time'] = round($loop['exec_time'], $this->decimals);
$ldata['exec_time'] .= ' s';
if($i == 0) {
$ldata['diff'] = '-';
$ldata['comparison'] = '-';
} else {
$ldata['diff'] = round($loop['diff'], $this->decimals);
$ldata['diff'] .= ' s';
$ldata['comparison'] = $loop['_faster'] ? 1 / $loop['comparison'] : $loop['comparison'];
$ldata['comparison'] = round($ldata['comparison'], 1);
$ldata['comparison'] .= 'x ';
$ldata['comparison'] .= $loop['_faster'] ? 'faster' : 'slower';
}
$ldata['_faster'] = $loop['_faster'];
$ldata['_fastest'] = $loop['_fastest'];
$ldata['_slowest'] = $loop['_slowest'];
$ldata['pct'] = round($loop['pct'], 1);
$ldata['pct'] .= '%';
$data['loops'][] = $ldata;
}
$this->data = $data;
}
/**
* Builds the table
* This fills the data into the table
*
* @return void
* @access protected
*/
protected function buildTable() {
$headers = $this->table_headers;
$data = $this->data;
$table = '<table style="font: 13px \'Helvetica Neue\', Helvetica, Arial, \'Liberation Sans\', sans-serif; border-collapse: collapse">';
$table .= '<caption>' . $headers[0] . '</caption>';
$table .= '<tr>';
$table .= '<th colspan="5" style="border: 1px solid #999; background-color: #E0E0E0; padding: 5px 10px">' . $headers[1] . '</th>';
$table .= '</tr>';
$table .= '<tr>';
$table .= '<td colspan="5" style="border: 1px solid #999; padding: 5px 10px">' . $data['exec_time'] . '</td>';
$table .= '</tr>';
$table .= '<tr>';
$table .= '<th colspan="5" style="border: 1px solid #999; background-color: #E0E0E0; padding: 5px 10px">' . $headers[2][0] . '</th>';
$table .= '</tr>';
$table .= '<tr>';
$table .= '<th style="border: 1px solid #999; background-color: #f1f1f1; padding: 5px 10px">' . $headers[2][1][0] . '</th>';
$table .= '<th style="border: 1px solid #999; background-color: #f1f1f1; padding: 5px 10px">' . $headers[2][1][1] . '</th>';
$table .= '<th colspan="2" style="border: 1px solid #999; background-color: #f1f1f1; padding: 5px 10px">' . $headers[2][1][2] . '</th>';
$table .= '<th style="border: 1px solid #999; background-color: #f1f1f1; padding: 5px 10px">' . $headers[2][1][3] . '</th>';
$table .= '</tr>';
foreach($this->data['loops'] as $loop) {
$table .= '<tr';
if($loop['_fastest']) {
$table .= ' style="background-color: #e0ffe0">';
} elseif($loop['_slowest']) {
$table .= ' style="background-color: #ffe0e0">';
} else {
$table .= '>';
}
$table .= '<td style="border: 1px solid #999; padding: 5px 10px">' . $loop['nbr'] . '</td>';
$table .= '<td style="border: 1px solid #999; padding: 5px 10px">' . $loop['exec_time'] . '</td>';
$table .= '<td style="border: 1px solid #999; padding: 5px 10px">' . $loop['diff'] . '</td>';
$table .= '<td style="border: 1px solid #999; padding: 5px 10px';
if($loop['_faster'] === true) { // -1 == true
$table .= '; color: green">';
} elseif(!$loop['_faster']) {
$table .= '; color: red">';
} else {
$table .= '">';
}
$table .= $loop['comparison'];
$table .= '</td>';
$table .= '<td style="border: 1px solid #999; padding: 5px 10px">' . $loop['pct'] . '</td>';
$table .= '</tr>';
}
$table .= '<tr>';
$table .= '<th colspan="5" style="border: 1px solid #999; background-color: #E0E0E0; padding: 5px 10px">' . $headers[3] . '</th>';
$table .= '</tr>';
$table .= '<tr>';
$table .= '<td colspan="5" style="border: 1px solid #999; padding: 5px 10px">' . $this->data['iterations'] . '</td>';
$table .= '</tr>';
$table .= '</table>';
$this->html = $table;
}
}