/hphp/tools/perf-call-graph.php
https://gitlab.com/iranjith4/hhvm · PHP · 159 lines · 116 code · 26 blank · 17 comment · 22 complexity · 2569bd471557e542f34937fc2c0da564 MD5 · raw file
- #!/usr/local/bin/php -j
- <?php
- require(__DIR__.'/perf-lib.php');
- # Returns true iff $sample contains a line containing $func.
- function contains_frame(Vector $sample, string $func): bool {
- foreach ($sample as $frame) {
- if (strstr($frame, $func) !== false) return true;
- }
- return false;
- }
- # Node is used to construct to call graph. It contains an inclusive count of
- # samples for itself and all of its children, along with a map from function
- # names to children.
- class Node {
- private $kids = Map {};
- public function __construct(private $name, private $count = 0) {}
- # Add an edge from this function to $name, increasing the count of the child
- # Node by one and returning it.
- public function followEdge($name) {
- if (!isset($this->kids[$name])) $this->kids[$name] = new Node($name);
- $new_node = $this->kids[$name];
- ++$new_node->count;
- return $new_node;
- }
- # Print out the current node and all children.
- public function show(
- $total_count = null,
- $indent = '',
- ) {
- if ($total_count === null) {
- # We're the parent node and don't contain a useful name.
- $total_count = $this->count;
- } else {
- # Prune any subtrees that are <0.5% of the total to keep the output
- # readable.
- if ($this->count / $total_count < 0.005) return;
- printf("%s%.1f%% %s\n",
- $indent,
- $this->count / $total_count * 100,
- $this->name);
- $indent .= '- ';
- }
- $items = $this->kids->items()->toVector();
- if ($items->count() == 0) return;
- # If our only printable child is the body of this function, leave it out to
- # keep the results clean.
- if ($items[0][0] == '<body>' &&
- ($items->count() == 1 || $items[1][1]->count / $total_count < 0.005)) {
- return;
- }
- usort($items, ($a, $b) ==> $b[1]->count - $a[1]->count);
- foreach ($items as $pair) {
- $pair[1]->show($total_count, $indent);
- }
- }
- }
- # Build and print a perf-annotated call graph of each stack trace in $samples,
- # truncated at the highest frame containing $top.
- function treeify($samples, $top) {
- $root = new Node('', $samples->count());
- foreach ($samples as $stack) {
- for ($i = $stack->count() - 1; $i >= 0; --$i) {
- if (strpos($stack[$i], $top) !== false) break;
- }
- $node = $root;
- for (; $i >= 0; --$i) {
- $node = $node->followEdge($stack[$i]);
- }
- # Add a final entry for exclusive time spent in the body of the bottom
- # function.
- $node->followEdge('<body>');
- }
- $root->show();
- }
- function usage($script_name) {
- echo <<<EOT
- Usage:
- $script_name [symbol]
- This script expects the output of "perf script -f comm,ip,sym" on stdin. If the
- optional symbol argument is present, a call graph of all frames containing that
- symbol will be output, with the root of the frame truncated at the highest
- frame containing the symbol. Note that the symbol may appear anywhere in the
- function name, so using 'Foo::translate' will match both 'Foo::translate' and
- 'Foo::translateFrob'. If you just want Foo::translate, use 'Foo::translate('.
- If symbol is not present, the total number of samples present will be printed
- along with the number of samples that contain a few hardcoded functions.
- EOT;
- }
- function main($argv) {
- ini_set('memory_limit', '64G');
- if (posix_isatty(STDIN)) {
- usage($argv[0]);
- return 1;
- }
- $samples = read_perf_samples(STDIN);
- if (count($argv) == 2) {
- $functions = Set { $argv[1] };
- } else {
- $functions = Set {
- 'MCGenerator::translate',
- 'jit::selectTracelet(',
- 'Translator::translateRegion(',
- 'jit::optimizeRefcounts(',
- 'jit::optimize(',
- 'jit::allocateRegs(',
- 'jit::genCode(',
- 'getStackValue(',
- 'IRBuilder::constrain',
- 'relaxGuards(',
- 'MCGenerator::retranslateOpt(',
- 'IRTranslator::translateInstr(',
- 'Type::subtypeOf(',
- 'jit::regionizeFunc',
- };
- }
- $subsamples = Map {'all' => $samples};
- foreach ($functions as $f) {
- $subsamples[$f] = $samples->filter($s ==> contains_frame($s, $f));
- }
- if (count($argv) == 2) {
- $sub = $subsamples[$argv[1]];
- printf("Looking for pattern *%s*. %d of %d total samples (%.2f%%)\n\n",
- $argv[1], $sub->count(), $samples->count(),
- $sub->count() / $samples->count() * 100);
- treeify($subsamples[$argv[1]], $argv[1]);
- } else {
- foreach ($subsamples as $k => $v) {
- printf("%5d %s\n", $v->count(), $k);
- }
- }
- return 0;
- }
- exit(main($argv));