OLD | NEW |
---|---|
(Empty) | |
1 #! /usr/bin/python2 | |
2 # | |
Michael Achenbach
2016/03/10 11:50:36
nit: Not sure if presubmit allows this empty comme
| |
3 # Copyright 2016 the V8 project authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 # | |
7 | |
8 import re | |
9 import sys | |
10 import collections | |
Michael Achenbach
2016/03/10 11:50:36
nit: alpha order
| |
11 import subprocess | |
12 import argparse | |
13 | |
14 | |
15 DESCRIPTION = """ | |
16 Processes a perf.data sample file and reports the hottest Ignition bytecodes, | |
17 or write an input file for flamegraph.pl. | |
18 """ | |
19 | |
20 | |
21 EPILOGUE = """ | |
22 examples: | |
23 # Get a flamegraph for Ignition bytecode handlers on Octane benchmark, | |
24 # without considering time spent compiling JS code. | |
25 # | |
26 $ tools/run-perf.sh out/x64.release/d8 --ignition octane/run.js | |
27 $ tools/ignition_perf_report.py --flamegraph --hide-compile -o out.collapsed | |
28 $ flamegraph.pl --colors js out.collapsed > out.svg | |
29 | |
30 # See the hottest bytecodes on Octane benchmark, by number of samples. | |
31 # | |
32 $ tools/run-perf.sh out/x64.release/d8 --ignition octane/run.js | |
33 $ tools/ignition_perf_report.py | |
34 """ | |
35 | |
36 | |
37 COMPILER_SYMBOLS_RE = re.compile(r"Builtin:Compile(?:Lazy|OptimizedConcurrent)" | |
38 "$|LazyCompile:|v8::internal::Compile") | |
39 | |
40 | |
41 def yield_collapsed_callchains(perf_stream, hide_compile_time=False): | |
42 current_chain = [] | |
43 keep_parsing_chain = True | |
44 for line in perf_stream: | |
45 if line[0] == "#": continue | |
46 line = line.strip() | |
47 if not line: | |
48 keep_parsing_chain = True | |
49 current_chain = [] | |
50 continue | |
51 if not keep_parsing_chain: continue | |
52 symbol = line.split(" ", 1)[1] | |
53 # Strip the parameters from the signature (i.e. the parts between braces) | |
54 symbol = symbol[0] + symbol[1:].split("(", 1)[0] | |
55 current_chain.append(symbol) | |
56 if hide_compile_time and COMPILER_SYMBOLS_RE.match(symbol): | |
57 keep_parsing_chain = False | |
58 elif symbol.startswith("BytecodeHandler:"): | |
59 keep_parsing_chain = False | |
60 yield current_chain | |
61 | |
62 | |
63 def count_callchains(perf_stream, hide_compile_time): | |
64 chain_counters = collections.defaultdict(int) | |
65 for callchain in yield_collapsed_callchains(perf_stream, hide_compile_time): | |
66 key = ";".join(reversed(callchain)) | |
67 chain_counters[key] += 1 | |
68 return chain_counters.items() | |
69 | |
70 | |
71 def count_handler_samples(perf_stream, hide_compile_time): | |
72 handler_counters = collections.defaultdict(int) | |
73 for callchain in yield_collapsed_callchains(perf_stream, hide_compile_time): | |
74 # Strip the "BytecodeHandler:" prefix | |
75 handler = callchain[-1].split(":", 1)[1] | |
Michael Achenbach
2016/03/10 11:50:36
Assuming that there is no other : in callchain[-1]
| |
76 handler_counters[handler] += 1 | |
77 # Sort by decreasing number of samples | |
78 return sorted(handler_counters.items(), | |
79 key=lambda entry: entry[1], reverse=True) | |
80 | |
81 | |
82 def parse_command_line(): | |
83 command_line_parser = argparse.ArgumentParser( | |
84 formatter_class=argparse.RawDescriptionHelpFormatter, | |
85 description=DESCRIPTION, | |
86 epilog=EPILOGUE) | |
87 | |
88 command_line_parser.add_argument( | |
89 "perf_filename", | |
90 help="perf sample file to process (default: perf.data)", | |
91 nargs="?", | |
92 default="perf.data", | |
93 metavar="<perf filename>") | |
94 command_line_parser.add_argument( | |
95 "--flamegraph", "-f", | |
96 help="output an input file for flamegraph.pl, not a report", | |
97 action="store_true", | |
98 dest="output_flamegraph") | |
99 command_line_parser.add_argument( | |
100 "--hide-compile", "-c", | |
101 help="do not count samples inside compiler routines", | |
102 action="store_true", | |
103 dest="hide_compile_time") | |
104 command_line_parser.add_argument( | |
105 "--output", "-o", | |
106 help="output file name (stdout if omitted)", | |
107 type=argparse.FileType('wt'), | |
108 default=sys.stdout, | |
109 metavar="<output filename>", | |
110 dest="output_stream") | |
111 | |
112 return command_line_parser.parse_args() | |
113 | |
114 | |
115 def main(): | |
116 program_options = parse_command_line() | |
117 output_stream = program_options.output_stream | |
118 perf = subprocess.Popen(["perf", "script", "-f", "ip,sym", | |
119 "-i", program_options.perf_filename], | |
120 stdout=subprocess.PIPE) | |
121 | |
122 if program_options.output_flamegraph: | |
123 callchain_counters = count_callchains(perf.stdout, | |
124 program_options.hide_compile_time) | |
125 for callchain, count in callchain_counters: | |
126 output_stream.write("{} {}\n".format(callchain, count)) | |
127 else: | |
128 handler_counters = count_handler_samples(perf.stdout, | |
129 program_options.hide_compile_time) | |
130 samples_num = sum(counter for _, counter in handler_counters) | |
131 for bytecode_name, count in handler_counters: | |
132 output_stream.write( | |
133 "{}\t{}\t{:.3f}%\n".format(bytecode_name, count, | |
134 100. * count / samples_num)) | |
135 | |
136 | |
137 if __name__ == "__main__": | |
138 main() | |
OLD | NEW |