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