Relay-page bandwidth history: Option B with clickable periods - #231
Relay-page bandwidth history: Option B with clickable periods#2311aeo wants to merge 88 commits into
Conversation
Add a proposal that ranks charts by tor-relays and forum pain points, plus Allium's own diagnostic catalog, and include mockups generated from the 2026-08-15 Onionoo details snapshot. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueComment |
Mock three encodings each of uptime and bandwidth on live Onionoo series, plus a flag-flapping swimlane. Clarify that the network scatter and Guard histogram do not belong on every relay page. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Replace the invented 95% line with the 98% Guard/HSDir WFU floor, show the 99.2% month average and the once-only 50% worst bucket on the plot, and document how #uptime scalars do and do not match. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
…egend Combine dual-line throughput with the advertised overlay and a write/read band (0.80–1.25). Move restart and overload into a color-coded legend. Reserve red for problems only. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Onionoo only reports when overload was last detected. Restart stays a point at last_restarted; overload is the proposal-328 band from that report through +72 hours, matching Allium's existing relay-page window. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
A and B plot every flag equally, so the 4-hour Running gaps that knock off HSDir are hairline slivers. C is cause→effect, D is episode counts, E keeps only flags that moved, and F is a status headline plus the month story. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Onionoo omits unpublished graphs rather than sending empty series. Show only what exists (th4r has no 5Y; a 3-day relay has only 1M). F3Netze is why 5Y matters: 1M is 99.2%, 5Y is 89% with real zeros. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
The chart is authorities seeing Running, not last_restarted. A process rail makes that obvious. Gray bands are 4-hour buckets where ≥8% of a 400-relay sample also dipped; orange is this relay inside that band, red is this relay alone. Onionoo bucket math goes in the #uptime info box and pill tooltips, not on the SVG. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
The green band is a frozen 0.90–1.15 expected range, not a live percentile — a network DoS would move p10–p90 and hide the event. Dashed line is the same-flag-set network median; dotted line is the AROI/family median. Omit the operator line when the group is one relay. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Onionoo has no overload history graph — only last-detected timestamps. Consensus files do not carry overload (it lives on descriptors). Until CollecTor descriptor history exists, charts show a now-badge when the 72h window is still open and leave restart as the only x-axis marker. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Remove the inferred 72h band from R2 A/B/C and top-10 chart 6. F3Netze shows OVERLOADED NOW because the last report is still inside the proposal-328 window at relays_published. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
| rts = datetime.fromtimestamp( | ||
| facts['ratelimits']['timestamp'] / 1000, tz=timezone.utc) | ||
| if last_report is None or rts > last_report: | ||
| last_report = rts |
There was a problem hiding this comment.
Badge date ignores rate-limit hit
Medium Severity
last_report pulls any overload_ratelimits.timestamp even when ratelimit_hit is false. General overload correctly gates on general_active, so a present-but-not-hit rate-limit object can overwrite the badge’s “last report” time with a timestamp that did not contribute to current overload.
Reviewed by Cursor Bugbot for commit 30cdf5b. Configure here.
Census every Onionoo bandwidth graph (1M/6M/1Y/5Y) at 2026-08-15 19:00. 1-month p10–p90 for relays ≥50 KB/s is 0.965–1.130, median 1.021. Longer periods are tighter. 0.90–1.15 is that 1M shape with a little room — not a live percentile. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
0.90–1.15 is a typicality band, not an outlier alarm. 1.20 is uncommon (6.3% of 1M relays, mostly Guards). Red is reserved for <0.80 or >1.50 (2.2% of 1M) — directory authorities and genuinely lopsided relays. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Move the throughput legend into the empty band above the advertised line so it never covers write/read. Replace the red OVERLOADED NOW pill with a title-line cue (or a legend diamond). Add three relay-info.html #bandwidth layout mockups: hero under the heading (F3Netze), after Capacity/Measurement (jeangrae / 1aeo family), and a History subsection after Network Participation. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Option 3 puts the overload cue in the chart legend, not the heading. The health-status row still has to say Overloaded — that is the live page's Stability cell, independent of the chart encoding. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Typical is this flag set’s p10–p90; investigate is beyond its p98. An Exit at 1.20 is p98.7 (red); a Guard at 1.20 is p91.6 (amber). Keep the numbers frozen from a quiet census so a network-wide DoS cannot walk the green band. Drop “check role overlay” — the overlay is already on the chart as confirmation. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
The strip already uses frozen per-role p10–p90 / p98 bands. The R2 intro and survey note still quoted the rejected global investigate line; align those sentences with the flag-set thresholds. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
| op = overlay_values(ts, overlays.get("operator")) | ||
| if op is not None: | ||
| axr.plot(ts, op, color=GRAY, linestyle=":", linewidth=1.6, zorder=2) | ||
| investigate = (ratio < ilo) | (ratio > ihi) |
There was a problem hiding this comment.
Investigate band clipped by ylim
Medium Severity
Exit+Guard invest_hi is 1.71, but the ratio strip ylim still tops out at 1.70. The high-side investigate span therefore sits entirely outside the visible axes on the primary F3Netze mockups, while the legend still promises a red zone beyond 1.71. The >p98 label also lands inside the amber shoulder.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 98abf91. Configure here.
A details published tick must not bust every figure. The cache payload now stores the derived current_overload_status boolean; the renderer still receives relays_published as the 72h clock. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Role medians are computed once and family medians once per Onionoo effective_family. The operator line is omitted when n<2. Contact-group grouping is gone; --charts-limit and --fingerprint slice chartable fingerprints in details order. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
auto stays an explicit choice. --charts-limit and repeatable --fingerprint slice a first run; History img follows that slice. Chart workers hard-cap at 8; one bad relay cannot kill the pool or the HTML generate, and a non-zero failed count is printed at the end. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Reuse one selection helper for HTML flags and the spawn pass, drop unused ChartSpec fields and renderer chrome, and hash the numeric band row instead of units the figure never reads. Reject non-hex fingerprints at path join, treat an empty cached PNG as a miss, and default the History img flags off. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Import identity, bands, series, and locked C outcome copy from the production package. Delete the local dated/verdict branches and the duplicated parsers. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Collapse the 565-line plan into the current architecture. Cut the encoding doc's repeated pipeline notes to a pointer; leave the C copy table, identity, and band spec in place. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
HTML still succeeds without matplotlib: one log line, skip figures, omit History img. --no-charts turns charts off. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Drop design-history mockups, generators, survey JSON, and encoding essays so this branch ships only the production chart pass. Keep the pipeline write-up and point encoding locks at allium.lib.charts. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Walk write/read history into series_by_fp, stash _selection after the HTML gate, and drive skip/overlay/overload/legend/ratio work from one helper each so the pass cannot drift from the img flags or re-parse the same blocks. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Add make_relay/make_bw/make_job/stub_chart_pool and fold the omit-img and pipeline monkeypatches so the DRY cuts stay covered without copy-paste cases. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Review pass: one _overload_fields body, and the chart loop continues instead of KeyError if a selected fingerprint is absent from series_by_fp. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
series_by_fp now fills a period dict from one bandwidth-map lookup. Register three spark ChartSpecs that share one renderer name, and add a slim spark cache payload (period + write/read + advertised, no overlays). Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Reuse _skip_reason, the stashed selection, one spawn pool, and the cache helpers. The 1M hero is unchanged; sparks omit the write/read strip, C copy, identity, and family overlays. Missing periods stay off the page. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Assert sparks register, cache keys differ by period, --no-charts omits them, and a missing 5Y graph does not emit that img. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Reuse the 1M hero renderer for 6M/1Y/5Y, publish four content-hashed PNGs per relay, and write 6m.html / 1y.html / 5y.html with relative spark links back to the other period views. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Charts now use build_bandwidth_map from bandwidth_utils and parse Onionoo timestamps / published_clock from time_utils. The charts copy of those parsers is gone; existing parse_onionoo_timestamp callers still get None on bad strings. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
apply_chart_html_flags now lives next to other relay_set HTML gates. pipeline re-exports it for existing tests. ChartSpec and the 1M/6M/1Y/5Y period heroes are defined in pipeline.py; registry.py is gone. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
series.py no longer re-exports build_bandwidth_map or published_clock. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
The locked 1M figure stays render_relay_bandwidth_1m. Sparks reuse the same walk, pool, and cache helpers with a slim period payload and a ~40-line drawer (write/read/advertised only). History stays one index.html slot; missing periods omit that img. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Revert the spark-only drawer that dropped 6m/1y/5y hero pages and clickable sparks. Restores Option B: each period opens as the hero with the other three as spark links (four images per relay). Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Drop leftover spark-only helpers, one-call wrappers, unused ChartSpec fields, and a pipeline re-export. Collapse tests that restated the same cache/CLI/History contracts. Shrink the pipeline note so it no longer duplicates the code. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Spark clicks now open 6m/1y/5y/index with #bandwidth so the period page lands on History instead of the document top. Templates still emit .html paths; the clean-route rewrite turns them into 6m#bandwidth and ./#bandwidth. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Hero identity is 11pt and titles 12pt so they sit nearer page body type. Tick labels are 11pt so CSS-scaled sparks stay readable. renderer_version is 2 so existing chart-cache entries miss. The write/read legend reuses _event_legend_handles when a restart falls in span. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Split the chart pass into concurrent 1M/6M/1Y/5Y spawn pools that share one process budget (default min(CPU, 16), hard cap 16) so a full generate no longer waits on four mixed workers. Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
Co-authored-by: 1aeo <1aeo@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d9d2e92. Configure here.
| import os | ||
|
|
||
| fig.canvas.draw() | ||
| rgba = _np.asarray(fig.canvas.buffer_rgba(), copy=False) |
There was a problem hiding this comment.
Chart save crashes on NumPy 1
High Severity
_save_trimmed now calls np.asarray(..., copy=False). That copy keyword exists only in NumPy 2.0+, so chart saving raises TypeError on NumPy 1.x. The documented Python 3.8 path (matplotlib<3.8) cannot use NumPy 2, so every PNG write fails there and History images stay broken.
Reviewed by Cursor Bugbot for commit d9d2e92. Configure here.


