bytecode_dispatches_report.py 9.03 KB
Newer Older
1 2 3 4 5 6 7
#! /usr/bin/python
#
# Copyright 2016 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#

8 9 10
# for py2/py3 compatibility
from __future__ import print_function

11 12 13 14 15 16 17
import argparse
import heapq
import json
from matplotlib import colors
from matplotlib import pyplot
import numpy
import struct
18
import sys
19 20 21 22 23


__DESCRIPTION = """
Process v8.ignition_dispatches_counters.json and list top counters,
or plot a dispatch heatmap.
24 25 26

Please note that those handlers that may not or will never dispatch
(e.g. Return or Throw) do not show up in the results.
27 28 29 30 31
"""


__HELP_EPILOGUE = """
examples:
32 33
  # Print the hottest bytecodes in descending order, reading from
  # default filename v8.ignition_dispatches_counters.json (default mode)
34 35
  $ tools/ignition/bytecode_dispatches_report.py

36 37
  # Print the hottest 15 bytecode dispatch pairs reading from data.json
  $ tools/ignition/bytecode_dispatches_report.py -t -n 15 data.json
38 39 40 41 42 43 44 45 46

  # Save heatmap to default filename v8.ignition_dispatches_counters.svg
  $ tools/ignition/bytecode_dispatches_report.py -p

  # Save heatmap to filename data.svg
  $ tools/ignition/bytecode_dispatches_report.py -p -o data.svg

  # Open the heatmap in an interactive viewer
  $ tools/ignition/bytecode_dispatches_report.py -p -i
47 48 49

  # Display the top 5 sources and destinations of dispatches to/from LdaZero
  $ tools/ignition/bytecode_dispatches_report.py -f LdaZero -n 5
50 51 52 53 54 55 56
"""

__COUNTER_BITS = struct.calcsize("P") * 8  # Size in bits of a pointer
__COUNTER_MAX = 2**__COUNTER_BITS - 1


def warn_if_counter_may_have_saturated(dispatches_table):
57 58
  for source, counters_from_source in iteritems(dispatches_table):
    for destination, counter in iteritems(counters_from_source):
59
      if counter == __COUNTER_MAX:
60 61
        print("WARNING: {} -> {} may have saturated.".format(source,
                                                             destination))
62 63


64
def find_top_bytecode_dispatch_pairs(dispatches_table, top_count):
65
  def flattened_counters_generator():
66 67
    for source, counters_from_source in iteritems(dispatches_table):
      for destination, counter in iteritems(counters_from_source):
68 69 70 71 72 73
        yield source, destination, counter

  return heapq.nlargest(top_count, flattened_counters_generator(),
                        key=lambda x: x[2])


