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
8import argparse
9import heapq
10import json
11from matplotlib import colors
12from matplotlib import pyplot
13import numpy
14import struct
15
16
17__DESCRIPTION = """
18Process v8.ignition_dispatches_counters.json and list top counters,
19or plot a dispatch heatmap.
20
21Please note that those handlers that may not or will never dispatch
22(e.g. Return or Throw) do not show up in the results.
23"""
24
25
26__HELP_EPILOGUE = """
27examples:
28  # Print the hottest bytecodes in descending order, reading from
29  # default filename v8.ignition_dispatches_counters.json (default mode)
30  $ tools/ignition/bytecode_dispatches_report.py
31
32  # Print the hottest 15 bytecode dispatch pairs reading from data.json
33  $ tools/ignition/bytecode_dispatches_report.py -t -n 15 data.json
34
35  # Save heatmap to default filename v8.ignition_dispatches_counters.svg
36  $ tools/ignition/bytecode_dispatches_report.py -p
37
38  # Save heatmap to filename data.svg
39  $ tools/ignition/bytecode_dispatches_report.py -p -o data.svg
40
41  # Open the heatmap in an interactive viewer
42  $ tools/ignition/bytecode_dispatches_report.py -p -i
43
44  # Display the top 5 sources and destinations of dispatches to/from LdaZero
45  $ tools/ignition/bytecode_dispatches_report.py -f LdaZero -n 5
46"""
47
48__COUNTER_BITS = struct.calcsize("P") * 8  # Size in bits of a pointer
49__COUNTER_MAX = 2**__COUNTER_BITS - 1
50
51
52def warn_if_counter_may_have_saturated(dispatches_table):
53  for source, counters_from_source in dispatches_table.items():
54    for destination, counter in counters_from_source.items():
55      if counter == __COUNTER_MAX:
56        print "WARNING: {} -> {} may have saturated.".format(source,
57                                                             destination)
58
59
60def find_top_bytecode_dispatch_pairs(dispatches_table, top_count):
61  def flattened_counters_generator():
62    for source, counters_from_source in dispatches_table.items():
63      for destination, counter in counters_from_source.items():
64        yield source, destination, counter
65
66  return heapq.nlargest(top_count, flattened_counters_generator(),
67                        key=lambda x: x[2])
68
69
70def print_top_bytecode_dispatch_pairs(dispatches_table, top_count):
71  top_bytecode_dispatch_pairs = (
72    find_top_bytecode_dispatch_pairs(dispatches_table, top_count))
73  print "Top {} bytecode dispatch pairs:".format(top_count)
74  for source, destination, counter in top_bytecode_dispatch_pairs:
75    print "{:>12d}\t{} -> {}".format(counter, source, destination)
76
77
78def find_top_bytecodes(dispatches_table):
79  top_bytecodes = []
80  for bytecode, counters_from_bytecode in dispatches_table.items():
81    top_bytecodes.append((bytecode, sum(counters_from_bytecode.values())))
82  top_bytecodes.sort(key=lambda x: x[1], reverse=True)
83  return top_bytecodes
84
85
86def print_top_bytecodes(dispatches_table):
87  top_bytecodes = find_top_bytecodes(dispatches_table)
88  print "Top bytecodes:"
89  for bytecode, counter in top_bytecodes:
90    print "{:>12d}\t{}".format(counter, bytecode)
91
92
93def find_top_dispatch_sources(dispatches_table, destination, top_count):
94  def source_counters_generator():
95    for source, table_row in dispatches_table.items():
96      if destination in table_row:
97        yield source, table_row[destination]
98
99  return heapq.nlargest(top_count, source_counters_generator(),
100                        key=lambda x: x[1])
101
102
103def print_top_dispatch_sources_and_destinations(dispatches_table, bytecode,
104                                                top_count):
105  top_sources = find_top_dispatch_sources(dispatches_table, bytecode, top_count)
106  top_destinations = heapq.nlargest(top_count,
107                                    dispatches_table[bytecode].items(),
108                                    key=lambda x: x[1])
109
110  print "Top sources of dispatches to {}:".format(bytecode)
111  for source_name, counter in top_sources:
112    print "{:>12d}\t{}".format(counter, source_name)
113
114  print "\nTop destinations of dispatches from {}:".format(bytecode)
115  for destination_name, counter in top_destinations:
116    print "{:>12d}\t{}".format(counter, destination_name)
117
118
119def build_counters_matrix(dispatches_table):
120  labels = sorted(dispatches_table.keys())
121
122  counters_matrix = numpy.empty([len(labels), len(labels)], dtype=int)
123  for from_index, from_name in enumerate(labels):
124    current_row = dispatches_table[from_name];
125    for to_index, to_name in enumerate(labels):
126      counters_matrix[from_index, to_index] = current_row.get(to_name, 0)
127
128  # Reverse y axis for a nicer appearance
129  xlabels = labels
130  ylabels = list(reversed(xlabels))
131  counters_matrix = numpy.flipud(counters_matrix)
132
133  return counters_matrix, xlabels, ylabels
134
135
136def plot_dispatches_table(dispatches_table, figure, axis):
137  counters_matrix, xlabels, ylabels = build_counters_matrix(dispatches_table)
138
139  image = axis.pcolor(
140    counters_matrix,
141    cmap="jet",
142    norm=colors.LogNorm(),
143    edgecolor="grey",
144    linestyle="dotted",
145    linewidth=0.5
146  )
147
148  axis.xaxis.set(
149    ticks=numpy.arange(0.5, len(xlabels)),
150    label="From bytecode handler"
151  )
152  axis.xaxis.tick_top()
153  axis.set_xlim(0, len(xlabels))
154  axis.set_xticklabels(xlabels, rotation="vertical")
155
156  axis.yaxis.set(
157    ticks=numpy.arange(0.5, len(ylabels)),
158    label="To bytecode handler",
159    ticklabels=ylabels
160  )
161  axis.set_ylim(0, len(ylabels))
162
163  figure.colorbar(
164    image,
165    ax=axis,
166    fraction=0.01,
167    pad=0.01
168  )
169
170
171def parse_command_line():
172  command_line_parser = argparse.ArgumentParser(
173    formatter_class=argparse.RawDescriptionHelpFormatter,
174    description=__DESCRIPTION,
175    epilog=__HELP_EPILOGUE
176  )
177  command_line_parser.add_argument(
178    "--plot-size", "-s",
179    metavar="N",
180    default=30,
181    help="shorter side in inches of the output plot (default 30)"
182  )
183  command_line_parser.add_argument(
184    "--plot", "-p",
185    action="store_true",
186    help="plot dispatch pairs heatmap"
187  )
188  command_line_parser.add_argument(
189    "--interactive", "-i",
190    action="store_true",
191    help="open the heatmap in an interactive viewer, instead of writing to file"
192  )
193  command_line_parser.add_argument(
194    "--top-bytecode-dispatch-pairs", "-t",
195    action="store_true",
196    help="print the top bytecode dispatch pairs"
197  )
198  command_line_parser.add_argument(
199    "--top-entries-count", "-n",
200    metavar="N",
201    type=int,
202    default=10,
203    help="print N top entries when running with -t or -f (default 10)"
204  )
205  command_line_parser.add_argument(
206    "--top-dispatches-for-bytecode", "-f",
207    metavar="<bytecode name>",
208    help="print top dispatch sources and destinations to the specified bytecode"
209  )
210  command_line_parser.add_argument(
211    "--output-filename", "-o",
212    metavar="<output filename>",
213    default="v8.ignition_dispatches_table.svg",
214    help=("file to save the plot file to. File type is deduced from the "
215          "extension. PDF, SVG, PNG supported")
216  )
217  command_line_parser.add_argument(
218    "input_filename",
219    metavar="<input filename>",
220    default="v8.ignition_dispatches_table.json",
221    nargs='?',
222    help="Ignition counters JSON file"
223  )
224
225  return command_line_parser.parse_args()
226
227
228def main():
229  program_options = parse_command_line()
230
231  with open(program_options.input_filename) as stream:
232    dispatches_table = json.load(stream)
233
234  warn_if_counter_may_have_saturated(dispatches_table)
235
236  if program_options.plot:
237    figure, axis = pyplot.subplots()
238    plot_dispatches_table(dispatches_table, figure, axis)
239
240    if program_options.interactive:
241      pyplot.show()
242    else:
243      figure.set_size_inches(program_options.plot_size,
244                             program_options.plot_size)
245      pyplot.savefig(program_options.output_filename)
246  elif program_options.top_bytecode_dispatch_pairs:
247    print_top_bytecode_dispatch_pairs(
248      dispatches_table, program_options.top_entries_count)
249  elif program_options.top_dispatches_for_bytecode:
250    print_top_dispatch_sources_and_destinations(
251      dispatches_table, program_options.top_dispatches_for_bytecode,
252      program_options.top_entries_count)
253  else:
254    print_top_bytecodes(dispatches_table)
255
256
257if __name__ == "__main__":
258  main()
259