mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-08 09:08:10 +02:00
pan/perf: Add libGPUCounters to xml translator
libGPUCounters (1) contains all required information to generate the counter definitions used in mesa for Bifrost+ architectures. This script gathers the required information from the xml definitions in libGPUCounters and outputs pan/perf xmls. It also already includes support for derived counters, meaning counters which are computed from other counters actually created by HW. For those, we recursively resolve the variables in the equation until only HW counters and configuration values are left. It makes sense to do it here already since the datastructures make it a simple addition and the codegen doesn't need to handle it at compile time later that way. Derived counters that require MALI_CONFIG_TIME_SPAN are skipped for now. libGPUCounters also does not generate the equations for those and it makes hooking up the derived counters in pan simpler when we don't have to estimate the duration of a sample in some way. 1) https://github.com/ARM-software/libGPUCounters
This commit is contained in:
parent
bf8cfffef5
commit
d8bdacfce8
1 changed files with 286 additions and 0 deletions
286
src/panfrost/perf/pan_gen_perf_defs.py
Normal file
286
src/panfrost/perf/pan_gen_perf_defs.py
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
# Copyright (c) 2026 Arm Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as et
|
||||
import re
|
||||
|
||||
COUNTERINFO_PATH = "./specification/database/counterinfo"
|
||||
HARDWARE_LAYOUT_PATH = "./specification/database/hardwarelayout"
|
||||
|
||||
HW_LAYOUT_LUT: dict[str, "HardwareLayout"] = {}
|
||||
|
||||
OUTPUT_COPYRIGHT = """<!--
|
||||
Copyright (c) {year} Arm, Ltd.
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Generated from libGPUCounters @ {rev}.
|
||||
https://github.com/ARM-software/libGPUCounters
|
||||
which is:
|
||||
Copyright (c) 2023-2025 Arm Limited
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def get_revision(path):
|
||||
cmd = ["git", "rev-parse", "HEAD"]
|
||||
res = subprocess.run(cmd, capture_output=True, cwd=path.as_posix())
|
||||
if res.returncode != 0:
|
||||
return None
|
||||
else:
|
||||
return res.stdout.decode().strip()
|
||||
|
||||
|
||||
def map_nn(v, f):
|
||||
return None if v is None else f(v)
|
||||
|
||||
|
||||
def get_elem_text(xml, name):
|
||||
e = xml.find(name)
|
||||
if e is not None:
|
||||
return e.text
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CounterHwLocation:
|
||||
block: str
|
||||
counter_index: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class HardwareLayout:
|
||||
gpu_name: str
|
||||
# map source name to (block index, counter index)
|
||||
locations: dict[str, CounterHwLocation]
|
||||
|
||||
@staticmethod
|
||||
def from_xml(xml: et.Element) -> "HardwareLayout":
|
||||
gpu_name = xml.get("gpu")
|
||||
assert gpu_name is not None
|
||||
locations = {}
|
||||
for cbe in xml.findall("CounterBlock"):
|
||||
cb_name = cbe.get("type")
|
||||
assert cb_name is not None
|
||||
for counter in cbe.findall("Counter"):
|
||||
source_name = counter.get("name")
|
||||
counter_index = counter.get("index")
|
||||
assert counter_index is not None
|
||||
locations[source_name] = CounterHwLocation(
|
||||
cb_name, int(counter_index))
|
||||
|
||||
return HardwareLayout(gpu_name=gpu_name, locations=locations)
|
||||
|
||||
|
||||
def parse_hw_layout(path: Path):
|
||||
xml = et.parse(path)
|
||||
return HardwareLayout.from_xml(xml.getroot())
|
||||
|
||||
|
||||
def parse_supported_gpus(xml):
|
||||
supported_list = xml.find("SupportedGPUs")
|
||||
return [e.text for e in supported_list.findall("GPU")]
|
||||
|
||||
|
||||
def group_from_filename(fname):
|
||||
# This maps to the values of the "type" field in the CounterBlock xml blocks.
|
||||
fname_to_dbkey = {
|
||||
"GPUFrontEnd": "GPU Front-end",
|
||||
"L2Cache": "Memory System",
|
||||
"Tiler": "Tiler",
|
||||
"ShaderCore": "Shader Core",
|
||||
"Constants": "Constants",
|
||||
"Content": "Content",
|
||||
}
|
||||
for name, key in fname_to_dbkey.items():
|
||||
if name in fname:
|
||||
return key
|
||||
assert False and "could not find group from filename"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CounterInfo:
|
||||
machine_name: str
|
||||
supported_gpus: list[str]
|
||||
group: str
|
||||
equation: str = ""
|
||||
source_name: str = ""
|
||||
# Can be used as a fallback to find hw offsets if source_name isn't available.
|
||||
source_alias_name: str = ""
|
||||
human_name: str = ""
|
||||
short_desc: str = ""
|
||||
units: str = ""
|
||||
|
||||
@staticmethod
|
||||
def from_xml(xml, group):
|
||||
machine_name = get_elem_text(xml, "MachineName")
|
||||
assert machine_name is not None
|
||||
supported = parse_supported_gpus(xml)
|
||||
|
||||
desc_raw = get_elem_text(xml, "ShortDescription") or ""
|
||||
desc_san = " ".join(map(str.strip, desc_raw.splitlines())).strip()
|
||||
|
||||
return CounterInfo(
|
||||
machine_name,
|
||||
supported,
|
||||
group,
|
||||
equation=map_nn(get_elem_text(xml, "Equation"), str.strip) or "",
|
||||
source_name=get_elem_text(xml, "SourceName") or "",
|
||||
source_alias_name=get_elem_text(xml, "SourceAlias") or "",
|
||||
human_name=get_elem_text(xml, "HumanName") or "",
|
||||
short_desc=desc_san,
|
||||
units=(get_elem_text(xml, "Units") or "").strip(),
|
||||
)
|
||||
|
||||
def is_derived(self):
|
||||
return not self.source_name
|
||||
|
||||
def get_hw_offsets(self, gpu: str) -> CounterHwLocation:
|
||||
assert self.source_name != ""
|
||||
assert gpu in self.supported_gpus
|
||||
locs = HW_LAYOUT_LUT[gpu].locations
|
||||
if self.source_name in locs:
|
||||
return locs[self.source_name]
|
||||
else:
|
||||
# If the normal source name doesn't work try the alias
|
||||
# Needed for example for RT_RAY_BOX_ISSUED on G1 which is using the
|
||||
# alias RT_BOX_ISSUE_CYCLES there.
|
||||
assert self.source_alias_name != ""
|
||||
return locs[self.source_alias_name]
|
||||
|
||||
def is_supported(self):
|
||||
return "MALI_CONFIG_TIME_SPAN" not in self.equation
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProductInfo:
|
||||
product_id: str
|
||||
database_key: str
|
||||
|
||||
|
||||
def parse_counters(path: Path):
|
||||
group = group_from_filename(path.name)
|
||||
xml = et.parse(path)
|
||||
return [CounterInfo.from_xml(e, group) for e in xml.findall("CounterInfo")]
|
||||
|
||||
|
||||
def resolve_equation(eq: str, counters_gpu: list[CounterInfo]):
|
||||
sorted_c = sorted(counters_gpu, key=lambda c: len(c.machine_name))
|
||||
max_len = max([len(c.machine_name) for c in sorted_c])
|
||||
|
||||
# This loop replaces variables which aren't hardware counters or config values
|
||||
# until only all have been replaced.
|
||||
# Iterate backwards from the largest to the smallest variable to make this work:
|
||||
# eq = MaliMainQueueTask * MaliMainQueueTaskSize * MaliMainQueueTaskSize
|
||||
|
||||
progress = True
|
||||
while progress:
|
||||
progress = False
|
||||
for l in range(max_len, 0, -1):
|
||||
for c in filter(lambda c: len(c.machine_name) == l, sorted_c):
|
||||
if c.machine_name in eq:
|
||||
if c.is_derived():
|
||||
repl = f"({c.equation})"
|
||||
else:
|
||||
assert c.source_name is not None
|
||||
repl = f"({c.source_name})"
|
||||
|
||||
eq = eq.replace(c.machine_name, repl)
|
||||
progress = True
|
||||
break
|
||||
|
||||
# There was a change, need to restart because we might have added
|
||||
# a variable with len(name) > l.
|
||||
if progress:
|
||||
break
|
||||
return eq
|
||||
|
||||
|
||||
def counter_list_to_xml(counters: list[CounterInfo], gpu: str):
|
||||
gpu_xml = gpu.replace("Mali-", "").replace("Mali", "").strip()
|
||||
root = et.Element("metrics", attrib={"id": gpu_xml})
|
||||
|
||||
IGNORE_CATS = {"Constants", "Content"}
|
||||
|
||||
cat_names = set([c.group for c in counters])
|
||||
categories = dict()
|
||||
for c in sorted(cat_names):
|
||||
if c in IGNORE_CATS:
|
||||
continue
|
||||
categories[c] = et.SubElement(root, "category", attrib={"name": c})
|
||||
|
||||
for counter in sorted(counters, key=lambda c: c.machine_name):
|
||||
if not counter.is_supported():
|
||||
continue
|
||||
|
||||
if counter.group in IGNORE_CATS:
|
||||
continue
|
||||
p = categories[counter.group]
|
||||
|
||||
attrib = {
|
||||
"name": counter.machine_name,
|
||||
"title": counter.human_name,
|
||||
"description": counter.short_desc,
|
||||
"units": counter.units,
|
||||
}
|
||||
|
||||
if counter.is_derived():
|
||||
attrib["equation"] = resolve_equation(counter.equation, counters)
|
||||
else:
|
||||
attrib["counter"] = counter.source_name
|
||||
attrib["offset"] = str(counter.get_hw_offsets(gpu).counter_index)
|
||||
|
||||
et.SubElement(p, "event", attrib)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def main():
|
||||
p = ArgumentParser()
|
||||
p.add_argument("lib_gpu_counters", type=Path,
|
||||
help="Path to libGPUCounter source")
|
||||
p.add_argument(
|
||||
"--output-path", type=Path, default=Path(__file__).parent / "generated"
|
||||
)
|
||||
args = p.parse_args()
|
||||
|
||||
for f in (args.lib_gpu_counters / HARDWARE_LAYOUT_PATH).glob("*.xml"):
|
||||
l = parse_hw_layout(f)
|
||||
HW_LAYOUT_LUT[l.gpu_name] = l
|
||||
|
||||
counters: list[CounterInfo] = []
|
||||
for f in (args.lib_gpu_counters / COUNTERINFO_PATH).glob("*.xml"):
|
||||
counters += parse_counters(f)
|
||||
|
||||
args.output_path.mkdir(exist_ok=True)
|
||||
|
||||
# Generate one file for each GPU.
|
||||
all_gpus = set().union(*(c.supported_gpus for c in counters))
|
||||
for gpu in all_gpus:
|
||||
gpu_counters = [c for c in counters if gpu in c.supported_gpus]
|
||||
xml = counter_list_to_xml(gpu_counters, gpu)
|
||||
et.indent(xml)
|
||||
|
||||
fname = gpu.replace("Mali-", "").replace("Mali", "").strip() + ".xml"
|
||||
year = datetime.datetime.now().year
|
||||
rev = get_revision(args.lib_gpu_counters)
|
||||
assert(rev is not None)
|
||||
|
||||
with open(args.output_path / fname, "wb") as f:
|
||||
f.write(
|
||||
OUTPUT_COPYRIGHT.format(
|
||||
year=year, rev=rev).encode(encoding="utf-8")
|
||||
)
|
||||
f.write(et.tostring(xml, encoding="utf-8"))
|
||||
f.write("\n".encode(encoding="utf-8"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Reference in a new issue