hardware-counters
parse_persets.py
#!/usr/bin/env python3
"""
Parse each CrayPat counter set as an INDEPENDENT sample.
The original parser merged the 5 counter sets of a configuration into one row,
which discarded 4/5 of the measurements. Each set is in fact a separate
execution of the same binary and configuration, with its own `Thread Time` and
(for sets A, B, D, E) its own PAPI_TOT_CYC. Treating them separately yields
~190 samples instead of 40, which is the single largest available improvement
to the training set and needs no extra compute time.
Set C measures the mem_bw group and does not include PAPI_TOT_CYC; those rows
carry prefetch/bandwidth counters and a Thread Time but no cycle count, so
they are kept and flagged rather than dropped.
Output: runs_persets.csv, one row per (app, ncore, set).
"""
import os, re, subprocess, csv, sys
from pathlib import Path
PR = "/opt/cray/pe/perftools/23.09.0/bin/pat_report"
# Data and output roots. Overridable so the pipeline can be relocated
# (a different account, a scratch copy, another site) without editing code;
# the default keeps existing invocations working unchanged.
D = os.environ.get("DISS_ROOT", "/work/project/project/user")
BASE = Path(f"{D}/runs/sweep")
OUT = Path(f"{D}/data")
VAL = re.compile(r"^ (?P<n>[A-Z][A-Za-z0-9_:]*)\s+"
r"(?:(?:[\d.]+)\s*(?:[GMK]/sec|W)\s+)?"
r"(?P<v>\d[\d,]*(?:\.\d+)?)\s*"
r"(?:ops|instr|cycles|J|refs|misses|hits)?\s*$")
TIME = re.compile(r"^ Thread Time\s+([\d.]+)\s+secs")
def parse(exp):
out = subprocess.run([PR, "-O", "hwpc", str(exp)],
capture_output=True, text=True, timeout=600).stdout
vals, t = {}, None
for line in out.splitlines():
if "PAT_RT_PERFCTR" in line:
break # legend section follows
mt = TIME.match(line)
if mt:
t = float(mt.group(1))
continue
m = VAL.match(line)
if m and m.group("n") not in ("Thread", "Total"):
try:
vals[m.group("n")] = float(m.group("v").replace(",", ""))
except ValueError:
pass
return t, vals
rows = []
for d in sorted(BASE.glob("*_c*")):
if not d.is_dir():
continue
m = re.match(r"(\w+)_c(\d+)$", d.name)
if not m:
continue
app, nc = m.group(1), int(m.group(2))
if app == "cp2k":
continue
for s in "ABCDE":
exp = d / f"exp_{s}"
if not exp.exists():
continue
t, vals = parse(exp)
if t is None or not vals:
continue
rows.append({"app": app, "ncore": nc, "cset": s, "runtime_s": t, **vals})
print(f"{app:9s} c{nc:<4d} set {s} t={t:8.2f}s {len(vals)} counters",
file=sys.stderr)
keys = ["app", "ncore", "cset", "runtime_s"]
keys += sorted({k for r in rows for k in r} - set(keys))
OUT.mkdir(parents=True, exist_ok=True)
with open(OUT / "runs_persets.csv", "w", newline="") as fh:
w = csv.DictWriter(fh, fieldnames=keys)
w.writeheader()
w.writerows(rows)
print(f"\nwrote runs_persets.csv: {len(rows)} rows x {len(keys)} cols")