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 8import argparse 9import heapq 10import json 11from matplotlib import colors 12from matplotlib import pyplot 13import numpy 14import struct 15import sys 16 17 18__DESCRIPTION = """ 19Process v8.ignition_dispatches_counters.json and list top counters, 20or plot a dispatch heatmap. 21 22Please note that those handlers that may not or will never dispatch 23(e.g. Return or Throw) do not show up in the results. 24""" 25 26 27__HELP_EPILOGUE = """ 28examples: 29 # Print the hottest bytecodes in descending order, reading from 30 # default filename v8.ignition_dispatches_counters.json (default mode) 31 $ tools/ignition/bytecode_dispatches_report.py 32 33 # Print the hottest 15 bytecode dispatch pairs reading from data.json 34 $ tools/ignition/bytecode_dispatches_report.py -t -n 15 data.json 35 36 # Save heatmap to default filename v8.ignition_dispatches_counters.svg 37 $ tools/ignition/bytecode_dispatches_report.py -p 38 39 # Save heatmap to filename data.svg 40 $ tools/ignition/bytecode_dispatches_report.py -p -o data.svg 41 42 # Open the heatmap in an interactive viewer 43 $ tools/ignition/bytecode_dispatches_report.py -p -i 44 45 # Display the top 5 sources and destinations of dispatches to/from LdaZero 46 $ tools/ignition/bytecode_dispatches_report.py -f LdaZero -n 5 47""" 48 49__COUNTER_BITS = struct.calcsize("P") * 8 # Size in bits of a pointer 50__COUNTER_MAX = 2**__COUNTER_BITS - 1 51 52 53def warn_if_counter_may_have_saturated(dispatches_table): 54 for source, counters_from_source in iteritems(dispatches_table): 55 for destination, counter in iteritems(counters_from_source): 56 if counter == __COUNTER_MAX: 57 print "WARNING: {} -> {} may have saturated.".format(source, 58 destination) 59 60 61def find_top_bytecode_dispatch_pairs(dispatches_table, top_count): 62 def flattened_counters_generator(): 63 for source, counters_from_source in iteritems(dispatches_table): 64 for destination, counter in iteritems(counters_from_source): 65 yield source, destination, counter 66 67 return heapq.nlargest(top_count, flattened_counters_generator(), 68 key=lambda x: x[2]) 69 70 71def print_top_bytecode_dispatch_pairs(dispatches_table, top_count): 72 top_bytecode_dispatch_pairs = ( 73 find_top_bytecode_dispatch_pairs(dispatches_table, top_count)) 74 print "Top {} bytecode dispatch pairs:".format(top_count) 75 for source, destination, counter in top_bytecode_dispatch_pairs: 76 print "{:>12d}\t{} -> {}".format(counter, source, destination) 77 78 79def find_top_bytecodes(dispatches_table): 80 top_bytecodes = [] 81 for bytecode, counters_from_bytecode in iteritems(dispatches_table): 82 top_bytecodes.append((bytecode, sum(itervalues(counters_from_bytecode)))) 83 84 top_bytecodes.sort(key=lambda x: x[1], reverse=True) 85 return top_bytecodes 86 87 88def print_top_bytecodes(dispatches_table): 89 top_bytecodes = find_top_bytecodes(dispatches_table) 90 print "Top bytecodes:" 91 for bytecode, counter in top_bytecodes: 92 print "{:>12d}\t{}".format(counter, bytecode) 93 94 95def find_top_dispatch_sources_and_destinations( 96 dispatches_table, bytecode, top_count, sort_source_relative): 97 sources = [] 98 for source, destinations in iteritems(dispatches_table): 99 total = float(sum(itervalues(destinations))) 100 if bytecode in destinations: 101 count = destinations[bytecode] 102 sources.append((source, count, count / total)) 103 104 destinations = [] 105 bytecode_destinations = dispatches_table[bytecode] 106 bytecode_total = float(sum(itervalues(bytecode_destinations))) 107 for destination, count in iteritems(bytecode_destinations): 108 destinations.append((destination, count, count / bytecode_total)) 109 110 return (heapq.nlargest(top_count, sources, 111 key=lambda x: x[2 if sort_source_relative else 1]), 112 heapq.nlargest(top_count, destinations, key=lambda x: x[1])) 113 114 115def print_top_dispatch_sources_and_destinations(dispatches_table, bytecode, 116 top_count, sort_relative): 117 top_sources, top_destinations = find_top_dispatch_sources_and_destinations( 118 dispatches_table, bytecode, top_count, sort_relative) 119 print "Top sources of dispatches to {}:".format(bytecode) 120 for source_name, counter, ratio in top_sources: 121 print "{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, source_name) 122 123 print "\nTop destinations of dispatches from {}:".format(bytecode) 124 for destination_name, counter, ratio in top_destinations: 125 print "{:>12d}\t{:>5.1f}%\t{}".format(counter, ratio * 100, destination_name) 126 127 128def build_counters_matrix(dispatches_table): 129 labels = sorted(dispatches_table.keys()) 130 131 counters_matrix = numpy.empty([len(labels), len(labels)], dtype=int) 132 for from_index, from_name in enumerate(labels): 133 current_row = dispatches_table[from_name]; 134 for to_index, to_name in enumerate(labels): 135 counters_matrix[from_index, to_index] = current_row.get(to_name, 0) 136 137 # Reverse y axis for a nicer appearance 138 xlabels = labels 139 ylabels = list(reversed(xlabels)) 140 counters_matrix = numpy.flipud(counters_matrix) 141 142 return counters_matrix, xlabels, ylabels 143 144 145def plot_dispatches_table(dispatches_table, figure, axis): 146 counters_matrix, xlabels, ylabels = build_counters_matrix(dispatches_table) 147 148 image = axis.pcolor( 149 counters_matrix, 150 cmap="jet", 151 norm=colors.LogNorm(), 152 edgecolor="grey", 153 linestyle="dotted", 154 linewidth=0.5 155 ) 156 157 axis.xaxis.set( 158 ticks=numpy.arange(0.5, len(xlabels)), 159 label="From bytecode handler" 160 ) 161 axis.xaxis.tick_top() 162 axis.set_xlim(0, len(xlabels)) 163 axis.set_xticklabels(xlabels, rotation="vertical") 164 165 axis.yaxis.set( 166 ticks=numpy.arange(0.5, len(ylabels)), 167 label="To bytecode handler", 168 ticklabels=ylabels 169 ) 170 axis.set_ylim(0, len(ylabels)) 171 172 figure.colorbar( 173 image, 174 ax=axis, 175 fraction=0.01, 176 pad=0.01 177 ) 178 179 180def parse_command_line(): 181 command_line_parser = argparse.ArgumentParser( 182 formatter_class=argparse.RawDescriptionHelpFormatter, 183 description=__DESCRIPTION, 184 epilog=__HELP_EPILOGUE 185 ) 186 command_line_parser.add_argument( 187 "--plot-size", "-s", 188 metavar="N", 189 default=30, 190 help="shorter side in inches of the output plot (default 30)" 191 ) 192 command_line_parser.add_argument( 193 "--plot", "-p", 194 action="store_true", 195 help="plot dispatch pairs heatmap" 196 ) 197 command_line_parser.add_argument( 198 "--interactive", "-i", 199 action="store_true", 200 help="open the heatmap in an interactive viewer, instead of writing to file" 201 ) 202 command_line_parser.add_argument( 203 "--top-bytecode-dispatch-pairs", "-t", 204 action="store_true", 205 help="print the top bytecode dispatch pairs" 206 ) 207 command_line_parser.add_argument( 208 "--top-entries-count", "-n", 209 metavar="N", 210 type=int, 211 default=10, 212 help="print N top entries when running with -t or -f (default 10)" 213 ) 214 command_line_parser.add_argument( 215 "--top-dispatches-for-bytecode", "-f", 216 metavar="<bytecode name>", 217 help="print top dispatch sources and destinations to the specified bytecode" 218 ) 219 command_line_parser.add_argument( 220 "--output-filename", "-o", 221 metavar="<output filename>", 222 default="v8.ignition_dispatches_table.svg", 223 help=("file to save the plot file to. File type is deduced from the " 224 "extension. PDF, SVG, PNG supported") 225 ) 226 command_line_parser.add_argument( 227 "--sort-sources-relative", "-r", 228 action="store_true", 229 help=("print top sources in order to how often they dispatch to the " 230 "specified bytecode, only applied when using -f") 231 ) 232 command_line_parser.add_argument( 233 "input_filename", 234 metavar="<input filename>", 235 default="v8.ignition_dispatches_table.json", 236 nargs='?', 237 help="Ignition counters JSON file" 238 ) 239 240 return command_line_parser.parse_args() 241 242 243def itervalues(d): 244 return d.values() if sys.version_info[0] > 2 else d.itervalues() 245 246 247def iteritems(d): 248 return d.items() if sys.version_info[0] > 2 else d.iteritems() 249 250 251def main(): 252 program_options = parse_command_line() 253 254 with open(program_options.input_filename) as stream: 255 dispatches_table = json.load(stream) 256 257 warn_if_counter_may_have_saturated(dispatches_table) 258 259 if program_options.plot: 260 figure, axis = pyplot.subplots() 261 plot_dispatches_table(dispatches_table, figure, axis) 262 263 if program_options.interactive: 264 pyplot.show() 265 else: 266 figure.set_size_inches(program_options.plot_size, 267 program_options.plot_size) 268 pyplot.savefig(program_options.output_filename) 269 elif program_options.top_bytecode_dispatch_pairs: 270 print_top_bytecode_dispatch_pairs( 271 dispatches_table, program_options.top_entries_count) 272 elif program_options.top_dispatches_for_bytecode: 273 print_top_dispatch_sources_and_destinations( 274 dispatches_table, program_options.top_dispatches_for_bytecode, 275 program_options.top_entries_count, program_options.sort_sources_relative) 276 else: 277 print_top_bytecodes(dispatches_table) 278 279 280if __name__ == "__main__": 281 main() 282