Index: tools/ignition_perf_report.py |
diff --git a/tools/ignition_perf_report.py b/tools/ignition_perf_report.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..5f8e003f780c1677779a31200b1b654011622836 |
--- /dev/null |
+++ b/tools/ignition_perf_report.py |
@@ -0,0 +1,138 @@ |
+#! /usr/bin/python2 |
+# |
Michael Achenbach
2016/03/10 11:50:36
nit: Not sure if presubmit allows this empty comme
|
+# Copyright 2016 the V8 project authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+# |
+ |
+import re |
+import sys |
+import collections |
Michael Achenbach
2016/03/10 11:50:36
nit: alpha order
|
+import subprocess |
+import argparse |
+ |
+ |
+DESCRIPTION = """ |
+Processes a perf.data sample file and reports the hottest Ignition bytecodes, |
+or write an input file for flamegraph.pl. |
+""" |
+ |
+ |
+EPILOGUE = """ |
+examples: |
+ # Get a flamegraph for Ignition bytecode handlers on Octane benchmark, |
+ # without considering time spent compiling JS code. |
+ # |
+ $ tools/run-perf.sh out/x64.release/d8 --ignition octane/run.js |
+ $ tools/ignition_perf_report.py --flamegraph --hide-compile -o out.collapsed |
+ $ flamegraph.pl --colors js out.collapsed > out.svg |
+ |
+ # See the hottest bytecodes on Octane benchmark, by number of samples. |
+ # |
+ $ tools/run-perf.sh out/x64.release/d8 --ignition octane/run.js |
+ $ tools/ignition_perf_report.py |
+""" |
+ |
+ |
+COMPILER_SYMBOLS_RE = re.compile(r"Builtin:Compile(?:Lazy|OptimizedConcurrent)" |
+ "$|LazyCompile:|v8::internal::Compile") |
+ |
+ |
+def yield_collapsed_callchains(perf_stream, hide_compile_time=False): |
+ current_chain = [] |
+ keep_parsing_chain = True |
+ for line in perf_stream: |
+ if line[0] == "#": continue |
+ line = line.strip() |
+ if not line: |
+ keep_parsing_chain = True |
+ current_chain = [] |
+ continue |
+ if not keep_parsing_chain: continue |
+ symbol = line.split(" ", 1)[1] |
+ # Strip the parameters from the signature (i.e. the parts between braces) |
+ symbol = symbol[0] + symbol[1:].split("(", 1)[0] |
+ current_chain.append(symbol) |
+ if hide_compile_time and COMPILER_SYMBOLS_RE.match(symbol): |
+ keep_parsing_chain = False |
+ elif symbol.startswith("BytecodeHandler:"): |
+ keep_parsing_chain = False |
+ yield current_chain |
+ |
+ |
+def count_callchains(perf_stream, hide_compile_time): |
+ chain_counters = collections.defaultdict(int) |
+ for callchain in yield_collapsed_callchains(perf_stream, hide_compile_time): |
+ key = ";".join(reversed(callchain)) |
+ chain_counters[key] += 1 |
+ return chain_counters.items() |
+ |
+ |
+def count_handler_samples(perf_stream, hide_compile_time): |
+ handler_counters = collections.defaultdict(int) |
+ for callchain in yield_collapsed_callchains(perf_stream, hide_compile_time): |
+ # Strip the "BytecodeHandler:" prefix |
+ handler = callchain[-1].split(":", 1)[1] |
Michael Achenbach
2016/03/10 11:50:36
Assuming that there is no other : in callchain[-1]
|
+ handler_counters[handler] += 1 |
+ # Sort by decreasing number of samples |
+ return sorted(handler_counters.items(), |
+ key=lambda entry: entry[1], reverse=True) |
+ |
+ |
+def parse_command_line(): |
+ command_line_parser = argparse.ArgumentParser( |
+ formatter_class=argparse.RawDescriptionHelpFormatter, |
+ description=DESCRIPTION, |
+ epilog=EPILOGUE) |
+ |
+ command_line_parser.add_argument( |
+ "perf_filename", |
+ help="perf sample file to process (default: perf.data)", |
+ nargs="?", |
+ default="perf.data", |
+ metavar="<perf filename>") |
+ command_line_parser.add_argument( |
+ "--flamegraph", "-f", |
+ help="output an input file for flamegraph.pl, not a report", |
+ action="store_true", |
+ dest="output_flamegraph") |
+ command_line_parser.add_argument( |
+ "--hide-compile", "-c", |
+ help="do not count samples inside compiler routines", |
+ action="store_true", |
+ dest="hide_compile_time") |
+ command_line_parser.add_argument( |
+ "--output", "-o", |
+ help="output file name (stdout if omitted)", |
+ type=argparse.FileType('wt'), |
+ default=sys.stdout, |
+ metavar="<output filename>", |
+ dest="output_stream") |
+ |
+ return command_line_parser.parse_args() |
+ |
+ |
+def main(): |
+ program_options = parse_command_line() |
+ output_stream = program_options.output_stream |
+ perf = subprocess.Popen(["perf", "script", "-f", "ip,sym", |
+ "-i", program_options.perf_filename], |
+ stdout=subprocess.PIPE) |
+ |
+ if program_options.output_flamegraph: |
+ callchain_counters = count_callchains(perf.stdout, |
+ program_options.hide_compile_time) |
+ for callchain, count in callchain_counters: |
+ output_stream.write("{} {}\n".format(callchain, count)) |
+ else: |
+ handler_counters = count_handler_samples(perf.stdout, |
+ program_options.hide_compile_time) |
+ samples_num = sum(counter for _, counter in handler_counters) |
+ for bytecode_name, count in handler_counters: |
+ output_stream.write( |
+ "{}\t{}\t{:.3f}%\n".format(bytecode_name, count, |
+ 100. * count / samples_num)) |
+ |
+ |
+if __name__ == "__main__": |
+ main() |