1616from __future__ import annotations
1717
1818import json
19+ import re
1920from dataclasses import dataclass
2021from datetime import datetime , timezone
2122from pathlib import Path
@@ -64,23 +65,57 @@ def _mpl():
6465 plt = _plt
6566 return plt
6667
67- # display order + labels. A gateway appears in a chart only if it has a result file this run.
68- GATEWAYS = {
69- # Alphabetical by key — deliberately NOT busbar-first. Order here is only the input/load order;
70- # every chart and the report tables sort by the MEASURED value, so placement never favors anyone.
71- "apisix" : "APISIX" ,
72- "arch" : "Arch" ,
73- "bifrost" : "Bifrost" ,
74- "busbar" : "Busbar" ,
75- "gomodel" : "GoModel" ,
76- "helicone" : "Helicone" ,
77- "kong" : "Kong" ,
78- "litellm-python" : "LiteLLM · Python" ,
79- "litellm-rust" : "LiteLLM · Rust" ,
80- "one-api" : "One-API" ,
81- "portkey" : "Portkey" ,
82- "tensorzero" : "TensorZero" ,
68+ # ── the field is discovered from the manifests, nothing is hard-coded here ─────────────────────────
69+ # Each gateway is fully defined by its own dir: gateways/<key>/gateway.sh declares GW_DISPLAY (label),
70+ # GW_LANG (color bucket), and GW_REPO (linked from the name in the report table). Add a dir → it shows
71+ # up in the charts/tables/run-lists; delete it → it's gone everywhere. A gateway only appears in a
72+ # chart once it also has a result file this run. Alphabetical by key — deliberately NOT busbar-first;
73+ # order here is only load order, every chart + table sorts by the MEASURED value.
74+ def _manifest_meta ():
75+ out = {}
76+ for man in sorted ((ROOT / "gateways" ).glob ("*/gateway.sh" )):
77+ key = man .parent .name
78+ txt = man .read_text ()
79+
80+ def grab (var , default = "" ):
81+ m = re .search (rf'^{ var } =(.*)$' , txt , re .M )
82+ if not m :
83+ return default
84+ v = m .group (1 ).split ("#" , 1 )[0 ].strip () # drop trailing comment
85+ return v .strip ('"' ).strip ("'" ).strip ()
86+
87+ out [key ] = {
88+ "display" : grab ("GW_DISPLAY" , key ),
89+ "lang" : grab ("GW_LANG" , "Other" ),
90+ "repo" : grab ("GW_REPO" ),
91+ }
92+ return out
93+
94+ _META = _manifest_meta ()
95+ GATEWAYS = {k : v ["display" ] for k , v in _META .items ()} # key → display label
96+ LANGS = {k : v ["lang" ] for k , v in _META .items ()} # key → language bucket
97+ REPOS = {k : v ["repo" ] for k , v in _META .items ()} # key → GitHub URL (may be "")
98+
99+
100+ def _linked (key : str ) -> str :
101+ """Gateway display name for the report table, linked to its GitHub repo when the manifest gives one."""
102+ name = GATEWAYS .get (key , key )
103+ repo = REPOS .get (key )
104+ return f"[{ name } ]({ repo } )" if repo else name
105+
106+ # Bars are colored by the gateway's IMPLEMENTATION LANGUAGE — informative (you can see the Rust/Go/
107+ # Python clustering) and neutral (no "winner" highlight for the sponsor; the best is already the top
108+ # bar since rows are sorted). A gateway that didn't serve is drawn grey regardless.
109+ # Five buckets: Rust / Go / Python / Node / Other (Lua/OpenResty, Envoy/C++, … fold into Other).
110+ LANG_ORDER = ["Rust" , "Go" , "Python" , "Node" , "Other" ]
111+ LANG_COLORS = {
112+ "Rust" : "#c4602d" , # orange
113+ "Go" : "#00a0c6" , # cyan
114+ "Python" : "#3b6ea5" , # blue
115+ "Node" : "#c59b2d" , # amber
116+ "Other" : "#6b7280" , # grey
83117}
118+ LANG_DEFAULT = "#6b7280"
84119
85120
86121@dataclass (frozen = True )
@@ -260,10 +295,10 @@ def _numlab(v: float) -> str:
260295 vals = [float (r .get (s .field , 0 ) or 0 ) for r in rows ]
261296 rank = s .kind == "rank"
262297 if rank :
263- # green = served winner; slate = served; muted = did-not-serve (bar is context only).
264- colors = [ BRAND if ( best is not None and _served ( r ) and v == best )
265- else ( SLATE if _served (r ) else MUTE )
266- for r , v in zip ( rows , vals ) ]
298+ # colored by implementation language ( served); did-not-serve is drawn grey. No winner
299+ # highlight — the best is already the top bar (rows are sorted).
300+ colors = [ LANG_COLORS . get ( LANGS . get ( r [ "_key" ], "" ), LANG_DEFAULT ) if _served (r ) else MUTE
301+ for r in rows ]
267302 else :
268303 colors = [s .kind ] * n
269304 # On a log axis a bar can't start at 0, and a negative/zero value can't be drawn at all.
@@ -323,17 +358,27 @@ def _numlab(v: float) -> str:
323358 ax .set_title (chart .title , fontsize = 15 , fontweight = "bold" , color = INK , loc = "left" , pad = 38 )
324359 ax .text (0 , 1.035 , chart .subtitle , transform = ax .transAxes , fontsize = 10.5 , color = GRAY , va = "bottom" )
325360
326- # legend (only multi-series charts need it)
327- if ns > 1 :
328- ax .legend (loc = "lower right" , fontsize = 9 , frameon = False , ncols = ns )
361+ # Language legend (swatch per language present) + a note for the secondary series (e.g. idle RAM).
362+ from matplotlib .patches import Patch
363+ present = [l for l in LANG_ORDER if any (_served (r ) and LANGS .get (r ["_key" ]) == l for r in rows )]
364+ handles = [Patch (facecolor = LANG_COLORS [l ], label = l ) for l in present ]
365+ if any (not _served (r ) for r in rows ):
366+ handles .append (Patch (facecolor = MUTE , label = "did not serve" ))
367+ if ns > 1 : # a muted secondary series (idle RAM) — label it too
368+ handles .append (Patch (facecolor = MUTE , label = chart .series [1 ].legend ))
369+ if handles :
370+ # "best" so the swatch box lands in whitespace (top bars are short on the log charts, longest
371+ # bars crowd the bottom) instead of sitting over a bar.
372+ ax .legend (handles = handles , loc = "best" , fontsize = 8.5 , frameon = False ,
373+ ncols = min (len (handles ), 6 ), title = "colored by language" )
329374
330375 meta = rows [0 ]
331376 bits = []
332377 if "hardware" in meta :
333378 bits .append (str (meta ["hardware" ]))
334379 if "concurrency" in meta and "payload_bytes" in meta :
335380 bits .append (f"{ meta ['concurrency' ]} × { int (meta ['payload_bytes' ])// 1000 } KB sustained" )
336- bits .append ("highlighted = measured best " )
381+ bits .append ("bars colored by implementation language " )
337382 fig .text (0.008 , 0.012 , " · " .join (bits ) + f" github.com/GetBusbar/benchmarking — regenerated { RENDER_TS } from raw results" ,
338383 fontsize = 7.3 , color = GRAY )
339384
@@ -364,8 +409,9 @@ def _report_md(rows: list, title: str, charts: list, pending: tuple = (), chart_
364409 lines .append (f"**Ran on:** { hw } · { when } " )
365410 lines .append ("" )
366411 lines .append ("Every number below is regenerated from the raw `results/*.json` — re-run "
367- "`run-all.sh` and this page updates. The highlighted bar in each chart = measured best. "
368- "**Rows are sorted by added latency (p99), lowest first.**" )
412+ "`run-all.sh` and this page updates. Chart bars are **colored by implementation "
413+ "language** (Rust / Go / Python / Node / Other). **Rows are sorted by added latency "
414+ "(p99), lowest first.**" )
369415 lines .append ("" )
370416 lines .append ("| Gateway | Added latency (p99) | Sustained RPS @20ms | Max proxy RPS | Idle RAM | Peak RAM | Built |" )
371417 lines .append ("|---|--:|--:|--:|--:|--:|---|" )
@@ -407,7 +453,7 @@ def rps_cell(val, bound, served):
407453 if served is False and r .get ("serve_error" ):
408454 fail_notes .append ((GATEWAYS [key ], str (r .get ("serve_error" ))))
409455 lines .append (
410- f"| { GATEWAYS [ key ] } "
456+ f"| { _linked ( key ) } "
411457 f"| { lat_cell } "
412458 f"| { llm } "
413459 f"| { proxy } "
@@ -418,7 +464,7 @@ def rps_cell(val, bound, served):
418464 # Gateways we intend to measure but haven't yet — shown so the field is transparent, never hidden.
419465 for key in pending :
420466 lines .append (
421- f"| { GATEWAYS [ key ] } | ⏳ *pending* | — | — | — | — | *pending measurement* |"
467+ f"| { _linked ( key ) } | ⏳ *pending* | — | — | — | — | *pending measurement* |"
422468 )
423469 lines .append ("" )
424470 if pending :
0 commit comments