Skip to content

Instantly share code, notes, and snippets.

@leeferwagen
Created December 19, 2017 10:10
Show Gist options
  • Save leeferwagen/a3db21e49378cdc0bee7a4e92a0a9a45 to your computer and use it in GitHub Desktop.
Save leeferwagen/a3db21e49378cdc0bee7a4e92a0a9a45 to your computer and use it in GitHub Desktop.
<?php
final class Perfbasta
{
private static $instance = null;
private $outputFile = '';
private $outputStream = null;
private $debugBacktraceArg1 = false;
private $outputStreamSupportsLocking = false;
private $times = array();
private $counts = array();
private $lastMicrotime = 0.0;
private $currentMicrotime = 0.0;
private $timeFormatForAlert = "\033[31m%2d:%03d:%03ds\033[39m";
private $timeFormatForNormal = "\033[32m%2d:%03d:%03ds\033[39m";
private $timeFormatForWarning = "\033[33m%2d:%03d:%03ds\033[39m";
private $lastMemoryUsage = 0;
private $currentMemoryUsage = 0;
private $bytesFormatLabels = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
private $bytesFormatForAlert = "\033[31m%6.2f%2s\033[39m";
private $bytesFormatForNormal = "\033[32m%6.2f%2s\033[39m";
private $bytesFormatForWarning = "\033[33m%6.2f%2s\033[39m";
private $memoryUsageDecreased = "\033[32m-\033[39m";
private $memoryUsageUnchanged = "\033[34m±\033[39m";
private $memoryUsageIncreased = "\033[31m+\033[39m";
private $hideSummery = false;
private $hideDetails = false;
private $hideIncludedFiles = false;
private $infectionLine = '';
private $infectedFile = "\033[31m Infected: \033[39m%s";
private $notInfectedFile = "\033[32mNot infected: \033[39m%s";
public static function start($outputFile, $options)
{
if (self::$instance === null) {
self::$instance = new self($outputFile, $options);
}
}
private function __construct($outputFile, $options)
{
$this->hideSummery = isset($options['HS']) && $options['HS'] === true;
$this->hideDetails = isset($options['HD']) && $options['HD'] === true;
$this->hideIncludedFiles = isset($options['HIF']) && $options['HIF'] === true;
$this->lastMicrotime = microtime(true);
$this->lastMemoryUsage = memory_get_peak_usage(false);
$this->outputFile = $outputFile;
$this->debugBacktraceArg1 = version_compare(PHP_VERSION, '5.3.6', '>=')
? DEBUG_BACKTRACE_IGNORE_ARGS
: false;
$this->infectionLine = '<?php if (isset($_SERVER["HTTP_PERFBASTA"]) || isset($_SERVER["PERFBASTA"])): include_once "' . __FILE__ . '"; declare(ticks=1); endif; ?>' . "\n";
register_tick_function(array($this, 'tickHandler'));
register_shutdown_function(array($this, 'shutdownHandler'));
}
public function __destruct()
{
if (is_resource($this->outputStream)) {
fflush($this->outputStream);
fclose($this->outputStream);
}
self::$instance = null;
}
public function tickHandler()
{
$this->currentMicrotime = microtime(true);
$this->currentMemoryUsage = memory_get_usage(false);
$this->subTickHandler();
$this->lastMicrotime = microtime(true);
$this->lastMemoryUsage = memory_get_usage(false);
}
private function subTickHandler()
{
$elapsedTime = $this->currentMicrotime - $this->lastMicrotime;
$usedMemoryDelta = $this->currentMemoryUsage - $this->lastMemoryUsage;
$usedMemoryDeltaPrefix = $usedMemoryDelta < 0
? $this->memoryUsageDecreased
: (
$usedMemoryDelta > 0
? $this->memoryUsageIncreased
: $this->memoryUsageUnchanged
);
if (count($this->times) === 0) {
$this->log('>>> START');
}
$bt = debug_backtrace($this->debugBacktraceArg1);
$file = $bt[1]['file'];
$line = $bt[1]['line'];
$trace = "{$file}:{$line}";
$this->times[$file] = (isset($this->times[$file]) ? $this->times[$file] : 0.0) + $elapsedTime;
$this->times[$trace] = (isset($this->times[$trace]) ? $this->times[$trace] : 0.0) + $elapsedTime;
$this->counts[$file] = (isset($this->counts[$file]) ? $this->counts[$file] : 0) + 1;
$this->counts[$trace] = (isset($this->counts[$trace]) ? $this->counts[$trace] : 0) + 1;
if (!$this->hideDetails) {
$this->log('%s %s %s%s %s', array(
$this->formatTime($elapsedTime, 0.1, 0.001),
$this->formatBytes($this->currentMemoryUsage, 1024 * 1024 * 10, 1024 * 512),
$usedMemoryDeltaPrefix,
$this->formatBytes($usedMemoryDelta, 1024 * 512, 1024 * 16),
$trace,
));
}
}
public function shutdownHandler()
{
if (!$this->hideSummery) {
$times = $this->times;
arsort($times);
$maxCountLen = strlen(
array_reduce($this->counts, function ($mem, $count) {
return max($mem, $count);
}, 0)
);
$this->log('>>> Total Times');
foreach ($times as $trace => $time) {
$this->log("%s \033[36m%{$maxCountLen}dx\033[39m %s", array(
$this->formatTime($time, 1.0, 0.1),
$this->counts[$trace],
$trace
));
}
}
if (!$this->hideIncludedFiles) {
$this->log('>>> Included Files:');
foreach (get_included_files() as $file) {
$format = $this->isInfected($file)
? $this->infectedFile
: $this->notInfectedFile;
$this->log($format, array($file));
}
}
$this->log('>>> END');
unregister_tick_function(array($this, 'tickHandler'));
}
private function isInfected($file)
{
list($firstLine,) = file($file);
return strpos($firstLine, 'PERFBASTA') > 0;
#return substr_compare(file_get_contents($file), $this->infectionLine, 0, strlen($this->infectionLine), false) === 0;
}
private function log($format, $args = array())
{
if ($this->outputStream === null) {
$this->outputStream = fopen($this->outputFile, 'a');
$this->outputStreamSupportsLocking = stream_supports_lock($this->outputStream);
chmod($this->outputFile, 0777);
}
if (is_resource($this->outputStream)) {
$data = vsprintf("{$format}\n", $args);
if ($this->outputStreamSupportsLocking) {
fseek($this->outputStream, 0, SEEK_END);
flock($this->outputStream, LOCK_EX);
fwrite($this->outputStream, $data);
flock($this->outputStream, LOCK_UN);
} else {
fseek($this->outputStream, 0, SEEK_END);
fwrite($this->outputStream, $data);
}
}
}
private function formatBytes($bytes, $alertLimit, $warnLimit)
{
$bytes = abs($bytes);
if ($bytes < 1024) {
$value = $bytes;
$label = $this->bytesFormatLabels[0];
} else {
$factor = (int)floor(log($bytes, 1024));
$value = $bytes / pow(1024, $factor);
$label = $this->bytesFormatLabels[$factor];
}
$format = $bytes >= $alertLimit
? $this->bytesFormatForAlert
: (
$bytes >= $warnLimit
? $this->bytesFormatForWarning
: $this->bytesFormatForNormal
);
return sprintf($format, $value, $label);
}
private function formatTime($time, $alertLimit, $warnLimit)
{
$format = $time >= $alertLimit
? $this->timeFormatForAlert
: (
$time >= $warnLimit
? $this->timeFormatForWarning
: $this->timeFormatForNormal
);
return vsprintf($format, str_split(sprintf('%09d', $time * 1e6), 3));
}
}
call_user_func(function () {
if (isset($_SERVER['HTTP_PERFBASTA'])) {
$options = $_SERVER['HTTP_PERFBASTA'];
} elseif (isset($_SERVER['PERFBASTA'])) {
$options = $_SERVER['PERFBASTA'];
} else {
$options = '';
}
if (strlen($options) >= 2 && substr($options, 0, 1) === '{' && substr($options, -1) === '}') {
$options = @json_decode($options, true);
if (is_array($options)) {
Perfbasta::start('/tmp/fooo.log', $options);
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment