java-topology/whitepaper/gumyum/gen-donuts.py
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

211 lines
9.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
gen-donuts.py — Generate SVG donut charts for CWE-407 speedup visualization.
One donut per ecosystem: javac, minecraft-server, minecraft-client.
Each donut ring = one defect. Arc proportional to fraction of BEFORE time.
Center shows peak speedup. Minimum-ink aesthetic: grays + one accent color.
Output: svg/donut-javac.svg, svg/donut-mc-server.svg, svg/donut-mc-client.svg
"""
import math
import os
OUT_DIR = os.path.join(os.path.dirname(__file__), "svg")
os.makedirs(OUT_DIR, exist_ok=True)
# ── Palette — print-friendly B&W (no color ink) ──────────────────────────────
BEFORE_COLOR = "#555555" # dark gray — defective arc (wasted time)
AFTER_COLOR = "#dddddd" # light gray — fixed arc (efficient portion)
GAP_COLOR = "white"
LABEL_COLOR = "#000000"
CENTER_TITLE = "#000000"
CENTER_SUB = "#444444"
BG = "white"
RING_BG = "white" # no background fill — saves ink
# ── Geometry ─────────────────────────────────────────────────────────────────
CX, CY = 180, 180 # center
RING_W = 28 # ring width per defect
RING_GAP = 5 # gap between rings
R_INNER_BASE = 62 # innermost ring inner radius
LABEL_X = 390 # x position for legend labels
SVG_W, SVG_H = 560, 380
# ── Data ─────────────────────────────────────────────────────────────────────
# speedup = before_time / after_time (real measured values)
# label_speedup = display string for the ring
JAVAC_DEFECTS = [
# (id, short_name, speedup_real, annotation)
("0001", "Tarjan stack.contains()", 5.46, "5.46× — V=800"),
("0002a","findNode ArrayList scan", 6.86, "6.9× — N=200"),
("0002b","closure uncached DFS", 20.91, "20.9× — V=100"),
("0003", "TopoSorter Deque.contains()",1.51,"1.5× — V=200"),
("0004", "DependencyList.add", 4.27, "4.3× — M=200"),
("0005", "BoundSet containsAll", 2.40, "2.4× — B=100"),
]
MC_SERVER_DEFECTS = [
("mc-0001","DependencySorter.isCyclic", 110.9, "110.9× — depth 14"),
("mc-0002","PistonStructureResolver", 1.1, "1.1× — bounded ≤12"),
("mc-0003","ExpRedstoneWireEvaluator", 25.0, "~25× — N=500 wires"),
("mc-0004","MoveThroughVillageGoal", 1.0, "~1.0× — bounded ≤15"),
]
MC_CLIENT_DEFECTS = [
("mc-0001","DependencySorter (world load)", 110.9, "110.9× — depth 14"),
("mc-0002","PistonStructureResolver", 1.1, "1.1× — bounded ≤12"),
("mc-0003","ExpRedstoneWireEval (redstone)", 25.0, "~25× — N=500 wires"),
("mc-0004","MoveThroughVillageGoal (AI)", 1.0, "~1.0× — bounded ≤15"),
]
# ── SVG helpers ───────────────────────────────────────────────────────────────
def polar(cx, cy, r, angle_deg):
a = math.radians(angle_deg - 90) # 0° = top
return cx + r * math.cos(a), cy + r * math.sin(a)
def arc_path(cx, cy, r_outer, r_inner, start_deg, end_deg):
"""SVG path for a donut arc segment."""
# Clamp sweep to avoid degenerate full-circle arcs
sweep = end_deg - start_deg
if abs(sweep) >= 359.99:
end_deg = start_deg + 359.0
sweep = 359.0
large = 1 if sweep > 180 else 0
ox1, oy1 = polar(cx, cy, r_outer, start_deg)
ox2, oy2 = polar(cx, cy, r_outer, end_deg)
ix1, iy1 = polar(cx, cy, r_inner, end_deg)
ix2, iy2 = polar(cx, cy, r_inner, start_deg)
return (
f"M {ox1:.2f},{oy1:.2f} "
f"A {r_outer},{r_outer} 0 {large},1 {ox2:.2f},{oy2:.2f} "
f"L {ix1:.2f},{iy1:.2f} "
f"A {r_inner},{r_inner} 0 {large},0 {ix2:.2f},{iy2:.2f} Z"
)
def fmt_speedup(s):
if s >= 100: return f"{s:.0f}×"
if s >= 10: return f"{s:.1f}×"
return f"{s:.2f}×"
# ── Main chart builder ───────────────────────────────────────────────────────
def make_donut(title, subtitle, defects, peak_label, filename):
n = len(defects)
lines = []
lines.append(f'<svg xmlns="http://www.w3.org/2000/svg" width="{SVG_W}" height="{SVG_H}" '
f'style="background:{BG};font-family:Helvetica,Arial,sans-serif;">')
# Title
lines.append(f' <text x="20" y="26" font-size="12" font-weight="500" '
f'fill="{CENTER_TITLE}">{title}</text>')
lines.append(f' <text x="20" y="42" font-size="8.5" fill="{CENTER_SUB}">{subtitle}</text>')
# Center text
lines.append(f' <text x="{CX}" y="{CY-10}" text-anchor="middle" font-size="22" '
f'font-weight="600" fill="{CENTER_TITLE}">{peak_label}</text>')
lines.append(f' <text x="{CX}" y="{CY+8}" text-anchor="middle" font-size="8" '
f'fill="{CENTER_SUB}">peak speedup</text>')
lines.append(f' <text x="{CX}" y="{CY+20}" text-anchor="middle" font-size="7.5" '
f'fill="{CENTER_SUB}">measured</text>')
# Rings — outermost ring = index 0
for i, (did, name, speedup, annot) in enumerate(defects):
r_out = R_INNER_BASE + (n - i) * (RING_W + RING_GAP)
r_in = r_out - RING_W
# Background full ring — outline only (white fill, thin gray stroke)
bg_path = arc_path(CX, CY, r_out, r_in, 0, 359.9)
lines.append(f' <path d="{bg_path}" fill="{RING_BG}" stroke="#cccccc" stroke-width="0.5"/>')
# BEFORE arc — the whole 360° represents BEFORE time
# AFTER arc = 360° / speedup degrees (after is smaller)
# We show: red arc = fraction spent in BEFORE overhead = (1 - 1/speedup) * 360
# Green arc = 1/speedup * 360 (the fixed portion)
frac_after = min(1.0, 1.0 / speedup)
after_deg = frac_after * 359.9
before_deg = 359.9 - after_deg
# Before arc (large, red)
if before_deg > 0.5:
p = arc_path(CX, CY, r_out, r_in, after_deg, 359.9)
lines.append(f' <path d="{p}" fill="{BEFORE_COLOR}" opacity="0.75"/>')
# After arc (small, green)
if after_deg > 0.5:
p = arc_path(CX, CY, r_out, r_in, 0, after_deg)
lines.append(f' <path d="{p}" fill="{AFTER_COLOR}"/>')
# Spoke label at right side of ring
mid_r = (r_out + r_in) / 2
lx, ly = polar(CX, CY, mid_r + 6, 0) # point at top
# Legend row on the right
ly_label = 72 + i * 46
lx_swatch = LABEL_X
# Color swatch
lines.append(f' <rect x="{lx_swatch}" y="{ly_label - 8}" width="10" height="10" '
f'fill="{BEFORE_COLOR}" rx="2"/>')
lines.append(f' <rect x="{lx_swatch}" y="{ly_label + 4}" width="10" height="3" '
f'fill="{AFTER_COLOR}" rx="1"/>')
# Ring number indicator
lines.append(f' <text x="{lx_swatch + 14}" y="{ly_label}" font-size="7.5" '
f'fill="{CENTER_SUB}">{did}</text>')
lines.append(f' <text x="{lx_swatch + 14}" y="{ly_label + 11}" font-size="8.5" '
f'font-weight="500" fill="{CENTER_TITLE}">{name}</text>')
lines.append(f' <text x="{lx_swatch + 14}" y="{ly_label + 23}" font-size="8" '
f'fill="{CENTER_TITLE}" font-weight="600">{annot}</text>')
lines.append(f' <text x="{lx_swatch + 14}" y="{ly_label + 34}" font-size="7.5" '
f'fill="{CENTER_SUB}">before arc = {before_deg/3.6:.0f}% of time</text>')
# Legend header
lines.append(f' <text x="{LABEL_X}" y="60" font-size="8" fill="{CENTER_SUB}" '
f'font-weight="600">DEFECTS (outer → inner ring)</text>')
# Bottom legend
lines.append(f' <rect x="20" y="{SVG_H-28}" width="12" height="12" fill="{BEFORE_COLOR}" rx="2"/>')
lines.append(f' <text x="36" y="{SVG_H-19}" font-size="8" fill="{CENTER_SUB}">BEFORE — wasted time (dark)</text>')
lines.append(f' <rect x="180" y="{SVG_H-28}" width="12" height="12" fill="{AFTER_COLOR}" stroke="#999" stroke-width="0.5" rx="2"/>')
lines.append(f' <text x="196" y="{SVG_H-19}" font-size="8" fill="{CENTER_SUB}">AFTER — time with fix (light)</text>')
lines.append(f' <text x="350" y="{SVG_H-19}" font-size="7.5" fill="{CENTER_SUB}">'
f'Arc proportional to fraction of BEFORE time.</text>')
lines.append('</svg>')
out = os.path.join(OUT_DIR, filename)
with open(out, "w") as f:
f.write("\n".join(lines))
print(f" wrote {out}")
# ── Generate all three ────────────────────────────────────────────────────────
print("Generating donut charts...")
make_donut(
title="javac — CWE-407 Speedups",
subtitle="OpenJDK javac · 5 defects · all patched upstream · measured at production scale",
defects=JAVAC_DEFECTS,
peak_label="20.9×",
filename="donut-javac.svg",
)
make_donut(
title="Minecraft Server-26.1 — CWE-407 Speedups",
subtitle="server-26.1-all-patched.jar · 4 defects · world load / game loop / redstone",
defects=MC_SERVER_DEFECTS,
peak_label="110.9×",
filename="donut-mc-server.svg",
)
make_donut(
title="Minecraft Client — CWE-407 Impact",
subtitle="Single-player · same defective classes · modpack world load + in-game",
defects=MC_CLIENT_DEFECTS,
peak_label="110.9×",
filename="donut-mc-client.svg",
)
print("Done.")