Skip to content

Instantly share code, notes, and snippets.

@pounard
Created September 23, 2022 09:20
Show Gist options
  • Save pounard/7702fc4fb3175e2dfb7c886e9bd7b975 to your computer and use it in GitHub Desktop.
Save pounard/7702fc4fb3175e2dfb7c886e9bd7b975 to your computer and use it in GitHub Desktop.
<?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