<?php
namespace Hoa\Bench {
class Mark {
/**
* Mark ID.
*
* @var \Hoa\Bench\Mark string
*/
protected $_id = null;
/**
* Start time.
*
* @var \Hoa\Bench\Mark float
*/
protected $start = 0.0;
/**
* Stop time.
*
* @var \Hoa\Bench\Mark float
*/
protected $stop = 0.0;
/**
* Addition of pause time.
*
* @var \Hoa\Bench\Mark float
*/
protected $pause = 0.0;
/**
* Whether the mark is running.
*
* @var \Hoa\Bench\Mark bool
*/
protected $_running = false;
/**
* Whether the mark is in pause.
*
* @var \Hoa\Bench\Mark bool
*/
protected $_pause = false;
/**
* Built a mark (and set the ID).
*
* @access public
* @param string $id The mark ID.
* @return void
*/
public function __construct ( $id ) {
$this->setId($id);
return;
}
/**
* Set the mark ID.
*
* @access protected
* @param string $id The mark ID.
* @return string
*/
protected function setId ( $id ) {
$old = $this->_id;
$this->_id = $id;
return $old;
}
/**
* Get the mark ID.
*
* @access public
* @return string
*/
public function getId ( ) {
return $this->_id;
}
/**
* Start the mark.
* A mark can be started if it is in pause, stopped, or if it is the first start.
* Else, an exception will be thrown.
*
* @access public
* @return \Hoa\Bench\Mark
* @throw \Hoa\Bench\Exception
*/
public function start ( ) {
if(true === $this->isRunning())
if(false === $this->isPause())
throw new Exception(
'Cannot start the %s mark, because it is running.',
0, $this->getId());
if(true === $this->isPause())
$this->pause += microtime(true) - $this->stop;
else {
$this->reset();
$this->start = microtime(true);
}
$this->_running = true;
$this->_pause = false;
return $this;
}
/**
* Stop the mark.
* A mark can be stopped if it is in pause, or started. Else, an exception
* will be thrown (or not, according to the $silent argument).
*
* @access public
* @param bool $silent If set to true and if the mark is not running,
* no exception will be thrown.
* @return \Hoa\Bench\Mark
* @throw \Hoa\Bench\Exception
*/
public function stop ( $silent = false ) {
if(false === $this->isRunning())
if(false === $silent)
throw new Exception(
'Cannot stop the %s mark, because it is not running.',
1, $this->getId());
else
return $this;
$this->stop = microtime(true);
$this->_running = false;
$this->_pause = false;
return $this;
}
/**
* Reset the mark.
*
* @access public
* @return \Hoa\Bench\Mark
*/
public function reset ( ) {
$this->start = 0.0.
$this->stop = 0.0;
$this->pause = 0.0;
$this->_running = false;
$this->_pause = false;
return $this;
}
/**
* Pause the mark.
* A mark can be in pause if it is started. Else, an exception will be
* thrown (or not, according to the $silent argument).
*
* @access public
* @param bool $silent If set to true and the mark is not running,
* no exception will be throw. Idem if the mark
* is in pause.
* @return \Hoa\Bench\Mark
* @throw \Hoa\Bench\Exception
*/
public function pause ( $silent = false ) {
if(false === $this->isRunning())
if(false === $silent)
throw new Exception(
'Cannot stop the %s mark, because it is not running.',
2, $this->getId());
else
return $this;
if(true === $this->isPause())
if(false === $silent)
throw new Exception(
'The %s mark is still in pause. Cannot pause it again.',
3, $this->getId());
else
return $this;
$this->stop = microtime(true);
$this->_pause = true;
return $this;
}
/**
* Get the difference between $stop and $start.
* If the mark is still running (it contains the pause case), the current
* microtime will be used in stay of $stop.
*
* @access public
* @return float
*/
public function diff ( ) {
if(false === $this->isRunning() || true === $this->isPause())
return $this->stop - $this->start - $this->pause;
return microtime(true) - $this->start - $this->pause;
}
/**
* Compare to mark.
* $a op $b : return -1 if $a < $b, 0 if $a == $b, and 1 if $a > $b. We
* compare the difference between $start and $stop, i.e. we call the diff()
* method.
*
* @access public
* @param \Hoa\Bench\Mark $mark The mark to compare to.
* @return int
*/
public function compareTo ( Mark $mark ) {
$a = $this->diff();
$b = $mark->diff();
if($a < $b)
return -1;
elseif($a == $b)
return 0;
else
return 1;
}
/**
* Check if the mark is running.
*
* @access public
* @return bool
*/
public function isRunning ( ) {
return $this->_running;
}
/**
* Check if the mark is in pause.
*
* @access public
* @return bool
*/
public function isPause ( ) {
return $this->_pause;
}
/**
* Alias of the diff() method, but return a string, not a float.
*
* @access public
* @return string
*/
public function __toString ( ) {
return (string) $this->diff();
}
}
class Bench implements \Iterator, \Countable {
/**
* Statistic : get the result.
*
* @const int
*/
const STAT_RESULT = 0;
/**
* Statistic : get the pourcent.
*
* @const int
*/
const STAT_POURCENT = 1;
/**
* Collection of marks.
*
* @var \Hoa\Bench array
*/
protected static $_mark = array();
/**
* Collection of filters.
*
* @var \Hoa\Bench array
*/
protected $_filters = array();
/**
* Get a mark.
* If the mark does not exist, it will be automatically create.
*
* @access public
* @param string $id The mark ID.
* @return \Hoa\Bench\Mark
* @throw \Hoa\Bench\Exception
*/
public function __get ( $id ) {
if(true === $this->markExists($id))
return self::$_mark[$id];
$mark = new Mark($id);
self::$_mark[$id] = $mark;
return $mark;
}
/**
* Check if a mark exists.
* Alias of the protected markExist method.
*
* @access public
* @param string $id The mark ID.
* @return bool
*/
public function __isset ( $id ) {
return $this->markExists($id);
}
/**
* Destroy a mark.
*
* @access public
* @param string $id The mark ID.
* @return void
*/
public function __unset ( $id ) {
unset(self::$_mark[$id]);
return;
}
/**
* Destroy all mark.
*
* @access public
* @return void
*/
public function unsetAll ( ) {
self::$_mark = array();
return;
}
/**
* Check if a mark already exists.
*
* @access protected
* @param string $id The mark ID.
* @return bool
*/
protected function markExists ( $id ) {
return isset(self::$_mark[$id]);
}
/**
* Get the current mark for the iterator.
*
* @access public
* @return \Hoa\Bench\Mark
*/
public function current ( ) {
return current(self::$_mark);
}
/**
* Get the current mark ID for the iterator.
*
* @access public
* @return string
*/
public function key ( ) {
return key(self::$_mark);
}
/**
* Advance the internal mark collection pointer, and return the current
* mark.
*
* @access public
* @return \Hoa\Bench\Mark
*/
public function next ( ) {
return next(self::$_mark);
}
/**
* Rewind the internal mark collection pointer, and return the first mark.
*
* @access public
* @return \Hoa\Bench\Mark
*/
public function rewind ( ) {
return reset(self::$_mark);
}
/**
* Check if there is a current element after calls the rewind or the next
* methods.
*
* @access public
* @return bool
*/
public function valid ( ) {
if(empty(self::$_mark))
return false;
$key = key(self::$_mark);
$return = (next(self::$_mark) ? true : false);
prev(self::$_mark);
if(false === $return) {
end(self::$_mark);
if($key === key(self::$_mark))
$return = true;
}
return $return;
}
/**
* Add a filter.
* Used in the self::getStatistic() method, no in iterator.
* A filter is a callable that will receive 3 values about a mark: ID, time
* result, and time pourcent. The callable must return a boolean.
*
* @access public
* @param mixed $callable Callable.
* @return void
*/
public function filter ( $callable ) {
$this->_filters[] = xcallable($callable);
return $this;
}
/**
* Return all filters.
*
* @access public
* @return array
*/
public function getFilters ( ) {
return $this->_filters;
}
/**
* Get statistic.
* Return an associative array : id => sub-array. The sub-array contains the
* result time in second (given by the constant self::STAT_RESULT), and the
* result pourcent (given by the constant self::START_POURCENT).
*
* @access public
* @param bool $considerFilters Whether we should consider filters or
* not.
* @return array
*/
public function getStatistic ( $considerFilters = true ) {
if(empty(self::$_mark))
return array();
$max = $this->getLongest()->diff();
$out = array();
foreach($this as $id => $mark) {
$result = $mark->diff();
$pourcent = ($result * 100) / $max;
if(true === $considerFilters)
foreach($this->getFilters() as $filter)
if(true !== $filter($id, $result, $pourcent))
continue 2;
$out[$id] = array(
self::STAT_RESULT => $result,
self::STAT_POURCENT => $pourcent
);
}
return $out;
}
/**
* Get the maximum, i.e. the longest mark in time.
*
* @access public
* @return \Hoa\Bench\Mark
*/
public function getLongest ( ) {
$max = 0;
$outMark = null;
foreach($this as $id => $mark)
if($mark->diff() > $max) {
$outMark = $mark;
$max = $mark->diff();
}
return $outMark;
}
/**
* Draw statistic in text mode.
*
* @access public
* @param int $width The graphic width.
* @return string
* @throw \Hoa\Bench\Exception
*/
public function drawStatistic ( $width = 80 ) {
if(empty(self::$_mark))
return '';
if($width < 1)
throw new Exception(
'The graphic width must be positive, given %d.', 0, $width);
$out = null;
$stats = $this->getStatistic();
$margin = 0;
foreach($stats as $id => $foo)
strlen($id) > $margin and $margin = strlen($id);
$width = $width - $margin - 18;
$format = '%-' . $margin . 's %-' . $width . 's %5dms, %5.1f%%' . "\n";
foreach($stats as $id => $stat)
$out .= sprintf(
$format,
$id,
str_repeat(
'|', round(($stat[self::STAT_POURCENT] * $width) / 100)
),
round(1000 * $stat[self::STAT_RESULT]),
round($stat[self::STAT_POURCENT], 3)
);
return $out;
}
/**
* Count the number of mark.
*
* @access public
* @return int
*/
public function count ( ) {
return count(self::$_mark);
}
/**
* Alias of drawStatistic() method.
*
* @access public
* @return string
*/
public function __toString ( ) {
return $this->drawStatistic();
}
}
}
namespace {
$bench = new Hoa\Bench();
$memory = array();
$string = str_repeat('foo', 10000);
$_string = $string;
$memory['substr'] = memory_get_usage();
$bench->substr->start();
while(0 < strlen($_string)) {
preg_match('#^(?:foo)#u', $_string, $matches);
$_string = mb_substr($_string, mb_strlen($matches[0]));
}
$bench->substr->stop();
$memory['substr'] = memory_get_usage() - $memory['substr'];
unset($matches);
unset($_string);
$_string = $string;
$offset = 0;
$memory['offset'] = memory_get_usage();
$bench->offset->start();
while($offset < strlen($_string)) {
preg_match('#(?:foo)#u', $_string, $matches, PREG_OFFSET_CAPTURE, $offset);
$offset += mb_strlen($matches[0][0]);
}
$bench->offset->stop();
$memory['offset'] = memory_get_usage() - $memory['offset'];
echo $bench;
print_r($memory);
}