Files
TheRock/rocm-systems/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/code_index.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

339 lines
11 KiB
Python

from __future__ import annotations
import csv
import json
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
from .records import Pc, Wave
CODE_HEADER = "ISA, _, LineNumber, Source, Codeobj, Vaddr, Hit, Latency, Stall, Idle"
CODE_ROW_INST = 0
CODE_ROW_LINE = 2
CODE_ROW_SOURCE = 3
CODE_ROW_CODE_OBJECT = 4
CODE_ROW_VADDR = 5
CODE_ROW_HIT = 6
CODE_ROW_LATENCY = 7
CODE_ROW_STALL = 8
CODE_ROW_IDLE = 9
CODE_ROW_COUNTERS = (CODE_ROW_HIT, CODE_ROW_LATENCY, CODE_ROW_STALL, CODE_ROW_IDLE)
CODE_ROW_WIDTH = CODE_ROW_IDLE + 1
STATS_COL_CODE_OBJECT = "CodeObj"
STATS_COL_VADDR = "Vaddr"
STATS_COL_INSTRUCTION = "Instruction"
STATS_COL_HITCOUNT = "Hitcount"
STATS_COL_LATENCY = "Latency"
STATS_COL_STALL = "Stall"
STATS_COL_IDLE = "Idle"
STATS_COL_SOURCE = "Source"
STATS_CSV_HEADER = [
STATS_COL_CODE_OBJECT,
STATS_COL_VADDR,
STATS_COL_INSTRUCTION,
STATS_COL_HITCOUNT,
STATS_COL_LATENCY,
STATS_COL_STALL,
STATS_COL_IDLE,
STATS_COL_SOURCE,
]
@dataclass
class CodeEntry:
pc: Pc
inst: str
line_number: int
source: str = ""
hitcount: int = 0
latency: int = 0
stall: int = 0
idle: int = 0
expected_hitcount: int = 0
expected_latency: int = 0
expected_stall: int = 0
expected_idle: int = 0
memory_size: int = 4
class CodeIndex:
def __init__(self, entries: Iterable[CodeEntry], document: dict | None = None):
self.document = document or {"code": [], "header": CODE_HEADER}
self.entries: dict[Pc, CodeEntry] = {}
self.line_numbers: dict[Pc, int] = {}
self._memory_sizes_dirty = False
for entry in entries:
self.entries[entry.pc] = entry
self.line_numbers[entry.pc] = entry.line_number
self._next_line_number = max(self.line_numbers.values(), default=0) + 1
self._mark_memory_sizes_dirty()
@classmethod
def from_code_json(cls, path: str | Path, *, load_counts: bool = True) -> "CodeIndex":
return cls.from_document(json.loads(Path(path).read_text()), load_counts=load_counts)
@classmethod
def from_document(cls, doc: dict, *, load_counts: bool = True) -> "CodeIndex":
document = json.loads(json.dumps(doc))
if not load_counts:
_clear_document_counts(document)
entries: list[CodeEntry] = []
for row in document.get("code", []):
if len(row) < CODE_ROW_WIDTH:
continue
inst = str(row[CODE_ROW_INST])
if _is_code_label(inst):
continue
pc = Pc(address=int(row[CODE_ROW_VADDR]), code_object_id=int(row[CODE_ROW_CODE_OBJECT]))
entries.append(
CodeEntry(
pc=pc,
inst=inst,
line_number=int(row[CODE_ROW_LINE]),
source=str(row[CODE_ROW_SOURCE]),
hitcount=int(row[CODE_ROW_HIT] or 0) if load_counts else 0,
latency=int(row[CODE_ROW_LATENCY] or 0) if load_counts else 0,
stall=int(row[CODE_ROW_STALL] or 0) if load_counts else 0,
idle=int(row[CODE_ROW_IDLE] or 0) if load_counts else 0,
)
)
return cls(entries, document)
@classmethod
def from_stats_csv(cls, paths: Iterable[str | Path]) -> "CodeIndex":
entries: dict[Pc, CodeEntry] = {}
for path in paths:
with Path(path).open("r", newline="") as fh:
reader = csv.DictReader(fh)
for row in reader:
if not row:
continue
pc = Pc(
address=int(row[STATS_COL_VADDR].strip(), 0),
code_object_id=int(row[STATS_COL_CODE_OBJECT].strip(), 0),
)
inst = row[STATS_COL_INSTRUCTION].strip()
if _is_code_label(inst):
continue
entry = entries.setdefault(
pc,
CodeEntry(
pc=pc,
inst=inst,
line_number=len(entries) + 1,
source=row.get(STATS_COL_SOURCE, ""),
),
)
entry.expected_hitcount += _int_cell(row.get(STATS_COL_HITCOUNT))
entry.expected_latency += _int_cell(row.get(STATS_COL_LATENCY))
entry.expected_stall += _int_cell(row.get(STATS_COL_STALL))
entry.expected_idle += _int_cell(row.get(STATS_COL_IDLE))
return cls(entries.values())
def isa_for_pc(self, pc: Pc) -> tuple[str, int] | None:
entry = self.entries.get(pc)
if entry is None:
return None
self._ensure_memory_sizes()
return entry.inst, entry.memory_size
def get_or_create(self, pc: Pc) -> CodeEntry:
entry = self.entries.get(pc)
if entry is not None:
return entry
entry = CodeEntry(
pc=pc,
inst="",
line_number=self._next_line_number,
)
self._next_line_number += 1
self.entries[pc] = entry
self.line_numbers[pc] = entry.line_number
self._mark_memory_sizes_dirty()
return entry
def line_number(self, pc: Pc) -> int:
"""Return the source line number for *pc*.
Raises KeyError if *pc* is not present in the index.
"""
return self.entries[pc].line_number
def accumulate_wave(self, wave: Wave) -> None:
prev_inst_time = wave.begin_time
for inst in wave.instructions:
if inst.pc.code_object_id == 0 and inst.pc.address == 0:
continue
entry = self.get_or_create(inst.pc)
entry.hitcount += 1
entry.latency += inst.duration
entry.stall += inst.stall
entry.idle += max(inst.time - prev_inst_time, 0)
prev_inst_time = max(prev_inst_time, inst.time + inst.duration)
def write_code_json(self, path: str | Path) -> None:
out_doc = dict(self.document)
rows = []
seen: set[Pc] = set()
for row in self.document.get("code", []):
if len(row) < CODE_ROW_WIDTH or _is_code_label(str(row[CODE_ROW_INST])):
rows.append(row)
continue
pc = _pc_from_code_row(row)
seen.add(pc)
entry = self.entries.get(pc)
if entry is None:
rows.append(row)
continue
updated = list(row)
_set_code_row_counts(updated, entry)
rows.append(updated)
for entry in sorted(
(entry for pc, entry in self.entries.items() if pc not in seen),
key=lambda e: e.line_number,
):
rows.append(_code_row(entry))
out_doc["code"] = rows
out_doc.setdefault("header", CODE_HEADER)
Path(path).write_text(json.dumps(out_doc, indent=2))
def write_stats_csv(self, path: str | Path) -> None:
with Path(path).open("w", newline="") as fh:
writer = csv.writer(fh)
writer.writerow(STATS_CSV_HEADER)
seen: set[Pc] = set()
for row in self.document.get("code", []):
if len(row) < CODE_ROW_WIDTH:
continue
pc = _pc_from_code_row(row)
if _is_code_label(str(row[CODE_ROW_INST])):
writer.writerow(
[
pc.code_object_id,
pc.address,
row[CODE_ROW_INST],
0,
0,
0,
0,
row[CODE_ROW_SOURCE],
]
)
continue
entry = self.entries.get(pc)
if entry is not None:
_write_stats_row(writer, entry)
seen.add(pc)
for entry in sorted(
(entry for pc, entry in self.entries.items() if pc not in seen),
key=lambda e: (e.pc.code_object_id, e.pc.address),
):
_write_stats_row(writer, entry)
def validate_expected(self) -> list[str]:
errors = []
for entry in sorted(
self.entries.values(),
key=lambda e: (e.pc.code_object_id, e.pc.address),
):
if (
entry.hitcount == entry.expected_hitcount
and entry.latency == entry.expected_latency
and entry.stall == entry.expected_stall
and entry.idle == entry.expected_idle
):
continue
errors.append(
f"{entry.inst} - PC: {entry.pc.code_object_id},{entry.pc.address}\n"
f"Hitcount: {entry.hitcount}/{entry.expected_hitcount}, "
f"Stall: {entry.stall}/{entry.expected_stall}, "
f"Latency: {entry.latency}/{entry.expected_latency}, "
f"Idle: {entry.idle}/{entry.expected_idle}"
)
return errors
def _mark_memory_sizes_dirty(self) -> None:
self._memory_sizes_dirty = True
def _ensure_memory_sizes(self) -> None:
if self._memory_sizes_dirty:
self._set_memory_sizes()
self._memory_sizes_dirty = False
def _set_memory_sizes(self) -> None:
by_codeobj: dict[int, list[CodeEntry]] = {}
for entry in self.entries.values():
by_codeobj.setdefault(entry.pc.code_object_id, []).append(entry)
for entries in by_codeobj.values():
entries.sort(key=lambda e: e.pc.address)
for idx, entry in enumerate(entries):
if idx + 1 < len(entries):
delta = entries[idx + 1].pc.address - entry.pc.address
entry.memory_size = max(delta, 4)
else:
entry.memory_size = 4
def _int_cell(value: str | None) -> int:
value = (value or "").strip()
return int(value, 0) if value else 0
def _is_code_label(inst: str) -> bool:
return inst.startswith(";") or inst.startswith("label")
def _pc_from_code_row(row: list) -> Pc:
return Pc(
address=int(row[CODE_ROW_VADDR]),
code_object_id=int(row[CODE_ROW_CODE_OBJECT]),
)
def _set_code_row_counts(row: list, entry: CodeEntry) -> None:
row[CODE_ROW_HIT] = entry.hitcount
row[CODE_ROW_LATENCY] = entry.latency
row[CODE_ROW_STALL] = entry.stall
row[CODE_ROW_IDLE] = entry.idle
def _code_row(entry: CodeEntry) -> list[object]:
row: list[object] = [0] * CODE_ROW_WIDTH
row[CODE_ROW_INST] = entry.inst
row[CODE_ROW_LINE] = entry.line_number
row[CODE_ROW_SOURCE] = entry.source
row[CODE_ROW_CODE_OBJECT] = entry.pc.code_object_id
row[CODE_ROW_VADDR] = entry.pc.address
_set_code_row_counts(row, entry)
return row
def _clear_document_counts(document: dict) -> None:
for row in document.get("code", []):
if len(row) >= CODE_ROW_WIDTH:
for column in CODE_ROW_COUNTERS:
row[column] = 0
def _write_stats_row(writer: object, entry: CodeEntry) -> None:
writer.writerow(
[
entry.pc.code_object_id,
entry.pc.address,
entry.inst,
entry.hitcount,
entry.latency,
entry.stall,
entry.idle,
entry.source,
]
)