| #! /usr/bin/python |
| # |
| # 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. |
| # |
| |
| # for py2/py3 compatibility |
| from __future__ import print_function |
| |
| 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), end=' ') |
| current_offset = next_offset() |
| else: |
| print(" ", end=' ') |
| 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, "--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() |