1#!/usr/bin/env python3 2 3import argparse 4import sys 5import os 6from json import loads 7from subprocess import Popen, PIPE 8 9# Holds code regions statistics. 10class Summary: 11 def __init__( 12 self, 13 name, 14 block_rthroughput, 15 dispatch_width, 16 ipc, 17 instructions, 18 iterations, 19 total_cycles, 20 total_uops, 21 uops_per_cycle, 22 iteration_resource_pressure, 23 name_target_info_resources, 24 ): 25 self.name = name 26 self.block_rthroughput = block_rthroughput 27 self.dispatch_width = dispatch_width 28 self.ipc = ipc 29 self.instructions = instructions 30 self.iterations = iterations 31 self.total_cycles = total_cycles 32 self.total_uops = total_uops 33 self.uops_per_cycle = uops_per_cycle 34 self.iteration_resource_pressure = iteration_resource_pressure 35 self.name_target_info_resources = name_target_info_resources 36 37 38# Parse the program arguments. 39def parse_program_args(parser): 40 parser.add_argument( 41 "file_names", 42 nargs="+", 43 type=str, 44 help="Names of files which llvm-mca tool process.", 45 ) 46 parser.add_argument( 47 "--llvm-mca-binary", 48 nargs=1, 49 required=True, 50 type=str, 51 action="store", 52 metavar="[=<path to llvm-mca>]", 53 help="Specified relative path to binary of llvm-mca.", 54 ) 55 parser.add_argument( 56 "--args", 57 nargs=1, 58 type=str, 59 action="store", 60 metavar="[='-option1=<arg> -option2=<arg> ...']", 61 default=["-"], 62 help="Forward options to lvm-mca tool.", 63 ) 64 parser.add_argument( 65 "-plot", 66 action="store_true", 67 default=False, 68 help="Draw plots of statistics for input files.", 69 ) 70 parser.add_argument( 71 "-plot-resource-pressure", 72 action="store_true", 73 default=False, 74 help="Draw plots of resource pressure per iterations for input files.", 75 ) 76 parser.add_argument( 77 "--plot-path", 78 nargs=1, 79 type=str, 80 action="store", 81 metavar="[=<path>]", 82 default=["-"], 83 help="Specify relative path where you want to save the plots.", 84 ) 85 parser.add_argument( 86 "-v", 87 action="store_true", 88 default=False, 89 help="More details about the running lvm-mca tool.", 90 ) 91 return parser.parse_args() 92 93 94# Verify that the program inputs meet the requirements. 95def verify_program_inputs(opts): 96 if opts.plot_path[0] != "-" and not opts.plot and not opts.plot_resource_pressure: 97 print( 98 "error: Please specify --plot-path only with the -plot or -plot-resource-pressure options." 99 ) 100 return False 101 102 return True 103 104 105# Returns the name of the file to be analyzed from the path it is on. 106def get_filename_from_path(path): 107 index_of_slash = path.rfind("/") 108 return path[(index_of_slash + 1) : len(path)] 109 110 111# Returns the results of the running llvm-mca tool for the input file. 112def run_llvm_mca_tool(opts, file_name): 113 # Get the path of the llvm-mca binary file. 114 llvm_mca_cmd = opts.llvm_mca_binary[0] 115 116 # The statistics llvm-mca options. 117 if opts.args[0] != "-": 118 llvm_mca_cmd += " " + opts.args[0] 119 llvm_mca_cmd += " -json" 120 121 # Set file which llvm-mca tool will process. 122 llvm_mca_cmd += " " + file_name 123 124 if opts.v: 125 print("run: $ " + llvm_mca_cmd + "\n") 126 127 # Generate the stats with the llvm-mca. 128 subproc = Popen( 129 llvm_mca_cmd.split(" "), 130 stdin=PIPE, 131 stdout=PIPE, 132 stderr=PIPE, 133 universal_newlines=True, 134 ) 135 136 cmd_stdout, cmd_stderr = subproc.communicate() 137 138 try: 139 json_parsed = loads(cmd_stdout) 140 except: 141 print("error: No valid llvm-mca statistics found.") 142 print(cmd_stderr) 143 sys.exit(1) 144 145 if opts.v: 146 print("Simulation Parameters: ") 147 simulation_parameters = json_parsed["SimulationParameters"] 148 for key in simulation_parameters: 149 print(key, ":", simulation_parameters[key]) 150 print("\n") 151 152 code_regions_len = len(json_parsed["CodeRegions"]) 153 array_of_code_regions = [None] * code_regions_len 154 155 for i in range(code_regions_len): 156 code_region_instructions_len = len( 157 json_parsed["CodeRegions"][i]["Instructions"] 158 ) 159 target_info_resources_len = len(json_parsed["TargetInfo"]["Resources"]) 160 iteration_resource_pressure = ["-" for k in range(target_info_resources_len)] 161 resource_pressure_info = json_parsed["CodeRegions"][i]["ResourcePressureView"][ 162 "ResourcePressureInfo" 163 ] 164 165 name_target_info_resources = json_parsed["TargetInfo"]["Resources"] 166 167 for s in range(len(resource_pressure_info)): 168 obj_of_resource_pressure_info = resource_pressure_info[s] 169 if ( 170 obj_of_resource_pressure_info["InstructionIndex"] 171 == code_region_instructions_len 172 ): 173 iteration_resource_pressure[ 174 obj_of_resource_pressure_info["ResourceIndex"] 175 ] = str(round(obj_of_resource_pressure_info["ResourceUsage"], 2)) 176 177 array_of_code_regions[i] = Summary( 178 file_name, 179 json_parsed["CodeRegions"][i]["SummaryView"]["BlockRThroughput"], 180 json_parsed["CodeRegions"][i]["SummaryView"]["DispatchWidth"], 181 json_parsed["CodeRegions"][i]["SummaryView"]["IPC"], 182 json_parsed["CodeRegions"][i]["SummaryView"]["Instructions"], 183 json_parsed["CodeRegions"][i]["SummaryView"]["Iterations"], 184 json_parsed["CodeRegions"][i]["SummaryView"]["TotalCycles"], 185 json_parsed["CodeRegions"][i]["SummaryView"]["TotaluOps"], 186 json_parsed["CodeRegions"][i]["SummaryView"]["uOpsPerCycle"], 187 iteration_resource_pressure, 188 name_target_info_resources, 189 ) 190 191 return array_of_code_regions 192 193 194# Print statistics in console for single file or for multiple files. 195def console_print_results(matrix_of_code_regions, opts): 196 try: 197 import termtables as tt 198 except ImportError: 199 print("error: termtables not found.") 200 sys.exit(1) 201 202 headers_names = [None] * (len(opts.file_names) + 1) 203 headers_names[0] = " " 204 205 max_code_regions = 0 206 207 print("Input files:") 208 for i in range(len(matrix_of_code_regions)): 209 if max_code_regions < len(matrix_of_code_regions[i]): 210 max_code_regions = len(matrix_of_code_regions[i]) 211 print("[f" + str(i + 1) + "]: " + get_filename_from_path(opts.file_names[i])) 212 headers_names[i + 1] = "[f" + str(i + 1) + "]: " 213 214 print("\nITERATIONS: " + str(matrix_of_code_regions[0][0].iterations) + "\n") 215 216 for i in range(max_code_regions): 217 218 print( 219 "\n-----------------------------------------\nCode region: " 220 + str(i + 1) 221 + "\n" 222 ) 223 224 table_values = [ 225 [[None] for i in range(len(matrix_of_code_regions) + 1)] for j in range(7) 226 ] 227 228 table_values[0][0] = "Instructions: " 229 table_values[1][0] = "Total Cycles: " 230 table_values[2][0] = "Total uOps: " 231 table_values[3][0] = "Dispatch Width: " 232 table_values[4][0] = "uOps Per Cycle: " 233 table_values[5][0] = "IPC: " 234 table_values[6][0] = "Block RThroughput: " 235 236 for j in range(len(matrix_of_code_regions)): 237 if len(matrix_of_code_regions[j]) > i: 238 table_values[0][j + 1] = str(matrix_of_code_regions[j][i].instructions) 239 table_values[1][j + 1] = str(matrix_of_code_regions[j][i].total_cycles) 240 table_values[2][j + 1] = str(matrix_of_code_regions[j][i].total_uops) 241 table_values[3][j + 1] = str( 242 matrix_of_code_regions[j][i].dispatch_width 243 ) 244 table_values[4][j + 1] = str( 245 round(matrix_of_code_regions[j][i].uops_per_cycle, 2) 246 ) 247 table_values[5][j + 1] = str(round(matrix_of_code_regions[j][i].ipc, 2)) 248 table_values[6][j + 1] = str( 249 round(matrix_of_code_regions[j][i].block_rthroughput, 2) 250 ) 251 else: 252 table_values[0][j + 1] = "-" 253 table_values[1][j + 1] = "-" 254 table_values[2][j + 1] = "-" 255 table_values[3][j + 1] = "-" 256 table_values[4][j + 1] = "-" 257 table_values[5][j + 1] = "-" 258 table_values[6][j + 1] = "-" 259 260 tt.print( 261 table_values, 262 header=headers_names, 263 style=tt.styles.ascii_thin_double, 264 padding=(0, 1), 265 ) 266 267 print("\nResource pressure per iteration: \n") 268 269 table_values = [ 270 [ 271 [None] 272 for i in range( 273 len(matrix_of_code_regions[0][0].iteration_resource_pressure) + 1 274 ) 275 ] 276 for j in range(len(matrix_of_code_regions) + 1) 277 ] 278 279 table_values[0] = [" "] + matrix_of_code_regions[0][ 280 0 281 ].name_target_info_resources 282 283 for j in range(len(matrix_of_code_regions)): 284 if len(matrix_of_code_regions[j]) > i: 285 table_values[j + 1] = [ 286 "[f" + str(j + 1) + "]: " 287 ] + matrix_of_code_regions[j][i].iteration_resource_pressure 288 else: 289 table_values[j + 1] = ["[f" + str(j + 1) + "]: "] + len( 290 matrix_of_code_regions[0][0].iteration_resource_pressure 291 ) * ["-"] 292 293 tt.print( 294 table_values, 295 style=tt.styles.ascii_thin_double, 296 padding=(0, 1), 297 ) 298 print("\n") 299 300 301# Based on the obtained results (summary view) of llvm-mca tool, draws plots for multiple input files. 302def draw_plot_files_summary(array_of_summary, opts): 303 try: 304 import matplotlib.pyplot as plt 305 except ImportError: 306 print("error: matplotlib.pyplot not found.") 307 sys.exit(1) 308 try: 309 from matplotlib.cm import get_cmap 310 except ImportError: 311 print("error: get_cmap (matplotlib.cm) not found.") 312 sys.exit(1) 313 314 names = [ 315 "Block RThroughput", 316 "Dispatch Width", 317 "IPC", 318 "uOps Per Cycle", 319 "Instructions", 320 "Total Cycles", 321 "Total uOps", 322 ] 323 324 rows, cols = (len(opts.file_names), 7) 325 326 values = [[0 for x in range(cols)] for y in range(rows)] 327 328 for i in range(len(opts.file_names)): 329 values[i][0] = array_of_summary[i].block_rthroughput 330 values[i][1] = array_of_summary[i].dispatch_width 331 values[i][2] = array_of_summary[i].ipc 332 values[i][3] = array_of_summary[i].uops_per_cycle 333 values[i][4] = array_of_summary[i].instructions 334 values[i][5] = array_of_summary[i].total_cycles 335 values[i][6] = array_of_summary[i].total_uops 336 337 fig, axs = plt.subplots(4, 2) 338 fig.suptitle( 339 "Machine code statistics", fontsize=20, fontweight="bold", color="black" 340 ) 341 i = 0 342 343 for x in range(4): 344 for y in range(2): 345 cmap = get_cmap("tab20") 346 colors = cmap.colors 347 if not (x == 0 and y == 1) and i < 7: 348 axs[x][y].grid(True, color="grey", linestyle="--") 349 maxValue = 0 350 if i == 0: 351 for j in range(len(opts.file_names)): 352 if maxValue < values[j][i]: 353 maxValue = values[j][i] 354 axs[x][y].bar( 355 0.3 * j, 356 values[j][i], 357 width=0.1, 358 color=colors[j], 359 label=get_filename_from_path(opts.file_names[j]), 360 ) 361 else: 362 for j in range(len(opts.file_names)): 363 if maxValue < values[j][i]: 364 maxValue = values[j][i] 365 axs[x][y].bar(0.3 * j, values[j][i], width=0.1, color=colors[j]) 366 axs[x][y].set_axisbelow(True) 367 axs[x][y].set_xlim([-0.3, len(opts.file_names) / 3]) 368 axs[x][y].set_ylim([0, maxValue + (maxValue / 2)]) 369 axs[x][y].set_title(names[i], fontsize=15, fontweight="bold") 370 axs[x][y].axes.xaxis.set_visible(False) 371 for j in range(len(opts.file_names)): 372 axs[x][y].text( 373 0.3 * j, 374 values[j][i] + (maxValue / 40), 375 s=str(values[j][i]), 376 color="black", 377 fontweight="bold", 378 fontsize=4, 379 ) 380 i = i + 1 381 382 axs[0][1].set_visible(False) 383 fig.legend(prop={"size": 15}) 384 figg = plt.gcf() 385 figg.set_size_inches((25, 15), forward=False) 386 if opts.plot_path[0] == "-": 387 plt.savefig("llvm-mca-plot.png", dpi=500) 388 print("The plot was saved within llvm-mca-plot.png") 389 else: 390 plt.savefig( 391 os.path.normpath(os.path.join(opts.plot_path[0], "llvm-mca-plot.png")), 392 dpi=500, 393 ) 394 print( 395 "The plot was saved within {}.".format( 396 os.path.normpath(os.path.join(opts.plot_path[0], "llvm-mca-plot.png")) 397 ) 398 ) 399 400 401# Calculates the average value (summary view) per region. 402def summary_average_code_region(array_of_code_regions, file_name): 403 summary = Summary(file_name, 0, 0, 0, 0, 0, 0, 0, 0, None, None) 404 for i in range(len(array_of_code_regions)): 405 summary.block_rthroughput += array_of_code_regions[i].block_rthroughput 406 summary.dispatch_width += array_of_code_regions[i].dispatch_width 407 summary.ipc += array_of_code_regions[i].ipc 408 summary.instructions += array_of_code_regions[i].instructions 409 summary.iterations += array_of_code_regions[i].iterations 410 summary.total_cycles += array_of_code_regions[i].total_cycles 411 summary.total_uops += array_of_code_regions[i].total_uops 412 summary.uops_per_cycle += array_of_code_regions[i].uops_per_cycle 413 summary.block_rthroughput = round( 414 summary.block_rthroughput / len(array_of_code_regions), 2 415 ) 416 summary.dispatch_width = round( 417 summary.dispatch_width / len(array_of_code_regions), 2 418 ) 419 summary.ipc = round(summary.ipc / len(array_of_code_regions), 2) 420 summary.instructions = round(summary.instructions / len(array_of_code_regions), 2) 421 summary.iterations = round(summary.iterations / len(array_of_code_regions), 2) 422 summary.total_cycles = round(summary.total_cycles / len(array_of_code_regions), 2) 423 summary.total_uops = round(summary.total_uops / len(array_of_code_regions), 2) 424 summary.uops_per_cycle = round( 425 summary.uops_per_cycle / len(array_of_code_regions), 2 426 ) 427 return summary 428 429 430# Based on the obtained results (resource pressure per iter) of llvm-mca tool, draws plots for multiple input files. 431def draw_plot_resource_pressure( 432 array_average_resource_pressure_per_file, opts, name_target_info_resources 433): 434 try: 435 import matplotlib.pyplot as plt 436 except ImportError: 437 print("error: matplotlib.pyplot not found.") 438 sys.exit(1) 439 try: 440 from matplotlib.cm import get_cmap 441 except ImportError: 442 print("error: get_cmap (matplotlib.cm) not found.") 443 sys.exit(1) 444 445 fig, axs = plt.subplots() 446 fig.suptitle( 447 "Resource pressure per iterations", 448 fontsize=20, 449 fontweight="bold", 450 color="black", 451 ) 452 453 maxValue = 0 454 for j in range(len(opts.file_names)): 455 if maxValue < max(array_average_resource_pressure_per_file[j]): 456 maxValue = max(array_average_resource_pressure_per_file[j]) 457 458 cmap = get_cmap("tab20") 459 colors = cmap.colors 460 461 xticklabels = [None] * len(opts.file_names) * len(name_target_info_resources) 462 index = 0 463 464 for j in range(len(name_target_info_resources)): 465 for i in range(len(opts.file_names)): 466 if i == 0: 467 axs.bar( 468 j * len(opts.file_names) * 10 + i * 10, 469 array_average_resource_pressure_per_file[i][j], 470 width=1, 471 color=colors[j], 472 label=name_target_info_resources[j], 473 ) 474 else: 475 axs.bar( 476 j * len(opts.file_names) * 10 + i * 10, 477 array_average_resource_pressure_per_file[i][j], 478 width=1, 479 color=colors[j], 480 ) 481 axs.text( 482 j * len(opts.file_names) * 10 + i * 10, 483 array_average_resource_pressure_per_file[i][j] + (maxValue / 40), 484 s=str(array_average_resource_pressure_per_file[i][j]), 485 color=colors[j], 486 fontweight="bold", 487 fontsize=3, 488 ) 489 xticklabels[index] = opts.file_names[i] 490 index = index + 1 491 492 axs.set_xticks( 493 [ 494 j * len(opts.file_names) * 10 + i * 10 495 for j in range(len(name_target_info_resources)) 496 for i in range(len(opts.file_names)) 497 ] 498 ) 499 axs.set_xticklabels(xticklabels, rotation=65) 500 501 axs.set_axisbelow(True) 502 axs.set_xlim([-0.5, len(opts.file_names) * len(name_target_info_resources) * 10]) 503 axs.set_ylim([0, maxValue + maxValue / 10]) 504 505 fig.legend(prop={"size": 15}) 506 figg = plt.gcf() 507 figg.set_size_inches((25, 15), forward=False) 508 if opts.plot_path[0] == "-": 509 plt.savefig("llvm-mca-plot-resource-pressure.png", dpi=500) 510 print("The plot was saved within llvm-mca-plot-resource-pressure.png") 511 else: 512 plt.savefig( 513 os.path.normpath( 514 os.path.join(opts.plot_path[0], "llvm-mca-plot-resource-pressure.png") 515 ), 516 dpi=500, 517 ) 518 print( 519 "The plot was saved within {}.".format( 520 os.path.normpath( 521 os.path.join( 522 opts.plot_path[0], "llvm-mca-plot-resource-pressure.png" 523 ) 524 ) 525 ) 526 ) 527 528 529# Calculates the average value (resource pressure per iter) per region. 530def average_code_region_resource_pressure(array_of_code_regions, file_name): 531 resource_pressure_per_iter_one_file = [0] * len( 532 array_of_code_regions[0].iteration_resource_pressure 533 ) 534 for i in range(len(array_of_code_regions)): 535 for j in range(len(array_of_code_regions[i].iteration_resource_pressure)): 536 if array_of_code_regions[i].iteration_resource_pressure[j] != "-": 537 resource_pressure_per_iter_one_file[j] += float( 538 array_of_code_regions[i].iteration_resource_pressure[j] 539 ) 540 for i in range(len(resource_pressure_per_iter_one_file)): 541 resource_pressure_per_iter_one_file[i] = round( 542 resource_pressure_per_iter_one_file[i] / len(array_of_code_regions), 2 543 ) 544 return resource_pressure_per_iter_one_file 545 546 547def Main(): 548 parser = argparse.ArgumentParser() 549 opts = parse_program_args(parser) 550 551 if not verify_program_inputs(opts): 552 parser.print_help() 553 sys.exit(1) 554 555 matrix_of_code_regions = [None] * len(opts.file_names) 556 557 for i in range(len(opts.file_names)): 558 matrix_of_code_regions[i] = run_llvm_mca_tool(opts, opts.file_names[i]) 559 if not opts.plot and not opts.plot_resource_pressure: 560 console_print_results(matrix_of_code_regions, opts) 561 else: 562 if opts.plot: 563 array_average_summary_per_file = [None] * len(matrix_of_code_regions) 564 for j in range(len(matrix_of_code_regions)): 565 array_average_summary_per_file[j] = summary_average_code_region( 566 matrix_of_code_regions[j], opts.file_names[j] 567 ) 568 draw_plot_files_summary(array_average_summary_per_file, opts) 569 if opts.plot_resource_pressure: 570 array_average_resource_pressure_per_file = [None] * len( 571 matrix_of_code_regions 572 ) 573 for j in range(len(matrix_of_code_regions)): 574 array_average_resource_pressure_per_file[ 575 j 576 ] = average_code_region_resource_pressure( 577 matrix_of_code_regions[j], opts.file_names[j] 578 ) 579 draw_plot_resource_pressure( 580 array_average_resource_pressure_per_file, 581 opts, 582 matrix_of_code_regions[0][0].name_target_info_resources, 583 ) 584 585 586if __name__ == "__main__": 587 Main() 588 sys.exit(0) 589