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 import sys 16 17 18 __DESCRIPTION = """ 19 Process v8.ignition_dispatches_counters.json and list top counters, 20 or plot a dispatch heatmap. 21 22 Please 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 = """ 28 examples: 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 53 def 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 61 def 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 71 def 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 79 def 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 88 def 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 95 def 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 115 def 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 128 def 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 145 def 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 180 def 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 243 def itervalues(d): 244 return d.values() if sys.version_info[0] > 2 else d.itervalues() 245 246 247 def iteritems(d): 248 return d.items() if sys.version_info[0] > 2 else d.iteritems() 249 250 251 def 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 280 if __name__ == "__main__": 281 main() 282