| #! /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() |