blob: 6681190d9909a461b14de2bd2a6ee73c58caa49a [file] [log] [blame]
Ben Murdochc5610432016-08-08 18:44:38 +01001#! /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()