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