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 15 16 17__DESCRIPTION = """ 18Process v8.ignition_dispatches_counters.json and list top counters, 19or plot a dispatch heatmap. 20 21Please 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 = """ 27examples: 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 52def 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 60def 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 70def 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 78def 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 86def 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 93def 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 103def 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 119def 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 136def 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 171def 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 228def 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 257if __name__ == "__main__": 258 main() 259