#!/usr/bin/env python3
"""Generate the FrOSCon io_uring talk 'time scales' wrapped-log-scale diagram.
Page: 254 x 142.9 mm (== LibreOffice Impress 16:9 slide, 25.4 x 14.29 cm)
User unit == 1 mm.
Structure: four bands, each spanning exactly three decades (1 -> 1000 of its
unit), all at identical horizontal scale. Each band is a separate Inkscape
layer so the whole thing can be exported step by step (Export > Page) and
stacked as transparent overlays in Impress.
@note Code generated 8/2026 by Claude -- prompted by Ichthyostega
"""
import math
W, H = 254.0, 142.9
X0, X1 = 34.0, 240.0 # axis: value 1 .. value 1000
DEC = (X1 - X0) / 3.0 # mm per decade
AXIS_END = 243.5 # slight overshoot + chevron
BANDS_Y = [40.0, 75.0, 102.0, 120.0]
# ---- colours -------------------------------------------------------------
C_AXIS = "#555555"
C_MINOR = "#b3b3b3"
C_MACH = "#14507d"
C_HUMAN = "#b4530a"
C_VAL_M = "#4a7ba6"
C_VAL_H = "#c98342"
C_MUTED = "#8a8a8a"
C_TITLE = "#222222"
# ---- type ----------------------------------------------------------------
F_NAME, F_VAL, F_BAND, F_TITLE, F_NOTE, F_DISC = 3.8, 3.2, 5.5, 8.0, 3.6, 3.0
FONT = "DejaVu Sans, Verdana, sans-serif"
# tier offsets relative to band axis Y
UP1_NAME, UP1_VAL, UP1_STUB = -10.3, -6.5, -5.0
UP2_NAME, UP2_VAL, UP2_STUB = -18.3, -14.5, -13.0
LO1_NAME, LO1_VAL, LO1_STUB = 11.2, 7.5, 5.0
BRACKET_Y = 4.0
def X(v):
return X0 + DEC * math.log10(v)
def esc(s):
return (s.replace("&", "&").replace("<", "<").replace(">", ">"))
def text(x, y, s, size, fill, anchor="middle", weight="normal"):
return (f'{esc(s)}')
def line(x1, y1, x2, y2, stroke, w, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'')
# ==========================================================================
# entry data
# ==========================================================================
# tier: 'up1' | 'up2' | 'lo1'
# kind: 'machine' | 'human'
# span: (v_lo, v_hi) -> draw a bracket instead of a dot
BANDS = [
dict(unit="1 ns", ay=BANDS_Y[0], layer="ns", entries=[
dict(v=1, name="L1 cache", val="1 ns", tier="up1", kind="machine"),
dict(v=4, name="L2 cache", val="4 ns", tier="up1", kind="machine"),
dict(v=10, name="full memory barrier", val="10 ns", tier="up2", kind="machine"),
dict(v=15, name="L3 cache", val="15 ns", tier="up1", kind="machine"),
dict(v=100, name="DRAM access", val="100 ns", tier="up1", kind="machine"),
dict(span=(50, 100), name="contended cache line", val="50-100 ns",
tier="lo1", kind="machine"),
dict(offscale=True, name="1 clock cycle", val="\u2248 0.3 ns",
tier="lo1", kind="machine"),
]),
dict(unit="1 \u00b5s", ay=BANDS_Y[1], layer="us", entries=[
dict(v=1, name="system call", val="1 \u00b5s", tier="up1", kind="machine"),
dict(v=2, name="context switch", val="2 \u00b5s", tier="up2", kind="machine"),
dict(v=50, name="OS timer slack", val="50 \u00b5s", tier="up1", kind="machine"),
dict(span=(10, 30), name="thread wake-up", val="10-30 \u00b5s",
tier="lo1", kind="machine"),
dict(span=(50, 100), name="SSD read", val="50-100 \u00b5s",
tier="lo1", kind="machine"),
]),
dict(unit="1 ms", ay=BANDS_Y[2], layer="ms", entries=[
dict(v=10, name="disk seek", val="10 ms", tier="up1", kind="machine"),
dict(v=40, name="one frame @ 25 fps", val="40 ms", tier="up1", kind="machine"),
dict(v=10, name="barely noticeable", val="10 ms", tier="lo1", kind="human"),
dict(v=100, name="UI feels sluggish", val="100 ms", tier="lo1", kind="human"),
]),
dict(unit="1 s", ay=BANDS_Y[3], layer="s", entries=[
dict(v=1, name="\u201cit\u2019s thinking\u2026\u201d", val="1 s",
tier="lo1", kind="human"),
dict(v=180, name="another coffee", val="3 min", tier="lo1", kind="human"),
dict(v=900, name="go do something else", val="\u2248 15 min",
tier="lo1", kind="human", anchor="end", ax=250.0),
]),
]
# ==========================================================================
# scaffold
# ==========================================================================
def band_axis(ay, unit):
o = []
o.append(line(X0, ay, AXIS_END, ay, C_AXIS, 0.6))
# continuation chevron
o.append(f'')
# ticks
for d in range(3):
base = 10 ** d
for m in range(1, 10):
v = base * m
x = X(v)
if m == 1:
o.append(line(x, ay - 3.0, x, ay + 3.0, C_AXIS, 0.6))
else:
o.append(line(x, ay - 1.6, x, ay + 1.6, C_MINOR, 0.25))
o.append(line(X(1000), ay - 3.0, X(1000), ay + 3.0, C_AXIS, 0.6))
o.append(text(32.0, ay + 2.0, unit, F_BAND, C_MUTED, "end", "600"))
return o
def scaffold():
o = []
o.append(text(10.0, 15.0, "Time scales", F_TITLE, C_TITLE, "start", "600"))
o.append(text(244.0, 12.5, "each row spans three decades", F_NOTE, C_MUTED, "end"))
o.append(text(244.0, 17.0, "one row down = \u00d7 1000", F_NOTE, C_MUTED, "end"))
for b in BANDS:
o += band_axis(b["ay"], b["unit"])
o.append(text(10.0, 137.0,
"Order of magnitude only \u2014 contemporary desktop / laptop x86. "
"Expect \u00b1 2\u20133\u00d7; the ratios are the point.",
F_DISC, C_MUTED, "start"))
return o
# ==========================================================================
# entries
# ==========================================================================
def render_entry(e, ay):
kind = e.get("kind", "machine")
cn = C_MACH if kind == "machine" else C_HUMAN
cv = C_VAL_M if kind == "machine" else C_VAL_H
tier = e["tier"]
anchor = e.get("anchor", "middle")
o = []
if tier == "up1":
ny, vy, sy = ay + UP1_NAME, ay + UP1_VAL, ay + UP1_STUB
elif tier == "up2":
ny, vy, sy = ay + UP2_NAME, ay + UP2_VAL, ay + UP2_STUB
else:
ny, vy, sy = ay + LO1_NAME, ay + LO1_VAL, ay + LO1_STUB
if e.get("offscale"):
# dashed stub running off the left-hand end of the scale
y = ay + BRACKET_Y + 0.5
o.append(line(X0, y, 22.5, y, cn, 0.5, dash="1.6,1.2"))
o.append(f'')
ax = 22.0
o.append(text(ax, vy, e["val"], F_VAL, cv, "start"))
o.append(text(ax, ny, e["name"], F_NAME, cn, "start", "600"))
return o
if "span" in e:
x1, x2 = X(e["span"][0]), X(e["span"][1])
by = ay + BRACKET_Y
o.append(line(x1, by, x2, by, cn, 0.7))
o.append(line(x1, by, x1, ay, cn, 0.7))
o.append(line(x2, by, x2, ay, cn, 0.7))
ax = e.get("ax", (x1 + x2) / 2.0)
else:
x = X(e["v"])
o.append(f'')
stub_from = ay - 2.0 if tier.startswith("up") else ay + 2.0
o.append(line(x, stub_from, x, sy, cn, 0.4))
ax = e.get("ax", x)
o.append(text(ax, vy, e["val"], F_VAL, cv, anchor))
o.append(text(ax, ny, e["name"], F_NAME, cn, anchor, "600"))
return o
# ==========================================================================
# assemble
# ==========================================================================
def layer(lid, label, body, display="inline"):
return (f'\n '
+ "\n ".join(body) + "\n")
def build():
layers = []
layers.append(layer("layer-1-scaffold", "1 - scaffold", scaffold()))
for i, b in enumerate(BANDS):
if b["layer"] == "ms":
mach = [x for e in b["entries"] if e["kind"] == "machine"
for x in render_entry(e, b["ay"])]
hum = [x for e in b["entries"] if e["kind"] == "human"
for x in render_entry(e, b["ay"])]
layers.append(layer("layer-4-ms-machine", "4 - ms band (machine)", mach))
layers.append(layer("layer-5-ms-human", "5 - ms band (human)", hum))
elif b["layer"] == "ns":
body = [x for e in b["entries"] for x in render_entry(e, b["ay"])]
layers.append(layer("layer-2-ns", "2 - ns band", body))
elif b["layer"] == "us":
body = [x for e in b["entries"] for x in render_entry(e, b["ay"])]
layers.append(layer("layer-3-us", "3 - us band", body))
else:
body = [x for e in b["entries"] for x in render_entry(e, b["ay"])]
layers.append(layer("layer-6-s", "6 - s band (human)", body))
dim = [f'']
layers.append(layer("layer-7-dim", "7 - dim ns+us (optional)", dim,
display="none"))
return f'''
'''
if __name__ == "__main__":
open("/mnt/user-data/outputs/time-scales.svg", "w").write(build())
print("written")