1 #!/usr/local/bin/php -j
4 require(__DIR__
.'/perf-lib.php');
6 # Returns true iff $sample contains a line containing $func.
7 function contains_frame(Vector
$sample, string $func): bool {
8 foreach ($sample as $frame) {
9 if (strstr($frame, $func) !== false) return true;
14 # Node is used to construct to call graph. It contains an inclusive count of
15 # samples for itself and all of its children, along with a map from function
18 private $kids = Map
{};
20 public function __construct(private $name, private $count = 0) {}
22 # Add an edge from this function to $name, increasing the count of the child
23 # Node by one and returning it.
24 public function followEdge($name) {
25 if (!isset($this->kids
[$name])) $this->kids
[$name] = new Node($name);
27 $new_node = $this->kids
[$name];
32 # Print out the current node and all children.
37 if ($total_count === null) {
38 # We're the parent node and don't contain a useful name.
39 $total_count = $this->count
;
41 # Prune any subtrees that are <0.5% of the total to keep the output
43 if ($this->count
/ $total_count < 0.005) return;
44 printf("%s%.1f%% %s\n",
46 $this->count
/ $total_count * 100,
51 $items = $this->kids
->items()->toVector();
52 if ($items->count() == 0) return;
54 # If our only printable child is the body of this function, leave it out to
55 # keep the results clean.
56 if ($items[0][0] == '<body>' &&
57 ($items->count() == 1 ||
$items[1][1]->count
/ $total_count < 0.005)) {
61 usort(inout
$items, ($a, $b) ==> $b[1]->count
- $a[1]->count
);
62 foreach ($items as $pair) {
63 $pair[1]->show($total_count, $indent);
68 # Build and print a perf-annotated call graph of each stack trace in $samples.
69 # If $top is set, each trace will be truncated at the highest frame containing
70 # $top, if $root_last == false, or the lowest frame containing $top, if
78 $root = new Node('', $samples->count());
79 $step = $reverse ?
1 : -1;
80 $search_step = $root_last ?
-$step : $step;
82 foreach ($samples as $stack) {
83 $first = $reverse ?
0 : $stack->count() - 1;
84 $last = $reverse ?
$stack->count() - 1 : 0;
85 $search_first = $root_last ?
$last : $first;
86 $search_last = $root_last ?
$first : $last;
89 for ($i = $search_first; $i !== $search_last +
$search_step;
91 if (strpos($stack[$i], $top) !== false) break;
98 for (; $i !== $last +
$step; $i +
= $step) {
100 if ($func === 'HHVM::retInlHelper') continue;
101 $func = preg_replace('/^PHP::.+\.php::/', 'PHP::', $func);
103 $node = $node->followEdge($func);
106 # Add a final entry for exclusive time spent in the body of the bottom
108 $node->followEdge('<body>');
115 function usage($script_name) {
119 $script_name [--reverse] [--root-last] [--exe <name>] [symbol]...
121 This script expects the output of "perf script --fields comm,ip,sym" on stdin.
122 If no symbols are given, all samples will be combined into a single tree showing
123 the call graph, annotated with the inclusive cost of each node.
125 If one symbol is given, the call graph will instead be built from samples with
126 at least one frame containing the symbol. Samples will be truncated at the
127 highest frame containing the symbol (or the lowest, if --root-last is given).
128 Note that the symbol may appear anywhere in the function name, so using
129 'Foo::translate' will match both 'Foo::translate' and 'Foo::translateFrob'.
130 If you just want Foo::translate, use 'Foo::translate('.
132 Finally, if multiple symbols are given, a summary will be printed showing the
133 total number of samples, and how many samples contain each symbol.
135 If --reverse is given, the call graph will be inverted before tree formation,
136 showing callers of any given symbols, rather than callees.
138 By default, only samples from the 'hhvm' binary will be considered. Giving
139 --exe with an argument overrides this.
143 function main($argv) {
144 ini_set('memory_limit', '64G');
145 if (posix_isatty(STDIN
)) {
155 for ($i = 0; $i < count($argv); ++
$i) {
157 if ($arg=== '--reverse') {
159 } else if ($arg === '--root-last') {
161 } else if ($arg === '--exe') {
164 $functions->add($arg);
168 $samples = read_perf_samples(STDIN
, $exe);
169 $subsamples = Map
{'all' => $samples};
170 foreach ($functions as $f) {
171 $subsamples[$f] = $samples->filter($s ==> contains_frame($s, $f));
174 if ($functions->isEmpty()) {
175 treeify($subsamples['all'], $reverse, $root_last, null);
176 } else if ($functions->count() === 1) {
177 $func = $functions->firstValue();
178 $sub = $subsamples[$func];
179 printf("Looking for pattern *%s*. %d of %d total samples (%.2f%%)\n\n",
180 $func, $sub->count(), $samples->count(),
181 $sub->count() / $samples->count() * 100);
182 treeify($sub, $reverse, $root_last, $func);
184 foreach ($subsamples as $k => $v) {
186 "%8d %5.1f %s\n", $v->count(), $v->count() / $samples->count() * 100, $k