| #! /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. | |
| # | |
| import argparse | |
| import heapq | |
| import json | |
| from matplotlib import colors | |
| from matplotlib import pyplot | |
| import numpy | |
| import struct | |
| import sys | |
| __DESCRIPTION = """ | |
| Process v8.ignition_dispatches_counters.json and list top counters, | |
| or plot a dispatch heatmap. | |
| Please note that those handlers that may not or will never dispatch | |
| (e.g. Return or Throw) do not show up in the results. | |
| """ | |
| __HELP_EPILOGUE = """ | |
| examples: | |
| # Print the hottest bytecodes in descending order, reading from | |
| # default filename v8.ignition_dispatches_counters.json (default mode) | |
| $ tools/ignition/bytecode_dispatches_report.py | |
| # Print the hottest 15 bytecode dispatch pairs reading from data.json | |
| $ tools/ignition/bytecode_dispatches_report.py -t -n 15 data.json | |
| # 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 | |
| # Display the top 5 sources and destinations of dispatches to/from LdaZero | |
| $ tools/ignition/bytecode_dispatches_report.py -f LdaZero -n 5 | |
| """ | |
| __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): | |
| for source, counters_from_source in iteritems(dispatches_table): | |
| for destination, counter in iteritems(counters_from_source): | |
| if counter == __COUNTER_MAX: | |
| print "WARNING: {} -> {} may have saturated.".format(source, | |
| destination) | |
| def find_top_bytecode_dispatch_pairs(dispatches_table, top_count): | |
| def flattened_counters_generator(): | |
| for source, counters_from_source in iteritems(dispatches_table): | |
| for destination, counter in iteritems(counters_from_source): | |
| yield source, destination, counter | |
| return heapq.nlargest(top_count, flattened_counters_generator(), | |
| key=lambda x: x[2]) | |
| def print_top_bytecode_dispatch_pairs(dispatches_table, top_count): | |
| top_bytecode_dispatch_pairs = ( | |
| find_top_bytecode_dispatch_pairs(dispatches_table, top_count)) | |
| print "Top {} bytecode dispatch pairs:".format(top_count) | |
| for source, destination, counter in top_bytecode_dispatch_pairs: | |
| print "{:>12d}\t{} -> {}".format(counter, source, destination) | |
| def find_top_bytecodes(dispatches_table): | |
| top_bytecodes = [] | |
| for bytecode, counters_from_bytecode in iteritems(dispatches_table): | |
| top_bytecodes.append((bytecode, sum(itervalues(counters_from_bytecode)))) | |
| 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) | |
| print "Top bytecodes:" | |
| for bytecode, counter in top_bytecodes: | |
| print "{:>12d}\t{}".format(counter, bytecode) | |
| 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)) | |
| 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)) | |
| 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])) | |
| 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) | |
| print "Top sources of dispatches to {}:".format(bytecode) | |
| for source_name, counter, ratio in top_sources: | |
| print "{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, source_name) | |
| print "\nTop destinations of dispatches from {}:".format(bytecode) | |
| for destination_name, counter, ratio in top_destinations: | |
| print "{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, destination_name) | |
| 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, | |
| cmap="jet", | |
| norm=colors.LogNorm(), | |
| edgecolor="grey", | |
| linestyle="dotted", | |
| 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)) | |
| axis.set_xticklabels(xlabels, rotation="vertical") | |
| 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( | |
| "--plot-size", "-s", | |
| metavar="N", | |
| default=30, | |
| help="shorter side in inches of the output plot (default 30)" | |
| ) | |
| command_line_parser.add_argument( | |
| "--plot", "-p", | |
| action="store_true", | |
| help="plot dispatch pairs heatmap" | |
| ) | |
| command_line_parser.add_argument( | |
| "--interactive", "-i", | |
| action="store_true", | |
| 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" | |
| ) | |
| command_line_parser.add_argument( | |
| "--top-entries-count", "-n", | |
| metavar="N", | |
| type=int, | |
| default=10, | |
| 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" | |
| ) | |
| command_line_parser.add_argument( | |
| "--output-filename", "-o", | |
| 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") | |
| ) | |
| 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") | |
| ) | |
| 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() | |
| 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() | |
| 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) | |
| elif program_options.top_bytecode_dispatch_pairs: | |
| print_top_bytecode_dispatch_pairs( | |
| 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, | |
| program_options.top_entries_count, program_options.sort_sources_relative) | |
| else: | |
| print_top_bytecodes(dispatches_table) | |
| if __name__ == "__main__": | |
| main() |