Skip to content

Commit 421098b

Browse files
committed
[claude] benchmarks-website v3: per-group descriptions + partial-coverage commits
Two independent fixes for the v3 server: Task A - per-group hover descriptions ===================================== Port v2's `BENCHMARK_DESCRIPTIONS` + `getBenchmarkDescription` strings into the v3 server. Adds an editorial `description: Option<String>` field on `Group` and `GroupChartsResponse`, populated from a small hand-maintained table in `api/descriptions.rs`. TPC-H / TPC-DS descriptions are derived from the parsed group name so we don't need one entry per (storage, sf) pair (TPC-H carries the scale-bytes annotation; TPC-DS does not, matching v2 verbatim). The landing page renders an info icon (cursor-help, ⓘ) next to every group title with a description, surfacing the text via a CSS-only `data-tooltip` pseudo-element (visible on hover *and* focus, with `role="note"` + `aria-label` for screen readers and keyboards). The `/group/{slug}` permalink page renders the same icon next to the chart-meta header. Vector-search groups have no canonical description in v2; the icon is omitted for those rather than fabricated. Task B - render commits with partial / missing series data ========================================================== Symptom: charts had invisible gaps where commits should be. Diagnosis: 1. `SeriesAccumulator::ensure_commit` only registered a commit *after* a series produced a row, so commits with zero rows in the chart's fact table were silently dropped from `commits[]`. 2. The client-side renderer set `spanGaps: true` on every dataset, so surviving nulls were drawn over as continuous lines. Server fix: each `collect_*_chart` now seeds the accumulator from a `commits`-dim pre-pass scoped to "every commit in the requested `CommitWindow` whose timestamp is at or after the earliest commit that has a row in this chart's fact table." Commits with zero fact rows appear in `commits[]` with `null` for every series; commits older than the bench's first row stay excluded so we never render pre-history. Client fix: `spanGaps: false` on every dataset so missing measurements render as visible gaps. The external tooltip already cleanly skipped null rows via `.filter(Boolean)` — preserved that behaviour. LTTB still operates on the union of non-null x-positions; below the cap every sparse-series commit is kept (so a series with a few non-null values across many commits still renders connected points). Tests ===== `tests/web_ui.rs` adds: * description rendering on the landing page and `/group/{slug}` (asserts the verbatim v2 strings + the SF-derived TPC blurbs) * `groups_api_carries_description_field` (the wire shape) * `vector_search_group_has_no_description_icon` (no description ⇒ no icon) * `chart_includes_commits_with_partial_series_coverage` (regression: B with only series Y appears in `commits[]` with `null` for X) * `chart_includes_commits_with_zero_rows_in_fact_table` * `chart_excludes_commits_before_first_fact_row` Existing landing/chart-page snapshots are unchanged in behaviour but pick up the new info-icon markup; bumping `STATIC_ASSET_VERSION` to `bench-v3-ui-18` so cached browsers see the new chart-init.js + style.css. Local cargo checks were skipped; relying on GitHub Actions to validate. Signed-off-by: Claude <noreply@anthropic.com>
1 parent afec890 commit 421098b

12 files changed

Lines changed: 1074 additions & 108 deletions

File tree

benchmarks-website/server/src/api/charts.rs

