Index: tools/ignition/linux_perf_bytecode_annotate.py |
diff --git a/tools/ignition/linux_perf_bytecode_annotate.py b/tools/ignition/linux_perf_bytecode_annotate.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..6681190d9909a461b14de2bd2a6ee73c58caa49a |
--- /dev/null |
+++ b/tools/ignition/linux_perf_bytecode_annotate.py |
@@ -0,0 +1,174 @@ |
+#! /usr/bin/python2 |
+# |
+# 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 argparse |
+import collections |
+import os |
+import subprocess |
+import sys |
+ |
+ |
+__DESCRIPTION = """ |
+Processes a perf.data sample file and annotates the hottest instructions in a |
+given bytecode handler. |
+""" |
+ |
+ |
+__HELP_EPILOGUE = """ |
+Note: |
+ This tool uses the disassembly of interpreter's bytecode handler codegen |
+ from out/<arch>.debug/d8. you should ensure that this binary is in-sync with |
+ the version used to generate the perf profile. |
+ |
+ Also, the tool depends on the symbol offsets from perf samples being accurate. |
+ As such, you should use the ":pp" suffix for events. |
+ |
+Examples: |
+ EVENT_TYPE=cycles:pp tools/run-perf.sh out/x64.release/d8 |
+ tools/ignition/linux_perf_bytecode_annotate.py Add |
+""" |
+ |
+ |
+def bytecode_offset_generator(perf_stream, bytecode_name): |
+ skip_until_end_of_chain = False |
+ bytecode_symbol = "BytecodeHandler:" + bytecode_name; |
+ |
+ for line in perf_stream: |
+ # Lines starting with a "#" are comments, skip them. |
+ if line[0] == "#": |
+ continue |
+ line = line.strip() |
+ |
+ # Empty line signals the end of the callchain. |
+ if not line: |
+ skip_until_end_of_chain = False |
+ continue |
+ |
+ if skip_until_end_of_chain: |
+ continue |
+ |
+ symbol_and_offset = line.split(" ", 1)[1] |
+ |
+ if symbol_and_offset.startswith("BytecodeHandler:"): |
+ skip_until_end_of_chain = True |
+ |
+ if symbol_and_offset.startswith(bytecode_symbol): |
+ yield int(symbol_and_offset.split("+", 1)[1], 16) |
+ |
+ |
+def bytecode_offset_counts(bytecode_offsets): |
+ offset_counts = collections.defaultdict(int) |
+ for offset in bytecode_offsets: |
+ offset_counts[offset] += 1 |
+ return offset_counts |
+ |
+ |
+def bytecode_disassembly_generator(ignition_codegen, bytecode_name): |
+ name_string = "name = " + bytecode_name |
+ for line in ignition_codegen: |
+ if line.startswith(name_string): |
+ break |
+ |
+ # Found the bytecode disassembly. |
+ for line in ignition_codegen: |
+ line = line.strip() |
+ # Blank line marks the end of the bytecode's disassembly. |
+ if not line: |
+ return |
+ |
+ # Only yield disassembly output. |
+ if not line.startswith("0x"): |
+ continue |
+ |
+ yield line |
+ |
+ |
+def print_disassembly_annotation(offset_counts, bytecode_disassembly): |
+ total = sum(offset_counts.values()) |
+ offsets = sorted(offset_counts, reverse=True) |
+ def next_offset(): |
+ return offsets.pop() if offsets else -1 |
+ |
+ current_offset = next_offset() |
+ print current_offset; |
+ |
+ for line in bytecode_disassembly: |
+ disassembly_offset = int(line.split()[1]) |
+ if disassembly_offset == current_offset: |
+ count = offset_counts[current_offset] |
+ percentage = 100.0 * count / total |
+ print "{:>8d} ({:>5.1f}%) ".format(count, percentage), |
+ current_offset = next_offset() |
+ else: |
+ print " ", |
+ print line |
+ |
+ if offsets: |
+ print ("WARNING: Offsets not empty. Output is most likely invalid due to " |
+ "a mismatch between perf output and debug d8 binary.") |
+ |
+ |
+def parse_command_line(): |
+ command_line_parser = argparse.ArgumentParser( |
+ formatter_class=argparse.RawDescriptionHelpFormatter, |
+ description=__DESCRIPTION, |
+ epilog=__HELP_EPILOGUE) |
+ |
+ command_line_parser.add_argument( |
+ "--arch", "-a", |
+ help="The architecture (default: x64)", |
+ default="x64", |
+ ) |
+ command_line_parser.add_argument( |
+ "--input", "-i", |
+ help="perf sample file to process (default: perf.data)", |
+ default="perf.data", |
+ metavar="<perf filename>", |
+ dest="perf_filename" |
+ ) |
+ 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" |
+ ) |
+ command_line_parser.add_argument( |
+ "bytecode_name", |
+ metavar="<bytecode name>", |
+ nargs="?", |
+ help="The bytecode handler to annotate" |
+ ) |
+ |
+ return command_line_parser.parse_args() |
+ |
+ |
+def main(): |
+ program_options = parse_command_line() |
+ perf = subprocess.Popen(["perf", "script", "-f", "ip,sym,symoff", |
+ "-i", program_options.perf_filename], |
+ stdout=subprocess.PIPE) |
+ |
+ v8_root_path = os.path.dirname(__file__) + "/../../" |
+ d8_path = "{}/out/{}.debug/d8".format(v8_root_path, program_options.arch) |
+ d8_codegen = subprocess.Popen([d8_path, "--ignition", |
+ "--trace-ignition-codegen", "-e", "1"], |
+ stdout=subprocess.PIPE) |
+ |
+ bytecode_offsets = bytecode_offset_generator( |
+ perf.stdout, program_options.bytecode_name) |
+ offset_counts = bytecode_offset_counts(bytecode_offsets) |
+ |
+ bytecode_disassembly = bytecode_disassembly_generator( |
+ d8_codegen.stdout, program_options.bytecode_name) |
+ |
+ print_disassembly_annotation(offset_counts, bytecode_disassembly) |
+ |
+ |
+if __name__ == "__main__": |
+ main() |