Merge pull request #96 from stephenswat/feat/yaml_output

Add support for structured YAML output
This commit is contained in:
Jan
2023-08-15 14:31:31 +02:00
committed by GitHub
4 changed files with 216 additions and 1 deletions

View File

@@ -8,6 +8,7 @@ import re
from datetime import datetime as dt
from osaca.semantics import INSTR_FLAGS, ArchSemantics, KernelDG, MachineModel
from osaca.parser import AttrDict
def _get_version(*file_paths):
@@ -201,6 +202,99 @@ class Frontend(object):
+ self.loopcarried_dependencies(kernel_dg.get_loopcarried_dependencies())
)
def full_analysis_dict(
self,
kernel,
kernel_dg: KernelDG,
arch_warning=False,
length_warning=False,
lcd_warning=False,
):
"""
Create a dictionary of the full analysis for machine-readable output.
:param kernel: kernel to report on
:type kernel: list
:param kernel_dg: directed graph containing CP and LCD
:type kernel_dg: :class:`~osaca.semantics.KernelDG`
:param arch_warning: flag for additional user warning to specify micro-arch
:type arch_warning: boolean, optional
:param length_warning: flag for additional user warning to specify kernel length with
--lines
:type length_warning: boolean, optional
:param lcd_warning: flag for additional user warning due to LCD analysis timed out
:type lcd_warning: boolean, optional
:returns: dict -- a dict of the analysis
"""
warnings = []
if arch_warning:
warnings.append("ArchWarning")
if length_warning:
warnings.append("LengthWarning")
if lcd_warning:
warnings.append("LCDWarning")
if INSTR_FLAGS.TP_UNKWN in [flag for instr in kernel for flag in instr["flags"]]:
warnings.append("UnknownInstrWarning")
tp_sum = ArchSemantics.get_throughput_sum(kernel) or kernel[0]["port_pressure"]
cp_kernel = kernel_dg.get_critical_path()
dep_dict = kernel_dg.get_loopcarried_dependencies()
lcd_sum = 0.0
if dep_dict:
longest_lcd = max(dep_dict, key=lambda ln: dep_dict[ln]["latency"])
lcd_sum = dep_dict[longest_lcd]["latency"]
return {
"Header": self._header_report_dict(),
"Warnings": warnings,
"Kernel": [
{
"Line": re.sub(r"\s+", " ", x["line"].strip()),
"LineNumber": x["line_number"],
"Flags": list(x["flags"]),
"Instruction": x["instruction"],
"Operands": AttrDict.get_dict(x["operands"]),
"SemanticOperands": AttrDict.get_dict(x["semantic_operands"]),
"Label": x["label"],
"Directive": x["directive"],
"Latency": float(x["latency"]),
"LatencyCP": float(x["latency_cp"]),
"LatencyLCD": float(x["latency_lcd"]),
"Throughput": float(x["throughput"]),
"LatencyWithoutLoad": float(x["latency_wo_load"]),
"PortPressure": {
self._machine_model.get_ports()[i]: v
for i, v in enumerate(x["port_pressure"])
},
"PortUops": [
{
"Ports": list(y[1]),
"Cycles": y[0],
}
for y in x["port_uops"]
],
"Comment": x["comment"],
}
for x in kernel
],
"Summary": {
"PortPressure": {
self._machine_model.get_ports()[i]: v for i, v in enumerate(tp_sum)
},
"CriticalPath": sum([x["latency_cp"] for x in cp_kernel]),
"LCD": lcd_sum,
},
"Target": {
"Name": self._arch.upper(),
"Ports": list(self._machine_model.get_ports()),
},
}
def combined_view(
self,
kernel,
@@ -449,6 +543,15 @@ class Frontend(object):
)
return header + "\n"
def _header_report_dict(self):
"""Return header information in a dictionary format"""
return {
"Version": _get_version("__init__.py"),
"FileName": self._filename,
"Architecture": self._arch,
"Timestamp": dt.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
}
def _symbol_map(self):
"""Prints instruction flag map."""
symbol_dict = {

View File

@@ -7,6 +7,8 @@ import re
import sys
from functools import lru_cache
import ruamel.yaml
from osaca.db_interface import import_benchmark_output, sanity_check
from osaca.frontend import Frontend
from osaca.parser import BaseParser, ParserAArch64, ParserX86ATT
@@ -188,6 +190,13 @@ def create_parser(parser=None):
type=argparse.FileType("w"),
help="Write analysis to this file (default to stdout).",
)
parser.add_argument(
"--yaml-out",
default=None,
dest="yaml_out",
type=argparse.FileType("w"),
help="Write analysis as YAML representation to this file",
)
parser.add_argument(
"file",
type=argparse.FileType("r"),
@@ -360,6 +369,17 @@ def inspect(args, output_file=sys.stdout):
),
file=output_file,
)
if args.yaml_out is not None:
ruamel.yaml.dump(
frontend.full_analysis_dict(
kernel,
kernel_graph,
arch_warning=print_arch_warning,
length_warning=print_length_warning,
lcd_warning=kernel_graph.timed_out,
),
args.yaml_out,
)
def run(args, output_file=sys.stdout):

View File

