#!/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''' Time scales - ns to minutes, wrapped logarithmic {chr(10).join(layers)} ''' if __name__ == "__main__": open("/mnt/user-data/outputs/time-scales.svg", "w").write(build()) print("written")