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
176 lines
5.8 KiB
Python
Executable File
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()
|