#! /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()