Lines changed: 206 additions & 82 deletions
Large diffs are not rendered by default.
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Editorial group descriptions, ported from v2.
5+
//!
6+
//! These strings are the source of truth for the hover tooltip rendered on
7+
//! every `<details>` group title on the landing page and on the
8+
//! `/group/{slug}` permalink. They are deliberately editorial and
9+
//! hand-maintained — derived from the group *name*, not from the database —
10+
//! so that adding a new group's blurb is a one-line edit here rather than a
11+
//! schema or ingest change.
12+
//!
13+
//! Source of truth in v2 (kept verbatim where applicable):
14+
//! - `benchmarks-website/src/utils.js` — `getBenchmarkDescription`
15+
//! - `benchmarks-website/src/config.js` — `BENCHMARK_DESCRIPTIONS`,
16+
//! `QUERY_SUITES.description`
17+
//!
18+
//! TPC-H / TPC-DS fan out by storage and scale factor; the description is
19+
//! synthesised from the parsed name so we don't have to hand-maintain one
20+
//! entry per `(storage, sf)` pair.
21+
22+
/// Look up a short editorial description for a group display name. Returns
23+
/// `None` when the group has no canonical description (e.g. vector-search
24+
/// groups) — callers render the title without a tooltip in that case.
25+
pub fn group_description(name: &str) -> Option<String> {
26+
if let Some(d) = tpc_description(name) {
27+
return Some(d);
28+
}
29+
static_description(name).map(str::to_string)
30+
}
31+
32+
/// Hard-coded, name-keyed descriptions for the non-fan-out groups. These
33+
/// match v2 verbatim where v2 had a description; new strings here should
34+
/// match the wording style v2 set.
35+
fn static_description(name: &str) -> Option<&'static str> {
36+
match name {
37+
"Random Access" => Some(
38+
"Tests performance of selecting arbitrary row indices from a file on NVMe storage",
39+
),
40+
"Compression" => Some(
41+
"Measures encoding and decoding throughput (MB/s) for Vortex files and Parquet \
42+
files (with zstd page compression)",
43+
),
44+
"Compression Size" => Some(
45+
"Compares compressed file sizes and compression ratios across different encoding \
46+
strategies",
47+
),
48+
"Clickbench" => Some(
49+
"ClickHouse's analytical benchmark suite testing real-world query patterns on web \
50+
analytics data",
51+
),
52+
"Statistical and Population Genetics" => Some(
53+
"A suite of Statistical and Population genetics queries using the gnomAD dataset",
54+
),
55+
"PolarSignals Profiling" => Some(
56+
"Profiling data benchmark modeled on PolarSignals/Parca, exercising scan-layer \
57+
performance with projection and filter pushdown on deeply nested schemas",
58+
),
59+
_ => None,
60+
}
61+
}
62+
63+
/// Derive a description for `TPC-H (NVMe|S3) (SF=N)` and `TPC-DS (NVMe) (SF=N)`
64+
/// group names. The shape is fixed because [`crate::api::groups::group_name_query`]
65+
/// emits exactly this format for tpch/tpcds. Returns `None` for any name that
66+
/// does not start with `TPC-H ` or `TPC-DS `.
67+
fn tpc_description(name: &str) -> Option<String> {
68+
let parts = if let Some(rest) = name.strip_prefix("TPC-H ") {
69+
Some(("TPC-H", rest))
70+
} else {
71+
name.strip_prefix("TPC-DS ").map(|rest| ("TPC-DS", rest))
72+
};
73+
let (suite, rest) = parts?;
74+
let storage = if rest.starts_with("(NVMe)") {
75+
"nvme"
76+
} else if rest.starts_with("(S3)") {
77+
"s3"
78+
} else {
79+
return None;
80+
};
81+
let sf = parse_sf(rest)?;
82+
Some(format_tpc(suite, storage, &sf))
83+
}
84+
85+
/// Pull `SF=N` (digits only) out of strings like `(NVMe) (SF=10)`. Returns
86+
/// `None` if no `SF=` substring or the digits don't parse.
87+
fn parse_sf(s: &str) -> Option<String> {
88+
let after = s.split_once("SF=")?.1;
89+
let digits: String = after.chars().take_while(char::is_ascii_digit).collect();
90+
if digits.is_empty() {
91+
None
92+
} else {
93+
Some(digits)
94+
}
95+
}
96+
97+
/// Render the v2-compatible TPC blurb. Storage label comes from the parsed
98+
/// group name; scale-bytes annotation only renders for TPC-H (TPC-DS in v2
99+
/// did not annotate scale bytes).
100+
fn format_tpc(suite: &str, storage: &str, sf: &str) -> String {
101+
let storage_phrase = match storage {
102+
"nvme" => "on local NVMe storage",
103+
"s3" => "against S3 storage",
104+
_ => "on local NVMe storage",
105+
};
106+
let bytes = match sf {
107+
"1" => Some("1GB"),
108+
"10" => Some("10GB"),
109+
"100" => Some("100GB"),
110+
"1000" => Some("1TB"),
111+
_ => None,
112+
};
113+
match (suite, bytes) {
114+
("TPC-H", Some(b)) => format!(
115+
"TPC-H benchmark queries {storage_phrase} at SF={sf} (~{b} of data)",
116+
),
117+
("TPC-H", None) => format!("TPC-H benchmark queries {storage_phrase} at SF={sf}"),
118+
("TPC-DS", _) => format!("TPC-DS benchmark queries {storage_phrase} at SF={sf}"),
119+
_ => format!("{suite} benchmark queries {storage_phrase} at SF={sf}"),
120+
}
121+
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use super::*;
126+
127+
#[test]
128+
fn static_descriptions_match_v2() {
129+
assert_eq!(
130+
group_description("Random Access").as_deref(),
131+
Some("Tests performance of selecting arbitrary row indices from a file on NVMe storage"),
132+
);
133+
assert_eq!(
134+
group_description("Compression").as_deref(),
135+
Some(
136+
"Measures encoding and decoding throughput (MB/s) for Vortex files and Parquet \
137+
files (with zstd page compression)",
138+
),
139+
);
140+
assert_eq!(
141+
group_description("Compression Size").as_deref(),
142+
Some(
143+
"Compares compressed file sizes and compression ratios across different encoding \
144+
strategies",
145+
),
146+
);
147+
assert_eq!(
148+
group_description("Clickbench").as_deref(),
149+
Some(
150+
"ClickHouse's analytical benchmark suite testing real-world query patterns on \
151+
web analytics data",
152+
),
153+
);
154+
assert_eq!(
155+
group_description("Statistical and Population Genetics").as_deref(),
156+
Some(
157+
"A suite of Statistical and Population genetics queries using the gnomAD dataset",
158+
),
159+
);
160+
assert_eq!(
161+
group_description("PolarSignals Profiling").as_deref(),
162+
Some(
163+
"Profiling data benchmark modeled on PolarSignals/Parca, exercising scan-layer \
164+
performance with projection and filter pushdown on deeply nested schemas",
165+
),
166+
);
167+
}
168+
169+
#[test]
170+
fn tpch_descriptions_carry_scale_bytes() {
171+
assert_eq!(
172+
group_description("TPC-H (NVMe) (SF=1)").as_deref(),
173+
Some("TPC-H benchmark queries on local NVMe storage at SF=1 (~1GB of data)"),
174+
);
175+
assert_eq!(
176+
group_description("TPC-H (S3) (SF=10)").as_deref(),
177+
Some("TPC-H benchmark queries against S3 storage at SF=10 (~10GB of data)"),
178+
);
179+
assert_eq!(
180+
group_description("TPC-H (NVMe) (SF=100)").as_deref(),
181+
Some("TPC-H benchmark queries on local NVMe storage at SF=100 (~100GB of data)"),
182+
);
183+
assert_eq!(
184+
group_description("TPC-H (S3) (SF=1000)").as_deref(),
185+
Some("TPC-H benchmark queries against S3 storage at SF=1000 (~1TB of data)"),
186+
);
187+
}
188+
189+
#[test]
190+
fn tpcds_descriptions_omit_scale_bytes() {
191+
assert_eq!(
192+
group_description("TPC-DS (NVMe) (SF=1)").as_deref(),
193+
Some("TPC-DS benchmark queries on local NVMe storage at SF=1"),
194+
);
195+
assert_eq!(
196+
group_description("TPC-DS (NVMe) (SF=10)").as_deref(),
197+
Some("TPC-DS benchmark queries on local NVMe storage at SF=10"),
198+
);
199+
}
200+
201+
#[test]
202+
fn unknown_groups_have_no_description() {
203+
assert_eq!(group_description("cohere-large-10m / partitioned"), None);
204+
assert_eq!(group_description("Made-up benchmark"), None);
205+
}
206+
207+
#[test]
208+
fn malformed_tpc_names_fall_through() {
209+
// No `(NVMe)` / `(S3)` prefix → not matched.
210+
assert_eq!(group_description("TPC-H something else"), None);
211+
// SF= without digits → not matched.
212+
assert_eq!(group_description("TPC-H (NVMe) (SF=)"), None);
213+
}
214+
}

