"""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''] out.append(f'' 'backend-http-ready per launch (ms) - lower is better') # 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'') out.append(f'{yv/1000:.0f}s') 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'') out.append(f'{v/1000:.0f}s') out.append(f'{r["version"]}') out.append(f'' f'warm') out.append(f'' f'cold (post-update)') out.append('') 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''] out.append(f'' 'where startup time goes (backend dwarfs the shell)') row_h = 46 for i, (label, al, fp, br) in enumerate(cases): y = pad_t + i * (row_h + 26) out.append(f'{label}') # backend is the full bar; app-launch+first-paint are the tiny left slice bw_backend = plot_w * br / vmax out.append(f'') shell = al + fp bw_shell = plot_w * shell / vmax out.append(f'') out.append(f'' f'backend {br/1000:.1f}s (shell {shell/1000:.2f}s)') out.append(f'' 'dark = electron shell (app-launch + first-paint); colored = python backend') out.append('') 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''] out.append(f'' 'warm boot breakdown: before vs after (ms) - the service lifespan was the bottleneck') for frac in (0, 0.5, 1.0): y = pad_t + plot_h - plot_h * frac out.append(f'') out.append(f'{vmax*frac/1000:.1f}s') 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'') out.append(f'{v/1000:.1f}s') out.append(f'{r["phase"]}') out.append(f'before') out.append(f'after') out.append('') 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''] out.append(f'' 'App Builder "create app -> live preview" breakdown (ms)') 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'{r["phase"]}') out.append(f'') label = f'{v/1000:.2f}s' if v >= 1000 else f'{v}ms' out.append(f'{label}') out.append(f'' 'green = warm/per-app cost; red = cold one-time download (npm with no archive)') out.append('') 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''] out.append(f'' 'signed v1.3.87 startup: before vs after (seconds, lower is better)') for frac in (0, 0.5, 1.0): y = pad_t + plot_h - plot_h * frac out.append(f'') out.append(f'{vmax*frac/1000:.0f}s') 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'') out.append(f'{v/1000:.1f}s') out.append(f'{r["metric"]}') out.append(f'before (1.2.x)') out.append(f'v1.3.87') out.append('') 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()