#!/usr/bin/env python3 import argparse import sys import os from json import loads from subprocess import Popen, PIPE # Holds code regions statistics. class Summary: def __init__( self, name, block_rthroughput, dispatch_width, ipc, instructions, iterations, total_cycles, total_uops, uops_per_cycle, iteration_resource_pressure, name_target_info_resources, ): self.name = name self.block_rthroughput = block_rthroughput self.dispatch_width = dispatch_width self.ipc = ipc self.instructions = instructions self.iterations = iterations self.total_cycles = total_cycles self.total_uops = total_uops self.uops_per_cycle = uops_per_cycle self.iteration_resource_pressure = iteration_resource_pressure self.name_target_info_resources = name_target_info_resources # Parse the program arguments. def parse_program_args(parser): parser.add_argument( "file_names", nargs="+", type=str, help="Names of files which llvm-mca tool process.", ) parser.add_argument( "--llvm-mca-binary", nargs=1, required=True, type=str, action="store", metavar="[=]", help="Specified relative path to binary of llvm-mca.", ) parser.add_argument( "--args", nargs=1, type=str, action="store", metavar="[='-option1= -option2= ...']", default=["-"], help="Forward options to lvm-mca tool.", ) parser.add_argument( "-plot", action="store_true", default=False, help="Draw plots of statistics for input files.", ) parser.add_argument( "-plot-resource-pressure", action="store_true", default=False, help="Draw plots of resource pressure per iterations for input files.", ) parser.add_argument( "--plot-path", nargs=1, type=str, action="store", metavar="[=]", default=["-"], help="Specify relative path where you want to save the plots.", ) parser.add_argument( "-v", action="store_true", default=False, help="More details about the running lvm-mca tool.", ) return parser.parse_args() # Verify that the program inputs meet the requirements. def verify_program_inputs(opts): if opts.plot_path[0] != "-" and not opts.plot and not opts.plot_resource_pressure: print( "error: Please specify --plot-path only with the -plot or -plot-resource-pressure options." ) return False return True # Returns the name of the file to be analyzed from the path it is on. def get_filename_from_path(path): index_of_slash = path.rfind("/") return path[(index_of_slash + 1) : len(path)] # Returns the results of the running llvm-mca tool for the input file. def run_llvm_mca_tool(opts, file_name): # Get the path of the llvm-mca binary file. llvm_mca_cmd = opts.llvm_mca_binary[0] # The statistics llvm-mca options. if opts.args[0] != "-": llvm_mca_cmd += " " + opts.args[0] llvm_mca_cmd += " -json" # Set file which llvm-mca tool will process. llvm_mca_cmd += " " + file_name if opts.v: print("run: $ " + llvm_mca_cmd + "\n") # Generate the stats with the llvm-mca. subproc = Popen( llvm_mca_cmd.split(" "), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True, ) cmd_stdout, cmd_stderr = subproc.communicate() try: json_parsed = loads(cmd_stdout) except: print("error: No valid llvm-mca statistics found.") print(cmd_stderr) sys.exit(1) if opts.v: print("Simulation Parameters: ") simulation_parameters = json_parsed["SimulationParameters"] for key in simulation_parameters: print(key, ":", simulation_parameters[key]) print("\n") code_regions_len = len(json_parsed["CodeRegions"]) array_of_code_regions = [None] * code_regions_len for i in range(code_regions_len): code_region_instructions_len = len( json_parsed["CodeRegions"][i]["Instructions"] ) target_info_resources_len = len(json_parsed["TargetInfo"]["Resources"]) iteration_resource_pressure = ["-" for k in range(target_info_resources_len)] resource_pressure_info = json_parsed["CodeRegions"][i]["ResourcePressureView"][ "ResourcePressureInfo" ] name_target_info_resources = json_parsed["TargetInfo"]["Resources"] for s in range(len(resource_pressure_info)): obj_of_resource_pressure_info = resource_pressure_info[s] if ( obj_of_resource_pressure_info["InstructionIndex"] == code_region_instructions_len ): iteration_resource_pressure[ obj_of_resource_pressure_info["ResourceIndex"] ] = str(round(obj_of_resource_pressure_info["ResourceUsage"], 2)) array_of_code_regions[i] = Summary( file_name, json_parsed["CodeRegions"][i]["SummaryView"]["BlockRThroughput"], json_parsed["CodeRegions"][i]["SummaryView"]["DispatchWidth"], json_parsed["CodeRegions"][i]["SummaryView"]["IPC"], json_parsed["CodeRegions"][i]["SummaryView"]["Instructions"], json_parsed["CodeRegions"][i]["SummaryView"]["Iterations"], json_parsed["CodeRegions"][i]["SummaryView"]["TotalCycles"], json_parsed["CodeRegions"][i]["SummaryView"]["TotaluOps"], json_parsed["CodeRegions"][i]["SummaryView"]["uOpsPerCycle"], iteration_resource_pressure, name_target_info_resources, ) return array_of_code_regions # Print statistics in console for single file or for multiple files. def console_print_results(matrix_of_code_regions, opts): try: import termtables as tt except ImportError: print("error: termtables not found.") sys.exit(1) headers_names = [None] * (len(opts.file_names) + 1) headers_names[0] = " " max_code_regions = 0 print("Input files:") for i in range(len(matrix_of_code_regions)): if max_code_regions < len(matrix_of_code_regions[i]): max_code_regions = len(matrix_of_code_regions[i]) print("[f" + str(i + 1) + "]: " + get_filename_from_path(opts.file_names[i])) headers_names[i + 1] = "[f" + str(i + 1) + "]: " print("\nITERATIONS: " + str(matrix_of_code_regions[0][0].iterations) + "\n") for i in range(max_code_regions): print( "\n-----------------------------------------\nCode region: " + str(i + 1) + "\n" ) table_values = [ [[None] for i in range(len(matrix_of_code_regions) + 1)] for j in range(7) ] table_values[0][0] = "Instructions: " table_values[1][0] = "Total Cycles: " table_values[2][0] = "Total uOps: " table_values[3][0] = "Dispatch Width: " table_values[4][0] = "uOps Per Cycle: " table_values[5][0] = "IPC: " table_values[6][0] = "Block RThroughput: " for j in range(len(matrix_of_code_regions)): if len(matrix_of_code_regions[j]) > i: table_values[0][j + 1] = str(matrix_of_code_regions[j][i].instructions) table_values[1][j + 1] = str(matrix_of_code_regions[j][i].total_cycles) table_values[2][j + 1] = str(matrix_of_code_regions[j][i].total_uops) table_values[3][j + 1] = str( matrix_of_code_regions[j][i].dispatch_width ) table_values[4][j + 1] = str( round(matrix_of_code_regions[j][i].uops_per_cycle, 2) ) table_values[5][j + 1] = str(round(matrix_of_code_regions[j][i].ipc, 2)) table_values[6][j + 1] = str( round(matrix_of_code_regions[j][i].block_rthroughput, 2) ) else: table_values[0][j + 1] = "-" table_values[1][j + 1] = "-" table_values[2][j + 1] = "-" table_values[3][j + 1] = "-" table_values[4][j + 1] = "-" table_values[5][j + 1] = "-" table_values[6][j + 1] = "-" tt.print( table_values, header=headers_names, style=tt.styles.ascii_thin_double, padding=(0, 1), ) print("\nResource pressure per iteration: \n") table_values = [ [ [None] for i in range( len(matrix_of_code_regions[0][0].iteration_resource_pressure) + 1 ) ] for j in range(len(matrix_of_code_regions) + 1) ] table_values[0] = [" "] + matrix_of_code_regions[0][ 0 ].name_target_info_resources for j in range(len(matrix_of_code_regions)): if len(matrix_of_code_regions[j]) > i: table_values[j + 1] = [ "[f" + str(j + 1) + "]: " ] + matrix_of_code_regions[j][i].iteration_resource_pressure else: table_values[j + 1] = ["[f" + str(j + 1) + "]: "] + len( matrix_of_code_regions[0][0].iteration_resource_pressure ) * ["-"] tt.print( table_values, style=tt.styles.ascii_thin_double, padding=(0, 1), ) print("\n") # Based on the obtained results (summary view) of llvm-mca tool, draws plots for multiple input files. def draw_plot_files_summary(array_of_summary, opts): try: import matplotlib.pyplot as plt except ImportError: print("error: matplotlib.pyplot not found.") sys.exit(1) try: from matplotlib.cm import get_cmap except ImportError: print("error: get_cmap (matplotlib.cm) not found.") sys.exit(1) names = [ "Block RThroughput", "Dispatch Width", "IPC", "uOps Per Cycle", "Instructions", "Total Cycles", "Total uOps", ] rows, cols = (len(opts.file_names), 7) values = [[0 for x in range(cols)] for y in range(rows)] for i in range(len(opts.file_names)): values[i][0] = array_of_summary[i].block_rthroughput values[i][1] = array_of_summary[i].dispatch_width values[i][2] = array_of_summary[i].ipc values[i][3] = array_of_summary[i].uops_per_cycle values[i][4] = array_of_summary[i].instructions values[i][5] = array_of_summary[i].total_cycles values[i][6] = array_of_summary[i].total_uops fig, axs = plt.subplots(4, 2) fig.suptitle( "Machine code statistics", fontsize=20, fontweight="bold", color="black" ) i = 0 for x in range(4): for y in range(2): cmap = get_cmap("tab20") colors = cmap.colors if not (x == 0 and y == 1) and i < 7: axs[x][y].grid(True, color="grey", linestyle="--") maxValue = 0 if i == 0: for j in range(len(opts.file_names)): if maxValue < values[j][i]: maxValue = values[j][i] axs[x][y].bar( 0.3 * j, values[j][i], width=0.1, color=colors[j], label=get_filename_from_path(opts.file_names[j]), ) else: for j in range(len(opts.file_names)): if maxValue < values[j][i]: maxValue = values[j][i] axs[x][y].bar(0.3 * j, values[j][i], width=0.1, color=colors[j]) axs[x][y].set_axisbelow(True) axs[x][y].set_xlim([-0.3, len(opts.file_names) / 3]) axs[x][y].set_ylim([0, maxValue + (maxValue / 2)]) axs[x][y].set_title(names[i], fontsize=15, fontweight="bold") axs[x][y].axes.xaxis.set_visible(False) for j in range(len(opts.file_names)): axs[x][y].text( 0.3 * j, values[j][i] + (maxValue / 40), s=str(values[j][i]), color="black", fontweight="bold", fontsize=4, ) i = i + 1 axs[0][1].set_visible(False) fig.legend(prop={"size": 15}) figg = plt.gcf() figg.set_size_inches((25, 15), forward=False) if opts.plot_path[0] == "-": plt.savefig("llvm-mca-plot.png", dpi=500) print("The plot was saved within llvm-mca-plot.png") else: plt.savefig( os.path.normpath(os.path.join(opts.plot_path[0], "llvm-mca-plot.png")), dpi=500, ) print( "The plot was saved within {}.".format( os.path.normpath(os.path.join(opts.plot_path[0], "llvm-mca-plot.png")) ) ) # Calculates the average value (summary view) per region. def summary_average_code_region(array_of_code_regions, file_name): summary = Summary(file_name, 0, 0, 0, 0, 0, 0, 0, 0, None, None) for i in range(len(array_of_code_regions)): summary.block_rthroughput += array_of_code_regions[i].block_rthroughput summary.dispatch_width += array_of_code_regions[i].dispatch_width summary.ipc += array_of_code_regions[i].ipc summary.instructions += array_of_code_regions[i].instructions summary.iterations += array_of_code_regions[i].iterations summary.total_cycles += array_of_code_regions[i].total_cycles summary.total_uops += array_of_code_regions[i].total_uops summary.uops_per_cycle += array_of_code_regions[i].uops_per_cycle summary.block_rthroughput = round( summary.block_rthroughput / len(array_of_code_regions), 2 ) summary.dispatch_width = round( summary.dispatch_width / len(array_of_code_regions), 2 ) summary.ipc = round(summary.ipc / len(array_of_code_regions), 2) summary.instructions = round(summary.instructions / len(array_of_code_regions), 2) summary.iterations = round(summary.iterations / len(array_of_code_regions), 2) summary.total_cycles = round(summary.total_cycles / len(array_of_code_regions), 2) summary.total_uops = round(summary.total_uops / len(array_of_code_regions), 2) summary.uops_per_cycle = round( summary.uops_per_cycle / len(array_of_code_regions), 2 ) return summary # Based on the obtained results (resource pressure per iter) of llvm-mca tool, draws plots for multiple input files. def draw_plot_resource_pressure( array_average_resource_pressure_per_file, opts, name_target_info_resources ): try: import matplotlib.pyplot as plt except ImportError: print("error: matplotlib.pyplot not found.") sys.exit(1) try: from matplotlib.cm import get_cmap except ImportError: print("error: get_cmap (matplotlib.cm) not found.") sys.exit(1) fig, axs = plt.subplots() fig.suptitle( "Resource pressure per iterations", fontsize=20, fontweight="bold", color="black", ) maxValue = 0 for j in range(len(opts.file_names)): if maxValue < max(array_average_resource_pressure_per_file[j]): maxValue = max(array_average_resource_pressure_per_file[j]) cmap = get_cmap("tab20") colors = cmap.colors xticklabels = [None] * len(opts.file_names) * len(name_target_info_resources) index = 0 for j in range(len(name_target_info_resources)): for i in range(len(opts.file_names)): if i == 0: axs.bar( j * len(opts.file_names) * 10 + i * 10, array_average_resource_pressure_per_file[i][j], width=1, color=colors[j], label=name_target_info_resources[j], ) else: axs.bar( j * len(opts.file_names) * 10 + i * 10, array_average_resource_pressure_per_file[i][j], width=1, color=colors[j], ) axs.text( j * len(opts.file_names) * 10 + i * 10, array_average_resource_pressure_per_file[i][j] + (maxValue / 40), s=str(array_average_resource_pressure_per_file[i][j]), color=colors[j], fontweight="bold", fontsize=3, ) xticklabels[index] = opts.file_names[i] index = index + 1 axs.set_xticks( [ j * len(opts.file_names) * 10 + i * 10 for j in range(len(name_target_info_resources)) for i in range(len(opts.file_names)) ] ) axs.set_xticklabels(xticklabels, rotation=65) axs.set_axisbelow(True) axs.set_xlim([-0.5, len(opts.file_names) * len(name_target_info_resources) * 10]) axs.set_ylim([0, maxValue + maxValue / 10]) fig.legend(prop={"size": 15}) figg = plt.gcf() figg.set_size_inches((25, 15), forward=False) if opts.plot_path[0] == "-": plt.savefig("llvm-mca-plot-resource-pressure.png", dpi=500) print("The plot was saved within llvm-mca-plot-resource-pressure.png") else: plt.savefig( os.path.normpath( os.path.join(opts.plot_path[0], "llvm-mca-plot-resource-pressure.png") ), dpi=500, ) print( "The plot was saved within {}.".format( os.path.normpath( os.path.join( opts.plot_path[0], "llvm-mca-plot-resource-pressure.png" ) ) ) ) # Calculates the average value (resource pressure per iter) per region. def average_code_region_resource_pressure(array_of_code_regions, file_name): resource_pressure_per_iter_one_file = [0] * len( array_of_code_regions[0].iteration_resource_pressure ) for i in range(len(array_of_code_regions)): for j in range(len(array_of_code_regions[i].iteration_resource_pressure)): if array_of_code_regions[i].iteration_resource_pressure[j] != "-": resource_pressure_per_iter_one_file[j] += float( array_of_code_regions[i].iteration_resource_pressure[j] ) for i in range(len(resource_pressure_per_iter_one_file)): resource_pressure_per_iter_one_file[i] = round( resource_pressure_per_iter_one_file[i] / len(array_of_code_regions), 2 ) return resource_pressure_per_iter_one_file def Main(): parser = argparse.ArgumentParser() opts = parse_program_args(parser) if not verify_program_inputs(opts): parser.print_help() sys.exit(1) matrix_of_code_regions = [None] * len(opts.file_names) for i in range(len(opts.file_names)): matrix_of_code_regions[i] = run_llvm_mca_tool(opts, opts.file_names[i]) if not opts.plot and not opts.plot_resource_pressure: console_print_results(matrix_of_code_regions, opts) else: if opts.plot: array_average_summary_per_file = [None] * len(matrix_of_code_regions) for j in range(len(matrix_of_code_regions)): array_average_summary_per_file[j] = summary_average_code_region( matrix_of_code_regions[j], opts.file_names[j] ) draw_plot_files_summary(array_average_summary_per_file, opts) if opts.plot_resource_pressure: array_average_resource_pressure_per_file = [None] * len( matrix_of_code_regions ) for j in range(len(matrix_of_code_regions)): array_average_resource_pressure_per_file[ j ] = average_code_region_resource_pressure( matrix_of_code_regions[j], opts.file_names[j] ) draw_plot_resource_pressure( array_average_resource_pressure_per_file, opts, matrix_of_code_regions[0][0].name_target_info_resources, ) if __name__ == "__main__": Main() sys.exit(0)