Files
TheRock/rocm-systems/projects/rdc/tools/dme_rdc_metric_sync_check.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

176 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""
DME-RDC Metric Sync Check
Compares metrics exposed by AMD Device Metrics Exporter (DME) against RDC
field definitions to detect gaps. Exits non-zero if unmapped DME metrics
are found.
Usage:
python3 dme_rdc_metric_sync_check.py \
--rdc-header projects/rdc/include/rdc/rdc.h \
--mapping projects/rdc/tools/dme_rdc_metric_mapping.json \
[--dme-path path/to/device-metrics-exporter] \
[--output report.json]
"""
import argparse
import json
import re
import sys
from pathlib import Path
def parse_rdc_fields(header_path: Path) -> set[str]:
"""Extract all RDC_FI_*, RDC_EVNT_*, and RDC_HEALTH_* enum names from rdc.h."""
fields = set()
pattern = re.compile(r"\b(RDC_FI_\w+|RDC_EVNT_\w+|RDC_HEALTH_\w+)\b")
text = header_path.read_text()
for match in pattern.finditer(text):
name = match.group(1)
# Skip sentinel/marker values
if name in (
"RDC_FI_INVALID",
"RDC_FI_ECC_FIRST",
"RDC_FI_ECC_LAST",
"RDC_EVNT_NOTIF_FIRST",
"RDC_EVNT_NOTIF_LAST",
"RDC_FI_CPU_FIRST",
"RDC_FI_CPU_LAST",
):
continue
fields.add(name)
return fields
def parse_dme_metrics_from_go(dme_path: Path) -> set[str]:
"""Parse DME metric names from protobuf-generated Go source.
DME defines its actual metrics as GPUMetricField_* protobuf enum values
in exporterconfig.pb.go. This is the authoritative list — not arbitrary
GPU_*/PCIE_* strings which include enum types, constants, etc.
"""
metrics = set()
# Primary: parse GPUMetricField protobuf enum definitions
pb_pattern = re.compile(r"GPUMetricField_(GPU_\w+|PCIE_\w+)\s+GPUMetricField\s*=\s*\d+")
for go_file in dme_path.rglob("*.pb.go"):
text = go_file.read_text(errors="replace")
for match in pb_pattern.finditer(text):
metrics.add(match.group(1))
# Fallback: if no pb.go found, parse string map entries
if not metrics:
map_pattern = re.compile(r':\s*"(GPU_\w+|PCIE_\w+)"')
for go_file in dme_path.rglob("*.go"):
text = go_file.read_text(errors="replace")
for match in map_pattern.finditer(text):
metrics.add(match.group(1))
return metrics
def load_mapping(mapping_path: Path) -> list[dict]:
"""Load the curated DME-to-RDC mapping file."""
data = json.loads(mapping_path.read_text())
return data.get("mappings", [])
def run_check(
rdc_header: Path, mapping_path: Path, dme_path: Path | None, output_path: Path | None
) -> int:
"""Run the sync check and return exit code (0=ok, 1=gaps found)."""
rdc_fields = parse_rdc_fields(rdc_header)
mappings = load_mapping(mapping_path)
# Build sets from mapping
mapped_dme = {m["dme"] for m in mappings}
# Check for stale mappings (mapped to non-existent RDC field)
stale = []
for m in mappings:
if m["status"] == "mapped" and m.get("rdc"):
if m["rdc"] not in rdc_fields:
stale.append(
{"dme": m["dme"], "rdc": m["rdc"], "issue": "RDC field not found in header"}
)
# Check for known missing
known_missing = [m for m in mappings if m["status"] == "missing"]
# If DME source is available, check for new unmapped metrics
unmapped = []
if dme_path and dme_path.exists():
dme_metrics = parse_dme_metrics_from_go(dme_path)
for metric in sorted(dme_metrics):
if metric not in mapped_dme:
unmapped.append(metric)
# Summary
report = {
"rdc_field_count": len(rdc_fields),
"mapping_count": len(mappings),
"mapped": len([m for m in mappings if m["status"] == "mapped"]),
"skipped": len([m for m in mappings if m["status"] == "skipped"]),
"known_missing": [m["dme"] for m in known_missing],
"stale_mappings": stale,
"unmapped_dme_metrics": unmapped,
}
# Print summary
print(f"RDC fields in header: {report['rdc_field_count']}")
print(f"Mapping entries: {report['mapping_count']}")
print(f" Mapped: {report['mapped']}")
print(f" Skipped: {report['skipped']}")
print(f" Known missing: {len(report['known_missing'])}")
if stale:
print(f"\nSTALE MAPPINGS ({len(stale)}):")
for s in stale:
print(f" {s['dme']} -> {s['rdc']}: {s['issue']}")
if known_missing:
print(f"\nKNOWN MISSING ({len(known_missing)}):")
for m in known_missing:
reason = m.get("reason", "")
print(f" {m['dme']}: {reason}")
if unmapped:
print(f"\nNEW UNMAPPED DME METRICS ({len(unmapped)}):")
for m in unmapped:
print(f" {m}")
if output_path:
output_path.write_text(json.dumps(report, indent=2))
print(f"\nReport written to {output_path}")
if unmapped:
print("\nFAILED: New DME metrics found without mapping entries.")
return 1
if stale:
print("\nWARNING: Stale mappings found.")
return 1
print("\nPASSED: All DME metrics are tracked.")
return 0
def main():
parser = argparse.ArgumentParser(description="DME-RDC Metric Sync Check")
parser.add_argument(
"--rdc-header", required=True, type=Path, help="Path to projects/rdc/include/rdc/rdc.h"
)
parser.add_argument(
"--mapping", required=True, type=Path, help="Path to dme_rdc_metric_mapping.json"
)
parser.add_argument(
"--dme-path", type=Path, default=None, help="Path to cloned device-metrics-exporter repo"
)
parser.add_argument("--output", type=Path, default=None, help="Path to write JSON report")
args = parser.parse_args()
sys.exit(run_check(args.rdc_header, args.mapping, args.dme_path, args.output))
if __name__ == "__main__":
main()