"""Re-render pe2pe latency PNGs from the existing summary.csv with the current (no-consume) labels. Used after a label-only test edit to avoid re-measuring (~5 min) when the data on disk is already correct. Reads docs/diagrams/pe2pe_latency_plots/summary.csv. Plots 2 curves: "IPCQ no-consume" (from the ipcq_no_consume rows if present, else from the ipcq rows) and "Raw DMA" (raw rows). """ from __future__ import annotations import csv from pathlib import Path import matplotlib.pyplot as plt ROOT = Path(__file__).resolve().parent.parent PLOT_DIR = ROOT / "docs" / "diagrams" / "pe2pe_latency_plots" CSV_PATH = PLOT_DIR / "summary.csv" def _load_records(): rows = [] with open(CSV_PATH, newline="") as f: for r in csv.DictReader(f): rows.append({ "hop": r["hop"], "label": r["label"], "size_bytes": int(r["size_bytes"]), "path": r["path"], "total_ns": float(r["total_ns"]), }) return rows def _ipcq_rows(records, hop): # Prefer ipcq_no_consume if present (older 3-path CSV); fall back to ipcq # (current single-path CSV where ipcq IS no-consume). nc = [r for r in records if r["hop"] == hop and r["path"] == "ipcq_no_consume"] if nc: return sorted(nc, key=lambda r: r["size_bytes"]) return sorted( [r for r in records if r["hop"] == hop and r["path"] == "ipcq"], key=lambda r: r["size_bytes"], ) def _raw_rows(records, hop): return sorted( [r for r in records if r["hop"] == hop and r["path"] == "raw"], key=lambda r: r["size_bytes"], ) def _hops(records): seen = [] for r in records: if r["hop"] not in {h["id"] for h in seen}: seen.append({"id": r["hop"], "label": r["label"]}) return seen def _plot_per_hop(records, hop, path): ipcq = _ipcq_rows(records, hop["id"]) raw = _raw_rows(records, hop["id"]) fig, ax = plt.subplots(figsize=(8, 5)) if ipcq: ax.plot( [r["size_bytes"] for r in ipcq], [r["total_ns"] for r in ipcq], marker="o", color="tab:blue", label="IPCQ no-consume (send/recv, no slot read)", ) if raw: ax.plot( [r["size_bytes"] for r in raw], [r["total_ns"] for r in raw], marker="s", color="tab:orange", label="Raw DMA (load+store)", ) ax.set_xlabel("Data size (bytes)") ax.set_ylabel("Latency (ns)") ax.set_title(hop["label"]) ax.grid(True, alpha=0.3) ax.legend() fig.tight_layout() fig.savefig(path, dpi=120) plt.close(fig) def _plot_overview(records, hops, path): fig, axes = plt.subplots(2, 2, figsize=(13, 9)) axes = axes.flatten() for i, hop in enumerate(hops): ax = axes[i] ipcq = _ipcq_rows(records, hop["id"]) raw = _raw_rows(records, hop["id"]) if ipcq: ax.plot( [r["size_bytes"] for r in ipcq], [r["total_ns"] for r in ipcq], marker="o", color="tab:blue", label="IPCQ no-consume", ) if raw: ax.plot( [r["size_bytes"] for r in raw], [r["total_ns"] for r in raw], marker="s", color="tab:orange", label="Raw DMA", ) ax.set_title(hop["label"], fontsize=10) ax.set_xlabel("bytes") ax.set_ylabel("ns") ax.grid(True, alpha=0.3) ax.legend(fontsize=8) for j in range(len(hops), len(axes)): axes[j].axis("off") fig.suptitle( "PE-to-PE latency: IPCQ no-consume vs raw DMA", fontsize=14, ) fig.tight_layout() fig.savefig(path, dpi=120) plt.close(fig) def main(): records = _load_records() hops = _hops(records) for hop in hops: out = PLOT_DIR / f"{hop['id']}.png" _plot_per_hop(records, hop, out) print(f"wrote {out}") overview = PLOT_DIR / "overview.png" _plot_overview(records, hops, overview) print(f"wrote {overview}") if __name__ == "__main__": main()