Generate-path charts on each relay page after HTML. Matplotlib stays in a spawn pass; Jinja never imports it.
This revision
Each drawable period is a full Option B hero (identity, throughput, write/read strip, frozen role bands, locked C copy). Family/peer overlays stay 1M-only.
Up to four PNGs per relay, reused as the hero and as CSS-scaled sparks:
bandwidth-1m.pngbandwidth-6m.pngbandwidth-1y.pngbandwidth-5y.pngSparks are links to that period’s own Option B in the same directory (
index.html,6m.html,1y.html,5y.html). Thin periods are omitted.jeangrae 1M Option B hero
jeangrae 6M Option B hero
F3Netze 1M Option B hero
F3Netze 6M Option B hero
Partial merge
Reasonable overlaps only.
registry.pyis gone. Charts usebandwidth_utils.build_bandwidth_mapandtime_utils(parse_onionoo_timestamp,published_clock).apply_chart_html_flagslives onrelays.py.ChartSpecis inlined in the chart pipeline.Not merged: matplotlib stays out of Jinja /
page_writer; PNG.chart-cachestays separate from the Onionoo JSON cache; series / overlay / outcome / bands stay out ofbandwidth_utils; spawn +--chart-workersstay (not the HTML--workersfork pool).--charts-limit/--fingerprintstill slice relays, not periods.--no-charts/ missing matplotlib omit History and extra period files.To show artifacts inline, enable in settings.