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