Ben Murdoch | c561043 | 2016-08-08 18:44:38 +0100 | [diff] [blame] | 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() |