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