Last active
May 31, 2016 04:38
-
-
Save pida42/95350fcfb1d5579c58c07c5ec9303c46 to your computer and use it in GitHub Desktop.
Simple benchmark class that allows you write markers in code and get info about exec time, memory usage and more...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* The MIT License | |
* | |
* Copyright (c) 2016 Frantisek Preissler <[email protected]> | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
* THE SOFTWARE. | |
*/ | |
namespace Pida42; | |
/** | |
* @package Pida42 | |
* @copyright Copyright (c) 2016 | |
* @author Frantisek Preissler <[email protected]> | |
* @license [MIT](http://en.wikipedia.org/wiki/MIT_License) | |
* @encoding UTF-8 | |
*/ | |
class Benchmark { | |
// <editor-fold defaultstate="collapsed" desc="Properties"> | |
/** @var string Result metrics output type (default: array) */ | |
protected static $outputType = 'array'; | |
/** @var array Available output types that's are supported/implemented */ | |
protected static $outputTypeList = ['array', 'string', 'json']; | |
// </editor-fold> | |
// <editor-fold defaultstate="collapsed" desc="Cache"> | |
/** | |
* Get a new (or already eixisting) instance using singleton. | |
* | |
* @static | |
* @return \ArrayObject | |
*/ | |
public static function getCache() { | |
// magic of singletons | |
static $instance = null; | |
// This is first time instance creation | |
if (false === isset($instance)) { | |
$instance = new \ArrayObject([], \ArrayObject::STD_PROP_LIST); | |
} | |
// Return already created instance | |
return $instance; | |
} | |
/** | |
* Check if exists item by index | |
* | |
* @param string $index | |
* | |
* @static | |
* @return bool | |
*/ | |
public static function cacheExists($index) { | |
return self::getCache()->offsetExists($index); | |
} | |
/** | |
* Set value into cache | |
* | |
* @param string $index | |
* @param mixed $value | |
* | |
* @static | |
* @return mixed | |
*/ | |
public static function cacheSet($index, $value) { | |
return self::getCache()->offsetSet($index, $value); | |
} | |
/** | |
* Get value by index | |
* | |
* @param string $index | |
* | |
* @static | |
* @return bool | |
*/ | |
public static function cacheGet($index) { | |
return self::getCache()->offsetGet($index); | |
} | |
// </editor-fold> | |
// <editor-fold defaultstate="collapsed" desc="Helpers"> | |
/** | |
* Get microtime | |
* | |
* @static | |
* @return float | |
*/ | |
private static function _time() { | |
return microtime(true); | |
} | |
/** | |
* Return memory usage | |
* | |
* @static | |
* @return int|null | |
*/ | |
private static function _memory() { | |
// function is not supported | |
if (false === function_exists('memory_get_usage')) { | |
return null; | |
} | |
return memory_get_usage(); | |
} | |
/** | |
* Return memory peak | |
* | |
* @static | |
* @return int|null | |
*/ | |
private static function _memoryPeak() { | |
// function is not supported | |
if (false === function_exists('memory_get_peak_usage')) { | |
return null; | |
} | |
return memory_get_peak_usage(); | |
} | |
/** | |
* Return average server load | |
* | |
* @return array|null | |
*/ | |
private static function _serverLoad() { | |
// function is not supported | |
if (false === function_exists('sys_getloadavg')) { | |
return null; | |
} | |
return sys_getloadavg(); | |
} | |
/** | |
* Get difference of arrays with keys intact | |
* | |
* @param array $arr1 | |
* @param array $arr2 | |
* | |
* @static | |
* @return array | |
*/ | |
private static function getRUsage($arr1, $arr2) { | |
// result data | |
$array = []; | |
// Add user mode time. | |
$arr1['ru_utime.tv'] = ($arr1['ru_utime.tv_usec'] / 1000000) + $arr1['ru_utime.tv_sec']; | |
$arr2['ru_utime.tv'] = ($arr2['ru_utime.tv_usec'] / 1000000) + $arr2['ru_utime.tv_sec']; | |
// Add system mode time. | |
$arr1['ru_stime.tv'] = ($arr1['ru_stime.tv_usec'] / 1000000) + $arr1['ru_stime.tv_sec']; | |
$arr2['ru_stime.tv'] = ($arr2['ru_stime.tv_usec'] / 1000000) + $arr2['ru_stime.tv_sec']; | |
// Unset time splits. | |
unset( | |
$arr1['ru_utime.tv_usec'], | |
$arr1['ru_utime.tv_sec'], | |
$arr2['ru_utime.tv_usec'], | |
$arr2['ru_utime.tv_sec'], | |
$arr1['ru_stime.tv_usec'], | |
$arr1['ru_stime.tv_sec'], | |
$arr2['ru_stime.tv_usec'], | |
$arr2['ru_stime.tv_sec'] | |
); | |
// Iterate over values. | |
foreach ($arr1 as $key => $value) { | |
$array[$key] = ($arr2[$key] - $arr1[$key]); | |
} | |
return $array; | |
} | |
/** | |
* Try to get stats using try{}catch() statement | |
* | |
* @param string $point | |
* | |
* @return array | |
* @internal param string $slabel | |
* @static | |
*/ | |
protected static function check($point) { | |
try { | |
return [ | |
'time' => self::getTime($point), | |
'usage' => self::getUsage($point), | |
'memory' => self::getMemory($point), | |
'peak_memory' => self::getMemoryPeak($point), | |
]; | |
} | |
catch (\Exception $e) { | |
return $e; | |
} | |
} | |
// </editor-fold> | |
// <editor-fold defaultstate="collapsed" desc="Common"> | |
/** | |
* Mark point in code | |
* | |
* @param string $point | |
* @param null|string $outputType | |
* | |
* @static | |
* @return void | |
*/ | |
public static function mark($point, $outputType = null) { | |
self::setTime($point); | |
self::setUsage($point, ($outputType ?: self::getOutputType())); | |
self::setMemory($point); | |
self::setMemoryPeak($point); | |
} | |
/** | |
* Try to get report using try{}catch() statement | |
* | |
* @param string $point | |
* | |
* @static | |
* @return array | |
* @throws \Exception | |
*/ | |
public static function report($point) { | |
try { | |
// stats separately | |
$results = self::check($point); | |
// result array | |
$result = [ | |
'ExecutionTime' => @$results['time'], | |
'MemoryLimit' => @rtrim(ini_get('memory_limit'), 'M'), | |
'MemoryUsage' => @$results['memory'], | |
'PeakMemory' => @$results['peak_memory'], | |
'AvgServerLoad' => @self::_serverLoad()['0'] | |
]; | |
return $result; | |
} | |
catch (Exception $e) { | |
return $e; | |
} | |
} | |
/** | |
* Get result data | |
* | |
* @param null|string $outputType Target result format type | |
* | |
* @static | |
* @return null|array|string | |
*/ | |
public static function usage($outputType = null) { | |
// If is not set, use default | |
if (null === $outputType) { | |
$outputType = self::getOutputType(); | |
} | |
if ('array' === $outputType) { | |
return getrusage(); | |
} | |
if ('string' === $outputType) { | |
return str_replace('&', ', ', http_build_query(getrusage())); | |
} | |
if ('json' === $outputType) { | |
return json_encode(getrusage()); | |
} | |
return null; | |
} | |
// </editor-fold> | |
// <editor-fold defaultstate="collapsed" desc="Getters/Setters - Output Type"> | |
/** | |
* Set the output type for results | |
* | |
* @param string $outputType Output type | |
* | |
* @static | |
* @return void | |
* @throws \UnexpectedValueException | |
* @see self::$outputTypeList | |
*/ | |
public static function setOutputType($outputType) { | |
// defined format is not supported | |
if (false === in_array(strtolower($outputType) . self::$outputTypeList)) { | |
throw new \UnexpectedValueException( | |
"Defined output type '{$outputType}' is not supported." . | |
"Supported output types are: " . implode(',', self::$outputTypeList) . "." | |
); | |
} | |
self::$outputType = strtolower($outputType); | |
} | |
/** | |
* Get actual output type | |
* | |
* @static | |
* @return string | |
*/ | |
public static function getOutputType() { | |
return self::$outputType; | |
} | |
// </editor-fold> | |
// <editor-fold defaultstate="collapsed" desc="Getters/Setters - Profile data"> | |
/** | |
* Get time difference | |
* | |
* @param string $point | |
* | |
* @static | |
* @return null|float | |
*/ | |
protected static function getTime($point) { | |
// if any point is missing, so end with null returning | |
if (false === self::cacheExists("{$point}_time_start") || false === self::cacheExists("{$point}_time_end")) { | |
return null; | |
} | |
return (float) number_format((self::cacheGet("{$point}_time_end") - self::cacheGet("{$point}_time_start")), 5, '.', ''); | |
} | |
/** | |
* Set time by label | |
* | |
* @param string $point | |
* | |
* @static | |
* @return void | |
*/ | |
protected static function setTime($point) { | |
if (true === self::cacheExists("{$point}_time_start") && false === self::cacheExists("{$point}_time_end")) { | |
self::cacheSet("{$point}_time_end", self::_time()); | |
} | |
else { | |
self::cacheSet("{$point}_time_start", self::_time()); | |
} | |
} | |
/** | |
* Get usage difference | |
* | |
* @param string $point | |
* | |
* @static | |
* @return null|array | |
*/ | |
protected static function getUsage($point) { | |
// if any point is missing, so end with null returning | |
if (false === self::cacheExists("{$point}_usage_start") || false === self::cacheExists("{$point}_usage_end")) { | |
return null; | |
} | |
return self::getRUsage(self::cacheGet("{$point}_usage_start"), self::cacheGet("{$point}_usage_end")); | |
} | |
/** | |
* Set usage by label | |
* | |
* @param string $point | |
* @param string $outputType | |
* | |
* @static | |
* @return void | |
*/ | |
protected static function setUsage($point, $outputType = null) { | |
self::cacheSet($point, self::usage($outputType)); | |
} | |
/** | |
* Get memory usage difference (in MB) | |
* | |
* @param string $point | |
* | |
* @static | |
* @return null|float | |
*/ | |
protected static function getMemory($point) { | |
// if any point is missing, so end with null returning | |
if (false === self::cacheExists("{$point}_memory_start") || false === self::cacheExists("{$point}_memory_end")) { | |
return null; | |
} | |
return (float) number_format(((self::cacheGet("{$point}_memory_end") - self::cacheGet("{$point}_memory_start")) / 1024 / 1024), 5, '.', ''); | |
} | |
/** | |
* Set memory by label. | |
* | |
* @param string $point | |
* | |
* @static | |
* @return void | |
*/ | |
protected static function setMemory($point) { | |
if (true === self::cacheExists("{$point}_memory_start") && false === self::cacheExists("{$point}_memory_end")) { | |
self::cacheSet("{$point}_memory_end", self::_memory()); | |
} | |
else { | |
self::cacheSet("{$point}_memory_start", self::_memory()); | |
} | |
} | |
/** | |
* Get memory peak usage | |
* | |
* @param string $point | |
* | |
* @static | |
* @return null|float | |
*/ | |
protected static function getMemoryPeak($point) { | |
// if any point is missing, so end with null returning | |
if (false === self::cacheExists("{$point}_peak_memory")) { | |
return null; | |
} | |
return (float) number_format((self::cacheGet("{$point}_peak_memory") / 1024 / 1024), 5, '.', ''); | |
} | |
/** | |
* Set peak memory by label. | |
* | |
* @param string $point | |
* | |
* @static | |
* @return void | |
*/ | |
protected static function setMemoryPeak($point) { | |
self::cacheSet("{$point}_peak_memory", self::_memoryPeak()); | |
} | |
// </editor-fold> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment