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 
     16 
     17 __DESCRIPTION = """
     18 Process v8.ignition_dispatches_counters.json and list top counters,
     19 or plot a dispatch heatmap.
     20 
     21 Please 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 = """
     27 examples:
     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 
     52 def 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 
     60 def 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 
     70 def 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 
     78 def 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 
     86 def 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 
     93 def 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 
    103 def 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 
    119 def 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 
    136 def 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 
    171 def 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 
    228 def 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 
    257 if __name__ == "__main__":
    258   main()
    259