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