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