74 75 76
def print_top_bytecode_dispatch_pairs(dispatches_table, top_count):
  top_bytecode_dispatch_pairs = (
    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


82 83
def find_top_bytecodes(dispatches_table):
  top_bytecodes = []
84 85 86
  for bytecode, counters_from_bytecode in iteritems(dispatches_table):
    top_bytecodes.append((bytecode, sum(itervalues(counters_from_bytecode))))

87 88 89 90 91 92
  top_bytecodes.sort(key=lambda x: x[1], reverse=True)
  return top_bytecodes


def print_top_bytecodes(dispatches_table):
  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


98 99 100 101 102 103 104 105
def find_top_dispatch_sources_and_destinations(
    dispatches_table, bytecode, top_count, sort_source_relative):
  sources = []
  for source, destinations in iteritems(dispatches_table):
    total = float(sum(itervalues(destinations)))
    if bytecode in destinations:
      count = destinations[bytecode]
      sources.append((source, count, count / total))
106

107 108 109 110 111
  destinations = []
  bytecode_destinations = dispatches_table[bytecode]
  bytecode_total = float(sum(itervalues(bytecode_destinations)))
  for destination, count in iteritems(bytecode_destinations):
    destinations.append((destination, count, count / bytecode_total))
112

113 114 115
  return (heapq.nlargest(top_count, sources,
                         key=lambda x: x[2 if sort_source_relative else 1]),
          heapq.nlargest(top_count, destinations, key=lambda x: x[1]))
116 117


118 119 120 121
def print_top_dispatch_sources_and_destinations(dispatches_table, bytecode,
                                                top_count, sort_relative):
  top_sources, top_destinations = find_top_dispatch_sources_and_destinations(
      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


131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
def build_counters_matrix(dispatches_table):
  labels = sorted(dispatches_table.keys())

  counters_matrix = numpy.empty([len(labels), len(labels)], dtype=int)
  for from_index, from_name in enumerate(labels):
    current_row = dispatches_table[from_name];
    for to_index, to_name in enumerate(labels):
      counters_matrix[from_index, to_index] = current_row.get(to_name, 0)

  # Reverse y axis for a nicer appearance
  xlabels = labels
  ylabels = list(reversed(xlabels))
  counters_matrix = numpy.flipud(counters_matrix)

  return counters_matrix, xlabels, ylabels


def plot_dispatches_table(dispatches_table, figure, axis):
  counters_matrix, xlabels, ylabels = build_counters_matrix(dispatches_table)

  image = axis.pcolor(
    counters_matrix,
153
    cmap="jet",
154
    norm=colors.LogNorm(),
155 156
    edgecolor="grey",
    linestyle="dotted",
157 158 159 160 161 162 163 164 165
    linewidth=0.5
  )

  axis.xaxis.set(
    ticks=numpy.arange(0.5, len(xlabels)),
    label="From bytecode handler"
  )
  axis.xaxis.tick_top()
  axis.set_xlim(0, len(xlabels))
166
  axis.set_xticklabels(xlabels, rotation="vertical")
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

  axis.yaxis.set(
    ticks=numpy.arange(0.5, len(ylabels)),
    label="To bytecode handler",
    ticklabels=ylabels
  )
  axis.set_ylim(0, len(ylabels))

  figure.colorbar(
    image,
    ax=axis,
    fraction=0.01,
    pad=0.01
  )


def parse_command_line():
  command_line_parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description=__DESCRIPTION,
    epilog=__HELP_EPILOGUE
  )
  command_line_parser.add_argument(
190
    "--plot-size", "-s",
191 192
    metavar="N",
    default=30,
193
    help="shorter side in inches of the output plot (default 30)"
194 195 196 197
  )
  command_line_parser.add_argument(
    "--plot", "-p",
    action="store_true",
198
    help="plot dispatch pairs heatmap"
199 200 201 202
  )
  command_line_parser.add_argument(
    "--interactive", "-i",
    action="store_true",
203 204 205 206 207 208
    help="open the heatmap in an interactive viewer, instead of writing to file"
  )
  command_line_parser.add_argument(
    "--top-bytecode-dispatch-pairs", "-t",
    action="store_true",
    help="print the top bytecode dispatch pairs"
209 210
  )
  command_line_parser.add_argument(
211
    "--top-entries-count", "-n",
212 213 214
    metavar="N",
    type=int,
    default=10,
215 216 217 218 219 220
    help="print N top entries when running with -t or -f (default 10)"
  )
  command_line_parser.add_argument(
    "--top-dispatches-for-bytecode", "-f",
    metavar="<bytecode name>",
    help="print top dispatch sources and destinations to the specified bytecode"
221 222
  )
  command_line_parser.add_argument(
223
    "--output-filename", "-o",
224 225 226 227 228
    metavar="<output filename>",
    default="v8.ignition_dispatches_table.svg",
    help=("file to save the plot file to. File type is deduced from the "
          "extension. PDF, SVG, PNG supported")
  )
229 230 231 232 233 234
  command_line_parser.add_argument(
    "--sort-sources-relative", "-r",
    action="store_true",
    help=("print top sources in order to how often they dispatch to the "
          "specified bytecode, only applied when using -f")
  )
235 236 237 238 239 240 241 242 243 244 245
  command_line_parser.add_argument(
    "input_filename",
    metavar="<input filename>",
    default="v8.ignition_dispatches_table.json",
    nargs='?',
    help="Ignition counters JSON file"
  )

  return command_line_parser.parse_args()


246 247 248 249 250 251 252 253
def itervalues(d):
  return d.values() if sys.version_info[0] > 2 else d.itervalues()


def iteritems(d):
  return d.items() if sys.version_info[0] > 2 else d.iteritems()


254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
def main():
  program_options = parse_command_line()

  with open(program_options.input_filename) as stream:
    dispatches_table = json.load(stream)

  warn_if_counter_may_have_saturated(dispatches_table)

  if program_options.plot:
    figure, axis = pyplot.subplots()
    plot_dispatches_table(dispatches_table, figure, axis)

    if program_options.interactive:
      pyplot.show()
    else:
      figure.set_size_inches(program_options.plot_size,
                             program_options.plot_size)
      pyplot.savefig(program_options.output_filename)
272 273
  elif program_options.top_bytecode_dispatch_pairs:
    print_top_bytecode_dispatch_pairs(
274 275 276 277
      dispatches_table, program_options.top_entries_count)
  elif program_options.top_dispatches_for_bytecode:
    print_top_dispatch_sources_and_destinations(
      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 283 284


if __name__ == "__main__":
  main()