Home | History | Annotate | Download | only in ignition
      1 #! /usr/bin/python2
      2 #
      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 argparse
      9 import collections
     10 import os
     11 import subprocess
     12 import sys
     13 
     14 
     15 __DESCRIPTION = """
     16 Processes a perf.data sample file and annotates the hottest instructions in a
     17 given bytecode handler.
     18 """
     19 
     20 
     21 __HELP_EPILOGUE = """
     22 Note:
     23   This tool uses the disassembly of interpreter's bytecode handler codegen
     24   from out/<arch>.debug/d8. you should ensure that this binary is in-sync with
     25   the version used to generate the perf profile.
     26 
     27   Also, the tool depends on the symbol offsets from perf samples being accurate.
     28   As such, you should use the ":pp" suffix for events.
     29 
     30 Examples:
     31   EVENT_TYPE=cycles:pp tools/run-perf.sh out/x64.release/d8
     32   tools/ignition/linux_perf_bytecode_annotate.py Add
     33 """
     34 
     35 
     36 def bytecode_offset_generator(perf_stream, bytecode_name):
     37   skip_until_end_of_chain = False
     38   bytecode_symbol = "BytecodeHandler:" + bytecode_name;
     39 
     40   for line in perf_stream:
     41     # Lines starting with a "#" are comments, skip them.
     42     if line[0] == "#":
     43       continue
     44     line = line.strip()
     45 
     46     # Empty line signals the end of the callchain.
     47     if not line:
     48       skip_until_end_of_chain = False
     49       continue
     50 
     51     if skip_until_end_of_chain:
     52       continue
     53 
     54     symbol_and_offset = line.split(" ", 1)[1]
     55 
     56     if symbol_and_offset.startswith("BytecodeHandler:"):
     57       skip_until_end_of_chain = True
     58 
     59       if symbol_and_offset.startswith(bytecode_symbol):
     60         yield int(symbol_and_offset.split("+", 1)[1], 16)
     61 
     62 
     63 def bytecode_offset_counts(bytecode_offsets):
     64   offset_counts = collections.defaultdict(int)
     65   for offset in bytecode_offsets:
     66     offset_counts[offset] += 1
     67   return offset_counts
     68 
     69 
     70 def bytecode_disassembly_generator(ignition_codegen, bytecode_name):
     71   name_string = "name = " + bytecode_name
     72   for line in ignition_codegen:
     73     if line.startswith(name_string):
     74       break
     75 
     76   # Found the bytecode disassembly.
     77   for line in ignition_codegen:
     78     line = line.strip()
     79     # Blank line marks the end of the bytecode's disassembly.
     80     if not line:
     81       return
     82 
     83     # Only yield disassembly output.
     84     if not line.startswith("0x"):
     85       continue
     86 
     87     yield line
     88 
     89 
     90 def print_disassembly_annotation(offset_counts, bytecode_disassembly):
     91   total = sum(offset_counts.values())
     92   offsets = sorted(offset_counts, reverse=True)
     93   def next_offset():
     94     return offsets.pop() if offsets else -1
     95 
     96   current_offset = next_offset()
     97   print current_offset;
     98 
     99   for line in bytecode_disassembly:
    100     disassembly_offset = int(line.split()[1])
    101     if disassembly_offset == current_offset:
    102       count = offset_counts[current_offset]
    103       percentage = 100.0 * count / total
    104       print "{:>8d} ({:>5.1f}%) ".format(count, percentage),
    105       current_offset = next_offset()
    106     else:
    107       print "                ",
    108     print line
    109 
    110   if offsets:
    111     print ("WARNING: Offsets not empty. Output is most likely invalid due to "
    112            "a mismatch between perf output and debug d8 binary.")
    113 
    114 
    115 def parse_command_line():
    116   command_line_parser = argparse.ArgumentParser(
    117       formatter_class=argparse.RawDescriptionHelpFormatter,
    118       description=__DESCRIPTION,
    119       epilog=__HELP_EPILOGUE)
    120 
    121   command_line_parser.add_argument(
    122       "--arch", "-a",
    123       help="The architecture (default: x64)",
    124       default="x64",
    125   )
    126   command_line_parser.add_argument(
    127       "--input", "-i",
    128       help="perf sample file to process (default: perf.data)",
    129       default="perf.data",
    130       metavar="<perf filename>",
    131       dest="perf_filename"
    132   )
    133   command_line_parser.add_argument(
    134       "--output", "-o",
    135       help="output file name (stdout if omitted)",
    136       type=argparse.FileType("wt"),
    137       default=sys.stdout,
    138       metavar="<output filename>",
    139       dest="output_stream"
    140   )
    141   command_line_parser.add_argument(
    142       "bytecode_name",
    143       metavar="<bytecode name>",
    144       nargs="?",
    145       help="The bytecode handler to annotate"
    146   )
    147 
    148   return command_line_parser.parse_args()
    149 
    150 
    151 def main():
    152   program_options = parse_command_line()
    153   perf = subprocess.Popen(["perf", "script", "-f", "ip,sym,symoff",
    154                            "-i", program_options.perf_filename],
    155                           stdout=subprocess.PIPE)
    156 
    157   v8_root_path = os.path.dirname(__file__) + "/../../"
    158   d8_path = "{}/out/{}.debug/d8".format(v8_root_path, program_options.arch)
    159   d8_codegen = subprocess.Popen([d8_path, "--ignition",
    160                                  "--trace-ignition-codegen", "-e", "1"],
    161                                 stdout=subprocess.PIPE)
    162 
    163   bytecode_offsets = bytecode_offset_generator(
    164       perf.stdout, program_options.bytecode_name)
    165   offset_counts = bytecode_offset_counts(bytecode_offsets)
    166 
    167   bytecode_disassembly = bytecode_disassembly_generator(
    168       d8_codegen.stdout, program_options.bytecode_name)
    169 
    170   print_disassembly_annotation(offset_counts, bytecode_disassembly)
    171 
    172 
    173 if __name__ == "__main__":
    174   main()
    175