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 8import argparse 9import collections 10import os 11import subprocess 12import sys 13 14 15__DESCRIPTION = """ 16Processes a perf.data sample file and annotates the hottest instructions in a 17given bytecode handler. 18""" 19 20 21__HELP_EPILOGUE = """ 22Note: 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 30Examples: 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 36def 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 63def 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 70def 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 90def 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 115def 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 151def 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 173if __name__ == "__main__": 174 main() 175