Dashboard¶
The k4Bench dashboard is a Streamlit app that browses
benchmark history collected by the nightly CI. It's deployed at
k4bench-dashboard.app.cern.ch and
also runs locally against your own logs/ directory.
Its internals are documented under Architecture → dashboard; this page is the user guide.
Purpose¶
Looking at one run tells you today's cost. The dashboard answers the questions that need history and comparison:
- How does each detector's cost compare in the latest run?
- Has a Key4hep release made simulation slower or hungrier?
- How does per-event time/memory drift across releases?
Where the data comes from¶
Every night, CI runs a curated set of benchmarks (defined in
.github/benchmarks/*.yml), uploads each result to CERN EOS, and the dashboard
reads them over HTTPS from a WebEOS endpoint. The EOS layout is hierarchical:
{detector}/{platform}/key4hep-{release}/{sample}/{YYYY-MM-DD}/
run_info.json machine_info.json
{config}_results.csv {config}_events.json {config}_regions.json {config}.log
The sidebar lets you drill down through that hierarchy: detector → platform → physics sample → Key4hep release. Single-run tabs use the newest uploaded run for that selection; the trend window controls the multi-run views.
Each level is rebuilt from the one above it, so changing the detector can leave the selected platform or sample with no match — in which case it re-defaults to a valid one and says so right under the selector. That notice matters because the Overview tab is scoped by platform and sample across all detectors: without it, a sample that quietly moved would look like detectors vanishing from its charts.
Not every tab honours the whole hierarchy, so a single muted line directly
under the section bar names the slice the tab in front of you actually uses —
for example Showing CLD — AlmaLinux 9 · GCC 14.2.0 (optimized) — Single e⁻ ·
10GeV — 2026-07-10. Each level is written the way the tabs and the nightly
mail write it, so a sample reads as its physics rather than as its EOS
directory. Where a tab deliberately ignores a level it says so
instead of quietly dropping it: Overview reads all detectors, Run Trends
reads all releases in the trend window, and Stack Changes says its package
diff is platform-wide, because a Key4hep release is the same stack whatever
benchmarked it. The line also follows the controls inside a tab that change its
scope: switching to a Historical Trends view replaces the release with all
releases in the trend window, and switching on Stack Changes' Whole platform
toggle replaces the detector and sample with all detectors and all samples.
The line never repeats a date range, report night or run date — each tab prints
its own, in its own words. In local mode there is no hierarchy to show, so it
names the run directory instead.
Example detectors
A couple of detectors (currently: SiD) come from a simulation toolkit's
own reference/tutorial geometry rather than a maintained FCC/Key4hep
experiment design. Picking one of these shows a note in the sidebar
linking back to its source, so it doesn't read as "just another
experiment" (see EXAMPLE_DETECTORS in
dashboard/ui_chrome.py).
The tabs¶
| Tab | What it shows | Backed by |
|---|---|---|
| Config Impact | baseline-relative configuration impact in the selected run | *_results.csv |
| Event timing | per-event wall time distributions | *_events.json |
| Event memory | per-event RSS and growth | *_events.json |
| Region timing | per-subdetector stepping time, at_location vs by_birth, step counts, attribution analysis |
*_regions.json |
| Trends | metrics over time across releases | many runs, windowed |
| Regressions | the nightly regression report for the selected detector/platform/sample | _reports/{date}/report.json |
| Stack Changes | which Key4hep packages moved between two nightly releases | run_info.json (k4h_packages) |
| Overview | cross-detector comparison: performance trends, landscape, regression status & the nightly mail | _reports/{date}/report.json |
| Machine info | the host the benchmark ran on (CPU, RAM, governor, throttling) | machine_info.json |
| Logs | the raw ddsim log for the selected run |
*.log |
Display options¶
Anything that changes how a figure is drawn — colour palette, opacity, style cycling, smoothing, histogram bins, error bars, mean lines, how many detectors a ranking keeps — lives in a 👁️ Display options popover at the right-hand end of the view's control row. It looks and behaves the same in every view that has one (Event Timing, Event Memory, Region Timing's four views and Run Trends), and each popover ends with Reset to defaults, which restores every control inside it in one click.
What stays on the page is everything that changes what you are looking at: baseline, configuration, attribution, metric, view, report night, the reliability scope and the regression flag pills. Overview's Log/Linear/Relative % scale stays out on the page for the same reason — it changes what the numbers mean, not how they are painted. Whenever unreliable runs are present, the shared Runs · ⚠ N unreliable selector occupies the right-hand end of that view's existing controls line (beside Display options where present); it never inserts a separate warning or controls row.
Tabs with multiple views use the same lightweight selector: horizontal radio buttons under a bold View label. The segmented bar is reserved for the dashboard's top-level section navigation and for compact value choices such as Scale; it is not repeated as tab-local navigation.
Palette defaults follow the data: a figure picks the smallest Matplotlib tab-N palette that colours every series without repeating a colour, and re-picks it when the number of series crosses a boundary. An explicit choice sticks until that happens.
Config Impact tab¶
The four summary cards name the alternative configuration with the largest
estimated impact on wall time, peak memory, user CPU and output size. Pick a
metric to open its horizontal ranking: the selected baseline is the zero line,
better-than-baseline changes extend right in blue, and worse changes extend left
in orange. Positive has the same meaning for every metric — less
time/memory/CPU/output, or more throughput. The default Full detector
(baseline_all) comparison is the usual subdetector-removal study; the wording
remains baseline-relative if you select another reference. Throughput remains
available in the metric selector as the rate-oriented view of wall time. Hover a
bar for rounded raw measurements and their absolute change. The compact ranking
keeps the 12 largest absolute impacts, so it retains both major gains and major
regressions. When more than 12 configurations are comparable, switch on All
configs beside it to expand the chart.
With the full-detector baseline, the removal impacts are ablation estimates rather than additive accounting. Removing material changes particle transport, so the individual percentages need not add up to the cost of the full detector. For a direct view of where simulation time is spent, Region Timing attributes per-subdetector Geant4 stepping time both to where tracks run and where they were created.
Region timing tab¶
The richest tab. It mirrors the region plugin's
two attribution views (at_location and by_birth) and adds:
- Current run — per-detector time for the selected run; Top N detectors (in Display options) sets how many of the most expensive regions it ranks.
- Attribution analysis — the gap between the two views (intrinsic vs imported cost).
- Step analysis — timer-interval counts per detector.
- Historical — the same detector's time across runs.
Trends tab¶
Plots a metric over time. The x-axis is anchored on the Key4hep release date
(falling back to the run date), so you see regressions aligned with releases.
A sidebar look-back window (Last 7 days, 14 — the default — 30, 90,
6 months, All, or a custom range) controls how much history is downloaded
and shown. A preset counts back from the latest available nightly, not today,
so it always shows data even if the nightly hasn't run recently. That anchor is
deliberately detector-independent: it is the newest nightly report night,
which every detector shares, so the same preset covers the same dates whichever
detector the sidebar has selected — including while a batch is still uploading
and one detector's run is already newer than the last report. (The window's
end does stretch to that run, so it stays visible in Run Trends; a window can
therefore be a day or two longer than its label.) This matters most for the
cross-detector Overview tab, which draws every detector over this one window: an
anchor that followed the selected detector's last run would pull the window back
whenever a lagging detector was picked, and detectors that had run more recently
would silently vanish from its charts. If the selected detector has no runs in
the window, the sidebar caption says so and names the night it last ran (see
trend_window — pure logic,
unit-tested).
Nights the nightly regression detector confirmed a step are ringed in red
on the lines, and nights it flagged but hasn't confirmed are ringed as ⚠️ watch
points — both on by default, each behind its own toggle. It's the same
Confirmed/Watch toggle and marker language as the Overview tab, so a flag here
means exactly what it means there. Flags appear on the metrics the engine judges
(wall time, user CPU, peak RSS) plus throughput, which borrows the wall-time
verdict since throughput is exactly n_events / wall_time_s (the same
regression, inverted); CPU efficiency and context switches carry none. Runs that
failed the host-reliability check are excluded by default with the same
Runs · ⚠ N unreliable selector as every other historical view: Reliable
only (the default) or All runs. Palette, style cycling, opacity
and line smoothing sit in this tab's Display options
popover, alongside the pills on the same row.
Warmup is excluded
Trend and summary statistics drop event 0 (warmup), matching the analysis convention.
Regressions tab¶
Where the Trends tab lets you look for regressions, this tab shows what the
nightly detector found — for the sidebar-selected detector, platform and
sample, the same scoping as every trend view (the cross-detector picture lives
in the Overview tab). Every night, CI (the regression-report
job in nightly.yml) walks the full EOS history and judges every
(detector, platform, sample, config, metric) series with a conservative
step-change detector (k4bench/regression/):
- Baseline. Each night is compared against the median and spread (scaled MAD, not mean/stddev, so one bad night can't skew it) of the trailing 14 reliable runs before it. Nights flagged by the machine-info reliability check are excluded outright and never judged themselves. With fewer than 7 reliable runs to compare against, the metric is left unknown rather than flagged.
- What trips a flag. The value has to clear two gates at once: a robust z-score above 3.5 (a statistical outlier) and a practical-effect floor — at least 5 % for time and memory, 3 percentage points for CPU efficiency, and a wider floor for the noisier region metrics. Requiring both keeps a very steady metric (tiny MAD) from flagging on a change too small to care about.
- The workload is pinned and recorded. Timing measures software only if both
nights simulated the same events, so the benchmarks fix the ddsim seed and
each run records the seed it used in
run_info.json. Without one, ddsim draws a fresh seed per run and every night re-rolls the shower physics. When the recorded seed changes — the migration to a pinned seed, a later change of the value — that release is not judged against the old baseline: a fixed workload sits at one particular offset and reproduces it every night, so the two-strike rule would confirm the changeover rather than protect against it. The changed-over values re-anchor the baseline instead, and judging resumes at the new level with the next release. It costs one release of sensitivity, which is the honest price of no longer measuring the same thing; the report also says so in its notes. - Shared moves are reported once. When every config of a run group moves
together — a host or stack effect rather than a detector one — that is one
event, not one per config. The group-wide shift is judged as its own series,
reported under all configs (common mode), and divided out of each config's
series so what remains is that config's own movement. Nothing is discarded:
averaging over the group makes a stack-wide change easier to catch, not
harder. The decomposition only happens when the group really did move
together — a single config still sitting at its historical level vetoes it,
because on a removal sweep a regression inside one detector moves the baseline
and every
without_Xexcept the one that removed it, and dividing that majority out would leave the one configuration that identifies the cause looking like the only thing that moved. That row's value is a ratio, not a measurement — 1.00 is the run group at its usual level, 1.20 is every config 20 % above it — so it is shown as×1.20and plotted on a× baselineaxis, never in seconds or MB. A config's own row reports its residual after the shift is removed, with the measurement it came from and the shift kept alongside it inreport.json(raw_value,common_mode_shift). - Trimmed alongside total. Timing is judged on the mean, the median and an upper-trimmed mean that drops the slowest 5 % of events — one-sided, so only the slow tail goes. It reports the typical event; the untrimmed totals stay judged because the tail is exactly where a tail-confined regression would appear.
- One measurement, one test.
user_cpu_strackswall_time_sclosely enough that judging both would count the same event twice, so it is recorded and plottable but never flagged. - Watch, then regression. The first night to clear both gates is a ⚠️ Watch. It only becomes a confirmed 🔴 Regression once the next reliable night moves the same way again — a two-strike rule that is the main defence against false alarms. The confirming night may re-benchmark the same release as the watch night: a second run of the same binary tripping the same way is exactly the independent evidence the rule wants.
- Releases, not nights. The unit of change is the Key4hep release; nights that re-benchmark one release are repeat measurements of the same binary. Every night of a release is judged against the same frozen baseline, and once a step is confirmed for a release, every later night of that release that trips the same way is confirmed too, carrying the same onset window — so all of a release's reports agree on its regressions, modulo each night's own measured value. A repeat confirmation is labelled as such in the report and email ("repeat — first confirmed {night}"), so consecutive alerts read as "still present", not fresh news.
- Confirmations follow the balance of evidence. A single quiet night inside a confirmed release cannot outvote the two nights that confirmed — it is reported OK with a note that the release's median still sits beyond the baseline ("tonight's value looks like noise"). But when quiet nights drag the median of the release's nights back inside the gates, the confirmation is revoked ("confirmation revised") — the confirming nights were more likely noise — and the baseline is never re-anchored onto a fluke level.
- Re-anchoring. A confirmed regression that still stands at the next release boundary is treated as a change-point: the baseline resets to the new level when the next release arrives, so a deliberate step (say, a physics change) is flagged on the release that introduced it — on every night that measured that release — and falls quiet from the next release on, instead of alerting every night until the window rolls over. A second change right afterwards is still caught.
- Direction (faster/slower, more/less memory) is shown but not treated as good or bad — a regression is simply any confirmed step beyond the baseline in either direction.
- Failures. A config exiting non-zero, or a whole run missing, is a ❌ Failure and skips the confirmation step (it alerts immediately).
- Region timing (sub-detector) is not flagged — it is the noisiest series in the system, dominated by timer-granularity wobble at microsecond scale, so the regression report judges only top-level run and per-event metrics.
A report is written per run, so several nights routinely re-benchmark one fixed release. Their reports mostly agree — every night of a release is judged against the same baseline — but can still differ: the first strike is only a watch, a marginal night can come out OK, and reports built before the release-grouped engine confirmed on a single night. The sidebar's release selects which of that release's report nights are on offer; the tab defaults to the most attention-worthy one (a confirmed regression or failure outranks a watch outranks a quiet night, newest breaking ties), so the most alarming night is never hidden behind a quieter one. A Report night pill always appears above the report — one option carrying that night's glance badge (❌ / 🔴 / ⚠️ / ✅) even when the release was only benchmarked once, and one pill per night to step through the rest when it was benchmarked several times. The newest release (the sidebar default) still surfaces the latest report even when it is newer than the release's last run, so a "no run uploaded" failure stays visible. The banner, flagged metrics, and trend preview follow the selected night's historical evidence. Package and pull-request attribution is instead release-level: it is fixed from the first report night that confirmed a change window for the release and reused on every rerun, because no upstream package changes between repeat measurements of the same binary.
A benchmark job is dated when it starts, so a nightly batch beginning near midnight splits across two dates and some detectors are stamped a day behind the report night. Those are still tonight's measurements and are reported as such, with a note saying so — they are recognised by the CI run they came from, which every detector of one nightly shares, so a detector whose job crashed and uploaded nothing is still reported as a missing run rather than passing as a straddling batch — once the report night names a CI run, only a detector naming that same run is kept, whatever the gap between the two dates. Nights recorded before that CI run existed fall back to accepting a single night's lag, and their note says that is what happened. The Overview's charts follow each run by the date it actually ran, so a straddling job is still covered by the unreliable-run selector — it is one of tonight's runs, and the date it is stamped with should not decide whether the filter can see it.
A ?report=YYYY-MM-DD query parameter pins one report night directly and is
authoritative when valid — this is the stable deep link the nightly email and
the Overview roster generate, so a link to a confirmed regression keeps
pointing at the exact report the email described. (The Run Trends
range= window is unrelated and does not select a regression report.)
The selected run group renders flat: an at-a-glance banner (regressed / watch
/ failures / within-baseline counts) and a trend preview that plots a
flagged metric's recent history with the baseline band it was judged against
(the flagged night marked 🔴, the night it was first watched marked ⚠️), a few
releases further out when the release has since moved on. The window contains
up to 14 distinct Key4hep releases through the flagged release plus up to
7 later releases when available; repeat measurements of one release stay
visible without consuming another release slot. Its dropdown lists every
flagged metric worst first, each option carrying its own severity badge and
Δ-vs-baseline — so scanning the list alone shows the size of every flag,
without a separate ledger table. The preview opens on the most severe flag
automatically; switch it to any other flagged metric, or to "—" to hide it.
Confirmed regressions and failures — and only those — are also emailed to the
team's e-group by the same CI job, with per-group links landing directly on
this scoped view, pinned (via stack= and report=) to the exact report
night they describe.
For a confirmed regression the drill-down also shades the release window the step entered in. Because confirmation is a two-strike rule, the reported night is one reliable night after the step appeared, so the cause is upstream of the ⚠️ onset, not at the 🔴 report — the amber band spans from the last release seen at the accepted level up to the onset release. The upstream-changes card below the preview (see below) is where to follow that window into the Stack Changes tab. When both ends of the window are the same release the band gives way to a "nothing upstream changed" note on that card: the stack did not move across the step, so the cause is the host, the sample, or noise rather than an upstream commit.
Below the drill-down, the release gets an upstream-changes card naming the
packages that moved in the blame window established by its first confirmed
report, each linking to its commit range, plus an Open in Stack Changes →
link that seeds that tab with the exact release range. That same card is shown
on every report-night view of a re-benchmarked release: a later run may provide
the second strike for another metric, but it cannot create a new package-change
story for unchanged software. When the canonical report contains several
distinct windows, each retains its own card. When the blame
sidecar
carries a ranking, the card also lists suggested candidate pull requests:
each PR in the window with a 0–100% Likelihood it is the cause and a one-line
Why. The ranking is produced offline by a language model that read the
metric that moved and each PR's actual code diff (configured in CI via
K4BENCH_LLM_*; see the sidecar format) —
the dashboard only displays the stored result. Several PRs can land in one
package's range, so each is scored on its own. All ranked leads are shown in
one sortable table.
The group is judged once
per detector/platform/sample/window: when several metrics (or several
benchmark-config labels, e.g. a removal sweep's baseline and
without_<detector> runs) stepped across the same release boundary in the
same run group, they share one diff and one candidate set, so the card shows a
single verdict rather than a table per metric. A different detector or sample
sharing the same release dates never shares a verdict. This is a ranked
lead for a human, not a verdict — a suggestion, not proof of cause, in
keeping with the
detector's no evidence ⇒ no verdict rule; the nightly email surfaces the same
top candidates under each regression. Most nights carry no blame.json at all,
and a night whose candidates are not yet ranked says that no AI ranking is
stored rather than leaving an ambiguous blank below the package diff.
Stack Changes tab¶
Answers "what came in last night?" — and, when a metric has stepped, "what upstream change could that be?". Pick two nightly tags and it lists the Key4hep packages whose commit differs between them, each linking to the range on GitHub. When you open the tab, To release defaults to the stack selected in the sidebar and From release to the release immediately before it, when one is available. A compact line below the range reports how many tracked packages changed and remained unchanged without turning those counts into a second dashboard banner.
The package diff is cross-detector: a Key4hep release is one stack, sourced identically by every detector benchmarked against it, so only the platform scopes it.
Below the diff, Regressions in this range lists the confirmed metrics whose onset falls inside the selected release interval. It is scoped to the sidebar's detector and sample. A Whole platform (+N metrics) toggle appears only when widening would reveal confirmed metrics in other detector/sample scopes; a package change can affect any detector that sources it.
The worst metric opens in a compact selector rather than a ledger. Every option shows its severity, metric (including its region), config, Δ-vs-baseline and exact blame window; in the all-detectors view it also names the detector and sample. If distinct steps otherwise have the same label, their onset run is shown too. The selected item uses the same trend component as the Regressions tab: up to 14 releases of history and 7 later releases, the accepted-baseline band, first-watch and confirmation markers, unreliable-run filtering, and the shaded blame window. This makes a multi-release cumulative diff safe to inspect without implying that every package above belongs to every metric below. When that selected metric's blame window is narrower than the package comparison, Focus package diff on … resets From/To to the exact interval in one click. If package provenance is unavailable, or the selected endpoint stacks are identical, the section says so instead of describing the metric changes as effects of a package diff that was not observed. For a cumulative range with identical endpoints it also warns that an intermediate release may still have moved.
When the selected regression has a ranked blame sidecar, its AI-generated PR ranking appears directly below the trend. The same complete ledger and "suggestion, not proof" framing are reused from the Regressions tab; Stack Changes does not recompute or reinterpret the model output. When no ranking was stored, the tab says so explicitly instead of leaving an ambiguous blank.
The selected regression is part of the URL (reg_* parameters), including its
detector/sample identity, config, metric, region, exact onset run and whether
Whole platform is enabled. Sharing, reopening, or navigating back to the URL
therefore restores the exact investigation rather than the range's default
metric. An explicitly linked metric remains selectable even when it falls below
the normal worst-30 display cap. Historical report JSONs
for a cold range are fetched as one parallel batch; the selected trend still
downloads run data only when it is shown.
It compares releases, not run dates. The nightly build does not publish every day; a benchmark then re-uses the newest release available, so several consecutive run dates routinely share one identical stack — most run dates on EOS are not release dates at all, and only dates that have a release are selectable.
Picking two releases that are far apart gives the cumulative change across every release in between, which the header states explicitly — a month-wide diff looks no different from one night's in the table.
An empty diff is a result, not a blank: if two releases sit at the same commit for every tracked package, nothing upstream changed between them, so a metric that moved did so for another reason — the host, the sample, or noise.
The data comes from k4h_packages in each run's run_info.json, recorded from
CVMFS as the benchmark runs (see
file formats).
Releases benchmarked before provenance capture — or whose stack had already aged
off CVMFS when the history was backfilled — cannot be compared, and the tab says
so rather than showing an empty diff.
Overview tab¶
Where every other metric tab compares configs within the selected detector,
this one compares the detectors against each other — always on their
baseline config, for the sidebar-selected platform and sample, over the
sidebar's trend window (the same scoping as Run Trends, minus the detector —
and the window is anchored detector-independently, so switching the sidebar
detector never changes which detectors this tab shows).
It reads the same nightly _reports/{date}/report.json as the Regressions
tab — whose verdicts carry the raw nightly value of every run and per-event
metric for all detectors — so the whole comparison loads from one small JSON
per night, with no per-detector run downloads.
Four views, dispatched by the same View switcher as the other multi-view tabs
(one colour per detector, consistent across the figures). The selected view
rides in the URL as ?view=, so a copied link reopens the view it came from
along with the parameters only that view reads:
Like every tab-local view selector in the dashboard, the Overview switcher uses simple horizontal radio buttons under a bold View label. It is visually separate from a single bordered comparison toolbar. Time, Memory and Scale form its left-hand group; Regressions and the explicit Runs · ⚠ N unreliable choice form its right-hand group on the same row. When unreliable runs are present, that choice offers: Reliable only (the default) or All runs; affected dates and the Machine Info pointer live in the control's help instead of a separate banner. The rows wrap naturally on narrow screens, with no CSS override. Each detector family has its own vertical legend below the figure: the family is the heading and its versions sit directly beneath it, while every version remains individually toggleable. Legend columns are centre-anchored and move into additional rows when there are many families or unusually long labels, keeping the plot usable at narrower dashboard widths.
- Performance Trends — the two selected metrics side by side (CPU, Memory), one line per detector across every nightly tag in the sidebar's trend window (x-axis: the Key4hep nightly tag, like every other trend view; a nightly benchmarked twice collapses to one point, newest run wins), so cross-detector gaps can be tracked over time. Nights the regression detector confirmed a step are ringed in red on the lines, and nights it flagged but hasn't confirmed are ringed as ⚠️ watch points — both on by default, each behind its own toggle. The Scale switcher's Relative % option rescales each line to its first night = 100 %, making drift comparable across detectors of very different absolute cost; it sits on the page rather than in Display options because it changes what the numbers mean. Any scoped detector the chart has no line for is named with the reason — every run excluded as unreliable · no value for the selected metrics · ran but produced no comparable metrics (a hard-failed config) · no run in the window, with the night it last ran · not benchmarked with this sample/platform — so a missing detector doesn't read as a quiet one. The roster comes from the nightly reports' run groups, not from their metric values, which is what lets a detector whose config failed outright be named at all. It is stated whether or not there is a chart, since a window with nothing to draw is when it matters most. One gap remains: a detector retired past the engine's seven-day missing-run grace period is dropped from the reports entirely and cannot be named — widen the window to a range it still ran in to see it;
- Performance Landscape — the selected time metric against the selected
memory metric, one point per detector at its most recent run — closer to
the origin is faster and leaner. It follows the runs rather than the
reports, so it is unaffected by the Regression Status night on screen, and a
detector that missed the newest night keeps its last measured point (the
caption and the hover name its nightly tag) instead of dropping off the
chart; the same fallback applies to a run excluded by the reliability
toggle; a detector that ran but produced no usable point — a hard-failed
config, or one whose every run the toggle dropped — is named in the caption
rather than left out. That last run is found even when it falls outside the trend window
— a detector that has been missing for a few nights is shown at the night it
really last ran, not at wherever the window happens to end. Both coordinates
always come from one run, reruns of a nightly tag included. The metric selectors
offer mean/median event time, wall time or user CPU for time; mean event RSS
or peak RSS for memory (shown in GB), are shared with the trends view, and
the selection is shareable via
?tmetric=/?mmetric=; - Regression Status — since the Regressions tab is scoped to one
detector, the cross-detector regression picture lives here: a report
night picker (every fetched night that covers the scope, newest first,
each badged with its worst cross-detector state; defaults to the newest,
pinnable via
?report=— the same parameter the roster links and alert emails carry, and the trend window sets how far back it reaches; changing the sidebar platform or sample returns it to the newest night, since two scopes rarely flag their regression on the same one), a banner with that night's verdict counts across all scoped detectors (checked / 🔴 regressed / ⚠️ watch / ❌ failures), a per-detector status roster (badge, flag counts, worst flagged metric, and a link that opens the Regressions tab scoped to that detector and pinned to the selected night), and a flagged-metric trend that opens on the night's worst flag — the metric's history over the trend window (plus the selected night itself when it lies outside) with the baseline band its verdict was judged against and, for a confirmed step, the shaded blame window, all built from the cached reports with no run downloads. The view renders even on a night whose configs all hard-failed (when there are no values to plot), so a failure is never hidden behind an empty chart; - Nightly Report — one night's regression report in the form the Key4hep
e-group received it. Nothing is archived for it: the mail body is rebuilt
from the already-fetched report (plus that night's blame sidecar) by the same
renderer the nightly mail uses, so it is a reconstruction rather than a stored
copy — an old night is redrawn by today's renderer, and the mail's CI run
button is absent because that workflow-run URL is not stored in the report.
Its night picker reaches as far back as the trend window and is deep-linkable
via
?report=, the same parameter the Regressions tab and the alert emails already speak. Unlike every other view it is not scoped to the sidebar's platform/sample — the mail covers every detector, platform and sample the night benchmarked — which is why it stays reachable even when the selected scope has no data. Links open in a new tab.
Colours follow the detector family (ALLEGRO, CLD, …), with versions of one family distinguished by dash pattern and marker symbol, so experiment-level comparisons read at a glance. Runs that failed the host-reliability check are excluded by default with the same Reliable only / All runs selector as every historical view (the nightly report carries each night's per-detector verdict); their raw values are still recorded — as unjudged points, never flagged — so choosing All runs plots them like Run Trends does. One selector covers all three views, the Regression Status trend preview included, and it reaches every report in view — including nights outside the trend window, which the landscape and the night picker both read, and any the landscape reaches back to for a detector that has not run in a while. Exclusion drops the run, not the nightly tag: a tag benchmarked twice keeps the point measured by whichever rerun passed, and a 🔴/⚠️ marker leaves the chart with the run that earned it rather than ringing on a rerun that was never flagged.
A point on these charts is a Key4hep nightly tag, not a run — the unit the regression engine judges on — so where a tag was benchmarked more than once it carries the newest run's value and the worst verdict among its runs. A release that regressed is therefore still marked even when a later rerun of the same stack came out quiet. The per-run view is the Regressions tab's drill-down, where every run gets its own point and a flag sits only on the run that earned it; if that run was excluded or falls outside the window, the drill-down hides the marker and says why rather than moving it.
The landscape is the one chart that is a run, not a tag: its point comes from a single run of the detector's newest tag, so the two coordinates are always the same measurement even when a rerun re-measured only one of them. Value axes are logarithmic by default (the Scale switcher offers Linear as well) — the detectors span more than a decade in both time and memory, so a linear scale squashes the small ones into an unreadable cluster.
Detectors are only compared like-for-like: anything not benchmarked with the selected sample/platform is listed as excluded instead of silently plotted against a different workload. The values shown are the raw nightly measurements the regression engine judged — baselines and Δ-verdicts stay in the Regressions tab.
Running the dashboard locally¶
You can point the dashboard at either a local directory or the remote WebEOS
endpoint, via environment variables read by
dashboard/config.py:
| Variable | Default | Meaning |
|---|---|---|
K4BENCH_DATA_DIR |
logs |
local directory to read runs from |
K4BENCH_DATA_URL |
(unset) | WebEOS base URL; when set, overrides local reads |
K4BENCH_CACHE_DIR |
$TMPDIR/k4bench_cache |
on-disk cache for downloaded runs |
K4BENCH_DASHBOARD_URL |
https://k4bench-dashboard.app.cern.ch |
this deployment's own public URL; the Overview tab's Nightly Report view needs it because the embedded mail's deep links must be absolute |
The dashboard imports k4bench.analysis for its loaders, so a working k4bench
install is required.
How downloads are cached¶
Historical runs are immutable, so the dashboard downloads each run at most
once into K4BENCH_CACHE_DIR and reuses it across reruns. Downloads are staged
in a temp dir and published with a single atomic rename, so a reader never
sees a half-written run and an interrupted download leaves no partial run
behind. Filenames from the directory listing are validated (decoded, then
rejected if they aren't a single plain path component) to prevent path-traversal.
Details: Architecture → data flow.
Deployment¶
The dashboard is containerised (dashboard/Dockerfile), pushed to ghcr.io,
and deployed to CERN's OpenShift PaaS via openshift/ manifests
(Deployment/Service/Route) by the deploy-dashboard.yml workflow. The full
deployment path is in
Architecture → data flow.