Opens a larger view. Escape closes it.

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")