/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

  1. #!/usr/local/bin/php -j
  2. <?php
  3. require(__DIR__.'/perf-lib.php');
  4. # Returns true iff $sample contains a line containing $func.
  5. function contains_frame(Vector $sample, string $func): bool {
  6. foreach ($sample as $frame) {
  7. if (strstr($frame, $func) !== false) return true;
  8. }
  9. return false;
  10. }
  11. # Node is used to construct to call graph. It contains an inclusive count of
  12. # samples for itself and all of its children, along with a map from function
  13. # names to children.
  14. class Node {
  15. private $kids = Map {};
  16. public function __construct(private $name, private $count = 0) {}
  17. # Add an edge from this function to $name, increasing the count of the child
  18. # Node by one and returning it.
  19. public function followEdge($name) {
  20. if (!isset($this->kids[$name])) $this->kids[$name] = new Node($name);
  21. $new_node = $this->kids[$name];
  22. ++$new_node->count;
  23. return $new_node;
  24. }
  25. # Print out the current node and all children.
  26. public function show(
  27. $total_count = null,
  28. $indent = '',
  29. ) {
  30. if ($total_count === null) {
  31. # We're the parent node and don't contain a useful name.
  32. $total_count = $this->count;
  33. } else {
  34. # Prune any subtrees that are <0.5% of the total to keep the output
  35. # readable.
  36. if ($this->count / $total_count < 0.005) return;
  37. printf("%s%.1f%% %s\n",
  38. $indent,
  39. $this->count / $total_count * 100,
  40. $this->name);
  41. $indent .= '- ';
  42. }
  43. $items = $this->kids->items()->toVector();
  44. if ($items->count() == 0) return;
  45. # If our only printable child is the body of this function, leave it out to
  46. # keep the results clean.
  47. if ($items[0][0] == '<body>' &&
  48. ($items->count() == 1 || $items[1][1]->count / $total_count < 0.005)) {
  49. return;
  50. }
  51. usort($items, ($a, $b) ==> $b[1]->count - $a[1]->count);
  52. foreach ($items as $pair) {
  53. $pair[1]->show($total_count, $indent);
  54. }
  55. }
  56. }
  57. # Build and print a perf-annotated call graph of each stack trace in $samples,
  58. # truncated at the highest frame containing $top.
  59. function treeify($samples, $top) {
  60. $root = new Node('', $samples->count());
  61. foreach ($samples as $stack) {
  62. for ($i = $stack->count() - 1; $i >= 0; --$i) {
  63. if (strpos($stack[$i], $top) !== false) break;
  64. }
  65. $node = $root;
  66. for (; $i >= 0; --$i) {
  67. $node = $node->followEdge($stack[$i]);
  68. }
  69. # Add a final entry for exclusive time spent in the body of the bottom
  70. # function.
  71. $node->followEdge('<body>');
  72. }
  73. $root->show();
  74. }
  75. function usage($script_name) {
  76. echo <<<EOT
  77. Usage:
  78. $script_name [symbol]
  79. This script expects the output of "perf script -f comm,ip,sym" on stdin. If the
  80. optional symbol argument is present, a call graph of all frames containing that
  81. symbol will be output, with the root of the frame truncated at the highest
  82. frame containing the symbol. Note that the symbol may appear anywhere in the
  83. function name, so using 'Foo::translate' will match both 'Foo::translate' and
  84. 'Foo::translateFrob'. If you just want Foo::translate, use 'Foo::translate('.
  85. If symbol is not present, the total number of samples present will be printed
  86. along with the number of samples that contain a few hardcoded functions.
  87. EOT;
  88. }
  89. function main($argv) {
  90. ini_set('memory_limit', '64G');
  91. if (posix_isatty(STDIN)) {
  92. usage($argv[0]);
  93. return 1;
  94. }
  95. $samples = read_perf_samples(STDIN);
  96. if (count($argv) == 2) {
  97. $functions = Set { $argv[1] };
  98. } else {
  99. $functions = Set {
  100. 'MCGenerator::translate',
  101. 'jit::selectTracelet(',
  102. 'Translator::translateRegion(',
  103. 'jit::optimizeRefcounts(',
  104. 'jit::optimize(',
  105. 'jit::allocateRegs(',
  106. 'jit::genCode(',
  107. 'getStackValue(',
  108. 'IRBuilder::constrain',
  109. 'relaxGuards(',
  110. 'MCGenerator::retranslateOpt(',
  111. 'IRTranslator::translateInstr(',
  112. 'Type::subtypeOf(',
  113. 'jit::regionizeFunc',
  114. };
  115. }
  116. $subsamples = Map {'all' => $samples};
  117. foreach ($functions as $f) {
  118. $subsamples[$f] = $samples->filter($s ==> contains_frame($s, $f));
  119. }
  120. if (count($argv) == 2) {
  121. $sub = $subsamples[$argv[1]];
  122. printf("Looking for pattern *%s*. %d of %d total samples (%.2f%%)\n\n",
  123. $argv[1], $sub->count(), $samples->count(),
  124. $sub->count() / $samples->count() * 100);
  125. treeify($subsamples[$argv[1]], $argv[1]);
  126. } else {
  127. foreach ($subsamples as $k => $v) {
  128. printf("%5d %s\n", $v->count(), $k);
  129. }
  130. }
  131. return 0;
  132. }
  133. exit(main($argv));