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