benchmarks-website/server/src/api/dto.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ pub struct GroupsResponse {
6161
}
6262

6363
/// One group: a display name, a slug for the group permalink, and the chart
64-
/// links inside it. Optionally carries a v2-compatible rollup summary.
64+
/// links inside it. Optionally carries a v2-compatible rollup summary and a
65+
/// short editorial description (rendered as a hover tooltip on the
66+
/// disclosure title).
6567
#[derive(Debug, Serialize)]
6668
pub struct Group {
6769
/// Human-readable group label rendered in the disclosure header.
@@ -73,6 +75,12 @@ pub struct Group {
7375
/// Optional v2-compatible rollup computed from the fact tables.
7476
#[serde(skip_serializing_if = "Option::is_none")]
7577
pub summary: Option<Summary>,
78+
/// Short editorial description ported from v2's `BENCHMARK_DESCRIPTIONS`
79+
/// + `getBenchmarkDescription`. Rendered as a hover tooltip on the
80+
/// disclosure title; absent when no description exists for this group
81+
/// name (e.g. vector-search groups).
82+
#[serde(skip_serializing_if = "Option::is_none")]
83+
pub description: Option<String>,
7684
}
7785

7886
/// All charts in one group, returned by `GET /api/group/{slug}`.
@@ -83,6 +91,9 @@ pub struct GroupChartsResponse {
8391
/// Optional v2-compatible rollup computed from the fact tables.
8492
#[serde(skip_serializing_if = "Option::is_none")]
8593
pub summary: Option<Summary>,
94+
/// Optional editorial description, mirroring [`Group::description`].
95+
#[serde(skip_serializing_if = "Option::is_none")]
96+
pub description: Option<String>,
8697
/// Every chart inside the group, with full payload inlined.
8798
pub charts: Vec<NamedChartResponse>,
8899
}

benchmarks-website/server/src/api/groups.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use anyhow::Context as _;
1111
use anyhow::Result;
1212
use duckdb::Connection;
1313

14+
use super::descriptions::group_description;
1415
use super::dto::ChartLink;
1516
use super::dto::Group;
1617
use super::dto::group_sort_key;
@@ -42,6 +43,7 @@ pub(crate) fn collect_groups(conn: &Connection) -> Result<Vec<Group>> {
4243
let key = GroupKey::from_slug(&group.slug)
4344
.with_context(|| format!("invalid generated group slug: {}", group.slug))?;
4445
group.summary = collect_group_summary(conn, &key, &group.charts)?;
46+
group.description = group_description(&group.name);
4547
}
4648

4749
// Apply canonical ordering. `sort_by_key` is stable, so groups whose
@@ -96,6 +98,7 @@ fn collect_query_groups(conn: &Connection) -> Result<Vec<Group>> {
9698
slug: group_slug,
9799
charts: Vec::new(),
98100
summary: None,
101+
description: None,
99102
});
100103
current = Some(key);
101104
}
@@ -213,6 +216,7 @@ fn collect_compression_time_group(conn: &Connection) -> Result<Option<Group>> {
213216
slug: GroupKey::CompressionTimeGroup.to_slug(),
214217
charts,
215218
summary: None,
219+
description: None,
216220
}))
217221
}
218222
}
@@ -258,6 +262,7 @@ fn collect_compression_size_group(conn: &Connection) -> Result<Option<Group>> {
258262
slug: GroupKey::CompressionSizeGroup.to_slug(),
259263
charts,
260264
summary: None,
265+
description: None,
261266
}))
262267
}
263268
}
@@ -287,6 +292,7 @@ fn collect_random_access_group(conn: &Connection) -> Result<Option<Group>> {
287292
slug: GroupKey::RandomAccessGroup.to_slug(),
288293
charts,
289294
summary: None,
295+
description: None,
290296
}))
291297
}
292298
}
@@ -324,6 +330,7 @@ fn collect_vector_search_groups(conn: &Connection) -> Result<Vec<Group>> {
324330
slug: group_slug,
325331
charts: Vec::new(),
326332
summary: None,
333+
description: None,
327334
});
328335
current = Some(key);
329336
}

