Files
openswarm/docs/perf/winv2/make_graphs.py
T
Eric 9f30c58d28 [eric] perf: task #10 results verified on signed v1.3.87 build
- signed installer (authenticode valid), 371MB @ 23.5MB/s; install ~9s

- cold backend-ready 54-138s -> 22.5s; warm 9-10s -> 5.0s (under 10s goal)

- asar 607MB -> 2.1MB; skills catalog live total=17 (bug #1 confirmed); validate 5/5 pass

- add before/after startup graph
2026-06-17 00:08:26 -07:00

227 lines
12 KiB
Python

"""Dependency-free SVG charts for the winv2 perf baseline.
No matplotlib/pandas (not in the bundled env). Reads baseline_startup.csv and
writes two self-contained SVGs that render in a browser, GitHub, or Notion:
baseline_startup.svg - backend-http-ready per launch (warm vs cold)
baseline_phases.svg - where the time goes (app-launch / first-paint / backend)
Run: python make_graphs.py
"""
import csv
import os
HERE = os.path.dirname(os.path.abspath(__file__))
CSV = os.path.join(HERE, "baseline_startup.csv")
WARM = "#2e9e5b"
COLD = "#d64545"
INK = "#1a1d27"
MUTE = "#8892a4"
GRID = "#e2e6ef"
def rows():
with open(CSV, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def bars_chart(data):
w, h = 900, 420
pad_l, pad_b, pad_t, pad_r = 60, 90, 50, 20
plot_w = w - pad_l - pad_r
plot_h = h - pad_t - pad_b
vals = [int(r["backend_http_ready_ms"]) for r in data]
vmax = max(vals)
n = len(data)
bw = plot_w / n * 0.7
gap = plot_w / n
out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" font-family="Segoe UI, sans-serif">']
out.append(f'<text x="{pad_l}" y="28" font-size="17" font-weight="600" fill="{INK}">'
'backend-http-ready per launch (ms) - lower is better</text>')
# y gridlines
for frac in (0, 0.25, 0.5, 0.75, 1.0):
yv = vmax * frac
y = pad_t + plot_h - plot_h * frac
out.append(f'<line x1="{pad_l}" y1="{y:.0f}" x2="{w-pad_r}" y2="{y:.0f}" stroke="{GRID}"/>')
out.append(f'<text x="{pad_l-8}" y="{y+4:.0f}" font-size="11" fill="{MUTE}" text-anchor="end">{yv/1000:.0f}s</text>')
for i, r in enumerate(data):
v = int(r["backend_http_ready_ms"])
bh = plot_h * v / vmax
x = pad_l + i * gap + (gap - bw) / 2
y = pad_t + plot_h - bh
color = COLD if r["class"] == "cold" else WARM
out.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{bw:.1f}" height="{bh:.1f}" fill="{color}" rx="2"/>')
out.append(f'<text x="{x+bw/2:.1f}" y="{y-5:.1f}" font-size="10" fill="{INK}" text-anchor="middle">{v/1000:.0f}s</text>')
out.append(f'<text x="{x+bw/2:.1f}" y="{h-pad_b+16:.0f}" font-size="9" fill="{MUTE}" '
f'text-anchor="end" transform="rotate(-40 {x+bw/2:.1f} {h-pad_b+16:.0f})">{r["version"]}</text>')
out.append(f'<rect x="{w-200}" y="{pad_t}" width="12" height="12" fill="{WARM}"/>'
f'<text x="{w-184}" y="{pad_t+11}" font-size="12" fill="{INK}">warm</text>')
out.append(f'<rect x="{w-130}" y="{pad_t}" width="12" height="12" fill="{COLD}"/>'
f'<text x="{w-114}" y="{pad_t+11}" font-size="12" fill="{INK}">cold (post-update)</text>')
out.append('</svg>')
return "\n".join(out)
def phases_chart(data):
warm = [r for r in data if r["class"] == "warm"]
cold = [r for r in data if r["class"] == "cold"]
def med(rows_, key):
xs = sorted(int(r[key]) for r in rows_)
return xs[len(xs) // 2] if xs else 0
cases = [
("typical warm launch", med(warm, "app_launch_ms"), med(warm, "first_paint_ms"), med(warm, "backend_http_ready_ms")),
("typical cold launch", med(cold, "app_launch_ms"), med(cold, "first_paint_ms"), med(cold, "backend_http_ready_ms")),
]
w, h = 900, 260
pad_l, pad_r, pad_t = 170, 30, 50
plot_w = w - pad_l - pad_r
vmax = max(c[3] for c in cases)
out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" font-family="Segoe UI, sans-serif">']
out.append(f'<text x="20" y="28" font-size="17" font-weight="600" fill="{INK}">'
'where startup time goes (backend dwarfs the shell)</text>')
row_h = 46
for i, (label, al, fp, br) in enumerate(cases):
y = pad_t + i * (row_h + 26)
out.append(f'<text x="20" y="{y+row_h/2+4:.0f}" font-size="13" fill="{INK}">{label}</text>')
# backend is the full bar; app-launch+first-paint are the tiny left slice
bw_backend = plot_w * br / vmax
out.append(f'<rect x="{pad_l}" y="{y}" width="{bw_backend:.1f}" height="{row_h}" fill="{COLD if i==1 else WARM}" rx="3"/>')
shell = al + fp
bw_shell = plot_w * shell / vmax
out.append(f'<rect x="{pad_l}" y="{y}" width="{max(bw_shell,2):.1f}" height="{row_h}" fill="{INK}" rx="3"/>')
out.append(f'<text x="{pad_l+bw_backend+8:.0f}" y="{y+row_h/2+4:.0f}" font-size="12" fill="{INK}">'
f'backend {br/1000:.1f}s (shell {shell/1000:.2f}s)</text>')
out.append(f'<text x="20" y="{h-12}" font-size="11" fill="{MUTE}">'
'dark = electron shell (app-launch + first-paint); colored = python backend</text>')
out.append('</svg>')
return "\n".join(out)
def boot_chart():
"""Before/after grouped bars for the boot-phase breakdown (profile_boot.py)."""
path = os.path.join(HERE, "boot_breakdown.csv")
with open(path, newline="", encoding="utf-8") as f:
data = list(csv.DictReader(f))
w, h = 900, 360
pad_l, pad_r, pad_t, pad_b = 60, 30, 50, 120
plot_w = w - pad_l - pad_r
plot_h = h - pad_t - pad_b
vmax = max(max(int(r["before_ms"]), int(r["after_ms"])) for r in data)
n = len(data)
group = plot_w / n
bw = group * 0.34
out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" font-family="Segoe UI, sans-serif">']
out.append(f'<text x="{pad_l}" y="28" font-size="17" font-weight="600" fill="{INK}">'
'warm boot breakdown: before vs after (ms) - the service lifespan was the bottleneck</text>')
for frac in (0, 0.5, 1.0):
y = pad_t + plot_h - plot_h * frac
out.append(f'<line x1="{pad_l}" y1="{y:.0f}" x2="{w-pad_r}" y2="{y:.0f}" stroke="{GRID}"/>')
out.append(f'<text x="{pad_l-8}" y="{y+4:.0f}" font-size="11" fill="{MUTE}" text-anchor="end">{vmax*frac/1000:.1f}s</text>')
for i, r in enumerate(data):
bx = pad_l + i * group + group / 2
for j, (key, color, lab) in enumerate((("before_ms", COLD, "before"), ("after_ms", WARM, "after"))):
v = int(r[key])
bh = plot_h * v / vmax
x = bx + (j - 1) * bw - bw * 0.05
y = pad_t + plot_h - bh
out.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{bw:.1f}" height="{bh:.1f}" fill="{color}" rx="2"/>')
out.append(f'<text x="{x+bw/2:.1f}" y="{y-4:.1f}" font-size="10" fill="{INK}" text-anchor="middle">{v/1000:.1f}s</text>')
out.append(f'<text x="{bx:.1f}" y="{h-pad_b+18:.0f}" font-size="10" fill="{MUTE}" text-anchor="end" '
f'transform="rotate(-25 {bx:.1f} {h-pad_b+18:.0f})">{r["phase"]}</text>')
out.append(f'<rect x="{w-200}" y="{pad_t}" width="12" height="12" fill="{COLD}"/><text x="{w-184}" y="{pad_t+11}" font-size="12" fill="{INK}">before</text>')
out.append(f'<rect x="{w-120}" y="{pad_t}" width="12" height="12" fill="{WARM}"/><text x="{w-104}" y="{pad_t+11}" font-size="12" fill="{INK}">after</text>')
out.append('</svg>')
return "\n".join(out)
def appbuilder_chart():
"""Horizontal bars for the App Builder create-path breakdown. Returns None
if the measurement CSV hasn't been generated yet."""
path = os.path.join(HERE, "appbuilder_breakdown.csv")
if not os.path.exists(path):
return None
with open(path, newline="", encoding="utf-8") as f:
raw = list(csv.DictReader(f))
# Keep only real timing phases (drop the boolean/skipped/-1 rows).
data = [r for r in raw if r["ms"].lstrip("-").isdigit() and int(r["ms"]) >= 0
and not r["phase"].strip().startswith("->")]
if not data:
return None
w = 980
row_h, gap, pad_t, pad_l, pad_r = 30, 14, 56, 320, 90
h = pad_t + len(data) * (row_h + gap) + 30
vmax = max(int(r["ms"]) for r in data) or 1
plot_w = w - pad_l - pad_r
out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" font-family="Segoe UI, sans-serif">']
out.append(f'<text x="20" y="30" font-size="17" font-weight="600" fill="{INK}">'
'App Builder "create app -> live preview" breakdown (ms)</text>')
for i, r in enumerate(data):
v = int(r["ms"])
y = pad_t + i * (row_h + gap)
bw = max(plot_w * v / vmax, 1)
# download/npm = cold cost (red-ish), everything else = warm/per-app (green)
cold = ("npm" in r["phase"]) or ("cold" in r["phase"])
color = COLD if cold else WARM
out.append(f'<text x="{pad_l-10}" y="{y+row_h*0.68:.0f}" font-size="12" fill="{INK}" text-anchor="end">{r["phase"]}</text>')
out.append(f'<rect x="{pad_l}" y="{y}" width="{bw:.1f}" height="{row_h}" fill="{color}" rx="3"/>')
label = f'{v/1000:.2f}s' if v >= 1000 else f'{v}ms'
out.append(f'<text x="{pad_l+bw+8:.0f}" y="{y+row_h*0.68:.0f}" font-size="12" fill="{INK}">{label}</text>')
out.append(f'<text x="20" y="{h-10}" font-size="11" fill="{MUTE}">'
'green = warm/per-app cost; red = cold one-time download (npm with no archive)</text>')
out.append('</svg>')
return "\n".join(out)
def startup_beforeafter_chart():
"""Before/after grouped bars for the signed-build startup result (Task #10)."""
path = os.path.join(HERE, "startup_beforeafter.csv")
if not os.path.exists(path):
return None
with open(path, newline="", encoding="utf-8") as f:
data = list(csv.DictReader(f))
w, h = 900, 320
pad_l, pad_r, pad_t, pad_b = 60, 30, 56, 90
plot_w, plot_h = w - pad_l - pad_r, h - pad_t - pad_b
vmax = max(max(int(r["before_ms"]), int(r["after_ms"])) for r in data)
n = len(data); group = plot_w / n; bw = group * 0.30
out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" font-family="Segoe UI, sans-serif">']
out.append(f'<text x="{pad_l}" y="30" font-size="17" font-weight="600" fill="{INK}">'
'signed v1.3.87 startup: before vs after (seconds, lower is better)</text>')
for frac in (0, 0.5, 1.0):
y = pad_t + plot_h - plot_h * frac
out.append(f'<line x1="{pad_l}" y1="{y:.0f}" x2="{w-pad_r}" y2="{y:.0f}" stroke="{GRID}"/>')
out.append(f'<text x="{pad_l-8}" y="{y+4:.0f}" font-size="11" fill="{MUTE}" text-anchor="end">{vmax*frac/1000:.0f}s</text>')
for i, r in enumerate(data):
bx = pad_l + i * group + group / 2
for j, (k, c, lab) in enumerate((("before_ms", COLD, "before"), ("after_ms", WARM, "after"))):
v = int(r[k]); bh = plot_h * v / vmax; x = bx + (j - 1) * bw - bw * 0.05; y = pad_t + plot_h - bh
out.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{bw:.1f}" height="{bh:.1f}" fill="{c}" rx="2"/>')
out.append(f'<text x="{x+bw/2:.1f}" y="{y-4:.1f}" font-size="11" fill="{INK}" text-anchor="middle">{v/1000:.1f}s</text>')
out.append(f'<text x="{bx:.1f}" y="{h-pad_b+22:.0f}" font-size="11" fill="{MUTE}" text-anchor="middle">{r["metric"]}</text>')
out.append(f'<rect x="{w-200}" y="{pad_t}" width="12" height="12" fill="{COLD}"/><text x="{w-184}" y="{pad_t+11}" font-size="12" fill="{INK}">before (1.2.x)</text>')
out.append(f'<rect x="{w-95}" y="{pad_t}" width="12" height="12" fill="{WARM}"/><text x="{w-79}" y="{pad_t+11}" font-size="12" fill="{INK}">v1.3.87</text>')
out.append('</svg>')
return "\n".join(out)
def main():
data = rows()
open(os.path.join(HERE, "baseline_startup.svg"), "w", encoding="utf-8").write(bars_chart(data))
open(os.path.join(HERE, "baseline_phases.svg"), "w", encoding="utf-8").write(phases_chart(data))
open(os.path.join(HERE, "boot_breakdown.svg"), "w", encoding="utf-8").write(boot_chart())
wrote = "baseline_startup.svg + baseline_phases.svg + boot_breakdown.svg"
ab = appbuilder_chart()
if ab:
open(os.path.join(HERE, "appbuilder_breakdown.svg"), "w", encoding="utf-8").write(ab)
wrote += " + appbuilder_breakdown.svg"
sba = startup_beforeafter_chart()
if sba:
open(os.path.join(HERE, "startup_beforeafter.svg"), "w", encoding="utf-8").write(sba)
wrote += " + startup_beforeafter.svg"
print("wrote " + wrote)
if __name__ == "__main__":
main()