| #!/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="[=<path to llvm-mca>]", |
| help="Specified relative path to binary of llvm-mca.", |
| ) |
| parser.add_argument( |
| "--args", |
| nargs=1, |
| type=str, |
| action="store", |
| metavar="[='-option1=<arg> -option2=<arg> ...']", |
| 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="[=<path>]", |
| 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) |