benchmarks-website/server/src/api/mod.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
//! [`crate::slug::ChartKey`] / [`crate::slug::GroupKey`].
1010
//!
1111
//! Submodules:
12-
//! - [`mod@dto`] — every wire-shape struct (`Group`, `ChartResponse`, …).
13-
//! - [`mod@window`] — [`CommitWindow`] + [`ChartQuery`].
14-
//! - [`mod@groups`] — discovery passes that build the group / chart-link tree.
15-
//! - [`mod@summary`] — v2-compatible per-group rollups.
16-
//! - [`mod@charts`] — `chart_payload` + the per-fact-table `collect_*_chart`
12+
//! - [`mod@dto`] — every wire-shape struct (`Group`, `ChartResponse`, …).
13+
//! - [`mod@window`] — [`CommitWindow`] + [`ChartQuery`].
14+
//! - [`mod@groups`] — discovery passes that build the group / chart-link tree.
15+
//! - [`mod@summary`] — v2-compatible per-group rollups.
16+
//! - [`mod@charts`] — `chart_payload` + the per-fact-table `collect_*_chart`
1717
//! functions and their shared `SeriesAccumulator`.
18-
//! - [`mod@filter`] — chip-universe collection for the global filter bar.
18+
//! - [`mod@filter`] — chip-universe collection for the global filter bar.
19+
//! - [`mod@descriptions`] — editorial blurbs surfaced as hover tooltips.
1920
2021
pub mod charts;
22+
pub mod descriptions;
2123
pub mod dto;
2224
pub mod filter;
2325
pub mod groups;
@@ -34,6 +36,7 @@ use duckdb::Connection;
3436

3537
pub(crate) use self::charts::chart_payload;
3638
pub(crate) use self::charts::collect_group_charts;
39+
pub use self::descriptions::group_description;
3740
pub use self::dto::ChartLink;
3841
pub use self::dto::ChartResponse;
3942
pub use self::dto::CommitPoint;

benchmarks-website/server/src/html/chart.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use maud::PreEscaped;
1212
use maud::html;
1313

1414
use super::landing::downsample_badge_slot;
15+
use super::landing::group_description_icon;
1516
use super::render::escape_json_for_script;
1617
use super::summary::summary_markup;
1718
use super::toolbar::per_chart_toolbar;
@@ -55,6 +56,7 @@ pub(super) fn group_body(group: &GroupChartsResponse) -> Markup {
5556
html! {
5657
p.chart-meta {
5758
(chart_count) " chart" @if chart_count != 1 { "s" }
59+
(group_description_icon(group.description.as_deref()))
5860
}
5961
(summary_markup(group.summary.as_ref()))
6062
div.chart-grid {

0 commit comments

Comments
 (0)