• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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