import csv
import sys
from collections import defaultdict
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
csv_path = sys.argv[1] if len(sys.argv) > 1 else "pibench-sweep.csv"
out_path = sys.argv[2] if len(sys.argv) > 2 else "pibench-sweep.png"
data = defaultdict(list)
with open(csv_path) as f:
for row in csv.DictReader(f):
try:
pps = float(row["pps"])
except (ValueError, KeyError):
continue
drops = row["drops"]
drops = float(drops) if drops not in ("n/a", "NA", "") else None
data[row["backend"]].append((
pps,
float(row["proc_cpu_pct"]),
float(row["cpu_us_frame"]),
drops,
))
STYLE = {
"ring": ("#1b9e77", "o", "ferranet ring (zero-copy mmap, V3)"),
"basic": ("#d95f02", "s", "ferranet basic (recvfrom)"),
"pnet": ("#7570b3", "^", "libpnet (recvfrom)"),
"pcap": ("#e7298a", "D", "libpcap (PACKET_MMAP)"),
}
order = [b for b in ("ring", "basic", "pnet", "pcap") if b in data]
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(16, 5))
for be in order:
pts = sorted(data[be])
color, marker, label = STYLE[be]
xs = [p[0] / 1000 for p in pts] ax1.plot(xs, [p[1] for p in pts], marker=marker, color=color, label=label)
ax2.plot(xs, [p[2] for p in pts], marker=marker, color=color, label=label)
dx = [p[0] / 1000 for p in pts if p[3] is not None]
dy = [p[3] for p in pts if p[3] is not None]
if dx:
ax3.plot(dx, dy, marker=marker, color=color, label=label)
ax1.set(title="Capture-process CPU vs offered pps",
xlabel="received packets/sec (kpps)", ylabel="proc_cpu (% of one core)")
ax2.set(title="Per-frame CPU cost vs offered pps",
xlabel="received packets/sec (kpps)", ylabel="CPU µs / frame")
ax3.set(title="Kernel drops vs offered pps",
xlabel="received packets/sec (kpps)", ylabel="drops in window")
for ax in (ax1, ax2, ax3):
ax.grid(True, alpha=0.3)
ax.legend()
ax.set_ylim(bottom=0)
fig.suptitle("ferranet capture backends on Raspberry Pi Zero 2 W\n"
"(direct 100M link saturated; pps swept via frame size 1472B→18B, reps averaged)",
fontsize=12)
fig.tight_layout(rect=(0, 0, 1, 0.96))
fig.savefig(out_path, dpi=120)
print(f"wrote {out_path}")