@@ -27,3 +27,27 @@ class AttrDict(dict):
dictionary[key] = [AttrDict.convert_dict(x) for x in entry]
return AttrDict(dictionary)
return dictionary
@staticmethod
def get_dict(attrdict):
"""
Convert given `AttrDict` to a standard dictionary.
:param attrdict: `AttrDict` to be converted
:type attrdict: `AttrDict`
:returns: `dict` representation of ``AttrDict``
"""
if isinstance(attrdict, type(list())):
return [AttrDict.get_dict(x) for x in attrdict]
if isinstance(attrdict, type(AttrDict())):
newdict = {}
for key in list(attrdict.keys()):
entry = attrdict[key]
if isinstance(entry, type(dict())) or isinstance(entry, type(AttrDict())):
newdict[key] = AttrDict.get_dict(attrdict[key])
elif isinstance(entry, type(list())):
newdict[key] = [AttrDict.get_dict(x) for x in entry]
else:
newdict[key] = entry
return newdict
return attrdict

View File

@@ -8,7 +8,7 @@ import unittest
from osaca.frontend import Frontend
from osaca.parser import ParserAArch64, ParserX86ATT
from osaca.semantics import ArchSemantics, KernelDG, MachineModel
from osaca.semantics import ArchSemantics, KernelDG, MachineModel, reduce_to_section
class TestFrontend(unittest.TestCase):
@@ -81,6 +81,74 @@ class TestFrontend(unittest.TestCase):
fe.full_analysis(self.kernel_AArch64, dg, verbose=True)
# TODO compare output with checked string
def test_dict_output_x86(self):
dg = KernelDG(self.kernel_x86, self.parser_x86, self.machine_model_csx, self.semantics_csx)
fe = Frontend(path_to_yaml=os.path.join(self.MODULE_DATA_DIR, "csx.yml"))
analysis_dict = fe.full_analysis_dict(self.kernel_x86, dg)
self.assertEqual(len(self.kernel_x86), len(analysis_dict["Kernel"]))
self.assertEqual("csx", analysis_dict["Header"]["Architecture"])
self.assertEqual(len(analysis_dict["Warnings"]), 0)
for i, line in enumerate(self.kernel_x86):
self.assertEqual(line["throughput"], analysis_dict["Kernel"][i]["Throughput"])
self.assertEqual(line["latency"], analysis_dict["Kernel"][i]["Latency"])
self.assertEqual(
line["latency_wo_load"], analysis_dict["Kernel"][i]["LatencyWithoutLoad"]
)
self.assertEqual(line["latency_cp"], analysis_dict["Kernel"][i]["LatencyCP"])
self.assertEqual(line["instruction"], analysis_dict["Kernel"][i]["Instruction"])
self.assertEqual(len(line["operands"]), len(analysis_dict["Kernel"][i]["Operands"]))
self.assertEqual(
len(line["semantic_operands"]["source"]),
len(analysis_dict["Kernel"][i]["SemanticOperands"]["source"]),
)
self.assertEqual(
len(line["semantic_operands"]["destination"]),
len(analysis_dict["Kernel"][i]["SemanticOperands"]["destination"]),
)
self.assertEqual(
len(line["semantic_operands"]["src_dst"]),
len(analysis_dict["Kernel"][i]["SemanticOperands"]["src_dst"]),
)
self.assertEqual(line["flags"], analysis_dict["Kernel"][i]["Flags"])
self.assertEqual(line["line_number"], analysis_dict["Kernel"][i]["LineNumber"])
def test_dict_output_AArch64(self):
reduced_kernel = reduce_to_section(self.kernel_AArch64, self.semantics_tx2._isa)
dg = KernelDG(
reduced_kernel,
self.parser_AArch64,
self.machine_model_tx2,
self.semantics_tx2,
)
fe = Frontend(path_to_yaml=os.path.join(self.MODULE_DATA_DIR, "tx2.yml"))
analysis_dict = fe.full_analysis_dict(reduced_kernel, dg)
self.assertEqual(len(reduced_kernel), len(analysis_dict["Kernel"]))
self.assertEqual("tx2", analysis_dict["Header"]["Architecture"])
self.assertEqual(len(analysis_dict["Warnings"]), 0)
for i, line in enumerate(reduced_kernel):
self.assertEqual(line["throughput"], analysis_dict["Kernel"][i]["Throughput"])
self.assertEqual(line["latency"], analysis_dict["Kernel"][i]["Latency"])
self.assertEqual(
line["latency_wo_load"], analysis_dict["Kernel"][i]["LatencyWithoutLoad"]
)
self.assertEqual(line["latency_cp"], analysis_dict["Kernel"][i]["LatencyCP"])
self.assertEqual(line["instruction"], analysis_dict["Kernel"][i]["Instruction"])
self.assertEqual(len(line["operands"]), len(analysis_dict["Kernel"][i]["Operands"]))
self.assertEqual(
len(line["semantic_operands"]["source"]),
len(analysis_dict["Kernel"][i]["SemanticOperands"]["source"]),
)
self.assertEqual(
len(line["semantic_operands"]["destination"]),
len(analysis_dict["Kernel"][i]["SemanticOperands"]["destination"]),
)
self.assertEqual(
len(line["semantic_operands"]["src_dst"]),
len(analysis_dict["Kernel"][i]["SemanticOperands"]["src_dst"]),
)
self.assertEqual(line["flags"], analysis_dict["Kernel"][i]["Flags"])
self.assertEqual(line["line_number"], analysis_dict["Kernel"][i]["LineNumber"])
##################
# Helper functions
##################