Created
September 23, 2022 09:20
-
-
Save pounard/7702fc4fb3175e2dfb7c886e9bd7b975 to your computer and use it in GitHub Desktop.
This file contains 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 | |
function usage($programeName) { | |
echo <<<EOT | |
Usage: | |
{$programeName} TRACE_FILE | |
EOT; | |
} | |
if (empty($argv[1])) { | |
usage($argv[0]); | |
die(); | |
} | |
$filename = $argv[1]; | |
if (!file_exists($filename)) { | |
die("input file does not exist"); | |
} | |
if (!is_readable($filename)) { | |
die("cannot read input file"); | |
} | |
class Registry | |
{ | |
private array $functions = []; | |
public function add(string $id, int $memory): void | |
{ | |
if (array_key_exists($id, $this->functions)) { | |
$this->functions[$id] += $memory; | |
} else { | |
$this->functions[$id] = $memory; | |
} | |
} | |
public function all(): array | |
{ | |
return $this->functions; | |
} | |
} | |
class TraceNode | |
{ | |
private string $name; | |
private ?string $prefix = null; | |
private int $memoryStart = 0; | |
private int $memoryStop = 0; | |
private int $childMemory = 0; | |
public function __construct(string $name, int $memoryStart, ?string $prefix) | |
{ | |
$this->name = $name; | |
$this->memoryStart = $memoryStart; | |
$this->prefix = $prefix; | |
} | |
public function getName(): string | |
{ | |
return $this->name; | |
} | |
public function getAbsoluteName(): string | |
{ | |
if ($this->prefix) { | |
return $this->prefix . ';' . $this->name; | |
} | |
return $this->name; | |
} | |
public function exit(int $memoryStop) | |
{ | |
$this->memoryStop = $memoryStop; | |
} | |
public function addChildCost(TraceNode $node): void | |
{ | |
$this->childMemory += $node->getInclusiveMemory(); | |
} | |
public function getInclusiveMemory(): int | |
{ | |
return ($this->memoryStop - $this->memoryStart); | |
} | |
public function getSelfMemory(): float | |
{ | |
return $this->getInclusiveMemory() - $this->childMemory; | |
} | |
} | |
$registry = new Registry(); | |
$handle = fopen($filename, 'r'); | |
if (!$handle) { | |
die("error while opening input file"); | |
} | |
/** | |
* Exit from a function, display its cost. | |
*/ | |
function handleExit(/* resource */ $handle, array $data, TraceNode $function): void | |
{ | |
global $registry; | |
$function->exit((int) $data[4]); | |
if (0 < ($bytes = $function->getSelfMemory())) { | |
$registry->add($function->getName(), $bytes); | |
} | |
} | |
/** | |
* Create and recurse into function. | |
*/ | |
function createFunction(/* resource */ $handle, array $data, ?TraceNode $parent = null): TraceNode | |
{ | |
return new TraceNode( | |
(string)$data[5], // Name | |
(int) $data[4], // Memory start | |
$parent ? $parent->getAbsoluteName() : null | |
); | |
} | |
/** | |
* Parse next line. | |
*/ | |
function parseLine(/* resource */ $handle): ?array | |
{ | |
while (!\feof($handle)) { | |
$line = \stream_get_line($handle, 1000000, "\n"); | |
// Sometime indent uses more than one \t hence the \array_filter(). | |
$data = \array_values( | |
\array_filter( | |
\explode("\t", $line), | |
fn ($line) => $line !== '' | |
) | |
); | |
if (\count($data) < 5) { | |
continue; | |
} | |
return $data; | |
} | |
return null; | |
} | |
/** | |
* Handle single line. | |
* | |
* @return ?RelativeCost | |
* Sum of relative costs. Null if exit. | |
*/ | |
function handleLine(/* resource */ $handle, array $data, ?TraceNode $parent = null): ?TraceNode | |
{ | |
if (isset($data[5])) { | |
$atLeastOne = false; | |
$function = createFunction($handle, $data, $parent); | |
// Parse all children until exit. | |
while ($data = parseLine($handle)) { | |
$atLeastOne = true; | |
if ($childNode = handleLine($handle, $data, $function)) { | |
$function->addChildCost($childNode); | |
} else{ | |
break; // We just found the exit statement of this function. | |
} | |
} | |
if (!$atLeastOne) { | |
throw new \Exception("File ended with unclosed function " . $function->getAbsoluteName()); | |
} | |
return $function; | |
} else if (!$parent) { | |
throw new \Exception("Cannot exit without parent."); | |
} else { | |
handleExit($handle, $data, $parent); | |
return null; | |
} | |
} | |
try { | |
// Handle top-level calls, in PHP there is always one, which is '{main}' | |
// nevertheless, better be safe than sorry. | |
while ($data = parseLine($handle)) { | |
handleLine($handle, $data); | |
} | |
} catch (\Throwable $e) { | |
print $e->getMessage() . "\n"; | |
} | |
$functions = $registry->all(); | |
asort($functions); | |
print_r($functions); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment