Opens a larger view. Escape closes it.

hardware-counters

parse_expand.py

#!/usr/bin/env python3
"""
Parse the expanded sweep into a per-counter-set dataset.

Differences from parse_persets.py:
  * directory names now encode problem size and thread count
    (app_c<cores>_s<size>_t<threads>), so those become real columns rather
    than being constant per application;
  * merges in the original campaign (runs/sweep) so nothing is lost, tagging
    those rows with their known fixed sizes.

Each counter set is an independent execution with its own Thread Time, so it
contributes one row.
"""
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")
ROOT  = Path(f"{D}/runs")
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")

# problem sizes used in the ORIGINAL campaign (fixed per application)
ORIG_SIZE = {"stream": 80000000, "hpcg": 32, "hpl": 0,
             "gromacs": 0, "openfoam": 100}


def parse(exp):
    try:
        out = subprocess.run([PR, "-O", "hwpc", str(exp)],
                             capture_output=True, text=True, timeout=600).stdout
    except Exception:
        return None, {}
    vals, t = {}, None
    for line in out.splitlines():
        if "PAT_RT_PERFCTR" in line:
            break
        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 = []

def harvest(d, app, ncore, size, nthread, campaign):
    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": ncore, "size": size,
                     "nthread": nthread, "nrank": ncore // max(nthread, 1),
                     "cset": s, "campaign": campaign,
                     "runtime_s": t, **vals})


# ---- expanded campaign ---------------------------------------------------
for d in sorted((ROOT / "expand").glob("*_c*_s*_t*")):
    m = re.match(r"([a-z]+)_c(\d+)_s(\d+)_t(\d+)$", d.name)
    if not m or not d.is_dir():
        continue
    harvest(d, m.group(1), int(m.group(2)), int(m.group(3)),
            int(m.group(4)), "expand")
    print(f"expand  {d.name}", file=sys.stderr)

# ---- original campaign ---------------------------------------------------
for d in sorted((ROOT / "sweep").glob("*_c*")):
    m = re.match(r"([a-z0-9]+)_c(\d+)$", d.name)
    if not m or not d.is_dir():
        continue
    app, nc = m.group(1), int(m.group(2))
    if app == "cp2k":
        continue
    harvest(d, app, nc, ORIG_SIZE.get(app, 0), 1, "orig")
    print(f"orig    {d.name}", file=sys.stderr)

lead = ["app", "campaign", "ncore", "nthread", "nrank", "size", "cset", "runtime_s"]
keys = lead + sorted({k for r in rows for k in r} - set(lead))
OUT.mkdir(parents=True, exist_ok=True)
with open(OUT / "runs_expanded.csv", "w", newline="") as fh:
    w = csv.DictWriter(fh, fieldnames=keys)
    w.writeheader(); w.writerows(rows)

print(f"\nwrote runs_expanded.csv: {len(rows)} rows x {len(keys)} cols")
from collections import Counter
print("by app:", dict(Counter(r["app"] for r in rows)))