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() 259