Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions benchmarks/aggregator-head-lag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/aggrega

prometheus:
window: 24h
# Live-feed sanity gate. Sums event-arrival changes across every
# aggregator series in the last 15min. If this is 0, ALL our probes
# are silent (harness crash / Prom scrape failing / config drift) and
# the per-provider "Feed down" badges would be misleading, so the UI
# suppresses them. If > 0, at least one provider is actively receiving
# events from us, which means a provider showing 0 changes on its own
# is genuinely silent from Codex/Mobula/Gecko upstream.
probe_ok: sum(changes(head_lag_seconds[15m]))

faq:
- q: "Which crypto data API has the lowest latency right now?"
Expand Down Expand Up @@ -154,6 +162,12 @@ providers:
success: clamp_max(count_over_time(head_lag_seconds{aggregator="mobula"}[24h]) / 5760, 1)
sample_size: sum(count_over_time(head_lag_seconds{aggregator="mobula"}[24h]))
series: avg_over_time(head_lag_seconds{aggregator="mobula"}[1h]) * 1000
# Total gauge-value changes on this aggregator across chains and
# regions in the last 15min. Non-zero = fresh events arrived; 0 =
# every subscription has been silent for 15+ minutes (the gauge is
# frozen at its last value). Threshold is > 0 rather than a rate
# so quiet chains at night don't false-flag the aggregate view.
live_activity: sum(changes(head_lag_seconds{aggregator="mobula"}[15m]))
regions:
- region: us-east
p50: quantile_over_time(0.50, head_lag_seconds{aggregator="mobula", region="us-east"}[24h]) * 1000
Expand All @@ -177,6 +191,7 @@ providers:
success: clamp_max(count_over_time(head_lag_seconds{aggregator="codex"}[24h]) / 5760, 1)
sample_size: sum(count_over_time(head_lag_seconds{aggregator="codex"}[24h]))
series: avg_over_time(head_lag_seconds{aggregator="codex"}[1h]) * 1000
live_activity: sum(changes(head_lag_seconds{aggregator="codex"}[15m]))
regions:
- region: us-east
p50: quantile_over_time(0.50, head_lag_seconds{aggregator="codex", region="us-east"}[24h]) * 1000
Expand All @@ -200,6 +215,7 @@ providers:
success: clamp_max(count_over_time(head_lag_seconds{aggregator="geckoterminal"}[24h]) / 5760, 1)
sample_size: sum(count_over_time(head_lag_seconds{aggregator="geckoterminal"}[24h]))
series: avg_over_time(head_lag_seconds{aggregator="geckoterminal"}[1h]) * 1000
live_activity: sum(changes(head_lag_seconds{aggregator="geckoterminal"}[15m]))
regions:
- region: us-east
p50: quantile_over_time(0.50, head_lag_seconds{aggregator="geckoterminal", region="us-east"}[24h]) * 1000
Expand Down
31 changes: 31 additions & 0 deletions src/app/benchmarks/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,37 @@ export default async function BenchmarkPage({
</div>
)}

{/* Live-feed outage banner. Renders when the spec declared
live_activity queries and one or more providers came back
"down" (short-window activity = 0, bench-level probe_ok
confirmed our end is fine). Placed above the fold so a reader
landing on the page during an incident sees the caveat before
reading last-known percentiles as current truth. */}
{(() => {
const downProviders = benchmark.results.filter(
(r) => r.liveStatus === "down"
);
if (downProviders.length === 0) return null;
const names = downProviders.map((r) => r.name).join(", ");
return (
<div
role="status"
className="mt-6 max-w-3xl rounded-md border border-danger/40 bg-danger/10 px-4 py-3 text-[14px] leading-relaxed text-ink"
>
<p className="label-mono mb-1 text-danger">
Live feed silent: {names}
</p>
<p>
No new events received from{" "}
{downProviders.length === 1 ? "this feed" : "these feeds"} in
the last several minutes. Percentiles and rankings shown are
the last-known values from the 24-hour window and update as
soon as fresh events resume.
</p>
</div>
);
})()}

{/* SEO-tuned intro paragraph rendered server-side under the H1 so
long-tail query phrases land in the first ~200 words crawlers
weight heavily. Optional - omitted when the YAML doesn't set it. */}
Expand Down
8 changes: 8 additions & 0 deletions src/components/ledger-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,14 @@ function Row({
</span>
</Hint>
)}
{!isMuted && r.liveStatus === "down" && (
<Hint label="The provider's live feed has produced no new events in the last several minutes. The percentiles shown are the last-known values from the 24-hour window and will keep displaying until fresh events resume. Rolling reliability (Success column) barely moves on a short outage, which is why this pill exists.">
<span className="inline-flex items-center gap-1 shrink-0 font-sans text-[10px] uppercase tracking-[0.14em] text-ink-muted">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-[var(--color-danger,#b0402e)]" aria-hidden />
Feed down
</span>
</Hint>
)}
{!isMuted && r.dataConfidence === "low" && (
<Hint
label={
Expand Down
37 changes: 36 additions & 1 deletion src/lib/materialize/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ function applyDimensionsToSpec(spec: Spec, labels: Record<string, string>): Spec
success: inject(p.queries.success),
sample_size: inject(p.queries.sample_size),
series: inject(p.queries.series),
live_activity: inject(p.queries.live_activity),
regions: p.queries.regions?.map((r) => ({
...r,
p50: inject(r.p50),
Expand All @@ -546,6 +547,9 @@ function applyDimensionsToSpec(spec: Spec, labels: Record<string, string>): Spec
}
: p.queries,
})),
prometheus: spec.prometheus
? { ...spec.prometheus, probe_ok: inject(spec.prometheus.probe_ok) }
: spec.prometheus,
};
}

Expand Down Expand Up @@ -654,6 +658,19 @@ async function tryLoadLive(
const sevenDaysSec = 7 * 86_400;
const thirtyDaysSec = 30 * 86_400;

// Bench-level "is our end fine" gate. Fetched once per sweep; feeds
// every provider's liveStatus verdict below so a broken harness / Prom
// scrape can't fake-flag every provider as down at once. Absent when
// the spec doesn't declare probe_ok — in which case we trust each
// provider's live_activity unconditionally.
const probeOkQuery = spec.prometheus?.probe_ok;
const probeOk = probeOkQuery
? await prom.scalar(probeOkQuery)
: null;
// Interpret: >0 or null-when-not-declared → trust per-provider verdicts.
// Explicit 0 (or NaN) → the probe itself is down; suppress all badges.
const trustLiveVerdicts = !probeOkQuery || (probeOk != null && probeOk > 0);

for (const p of spec.providers) {
const q = p.queries;
if (!q) return null;
Expand All @@ -663,12 +680,13 @@ async function tryLoadLive(
q.p90 ? prom.scalar(q.p90) : Promise.resolve(null),
q.p99 ? prom.scalar(q.p99) : Promise.resolve(null),
]);
const [mean, success, sampleSize, slotP50, slotP99] = await Promise.all([
const [mean, success, sampleSize, slotP50, slotP99, liveActivity] = await Promise.all([
q.mean ? prom.scalar(q.mean) : Promise.resolve(null),
q.success ? prom.scalar(q.success) : Promise.resolve(null),
q.sample_size ? prom.scalar(q.sample_size) : Promise.resolve(null),
q.slot_p50 ? prom.scalar(q.slot_p50) : Promise.resolve(null),
q.slot_p99 ? prom.scalar(q.slot_p99) : Promise.resolve(null),
q.live_activity ? prom.scalar(q.live_activity) : Promise.resolve(null),
]);

// One retry on the load-bearing percentiles. A null here is either
Expand Down Expand Up @@ -734,6 +752,22 @@ async function tryLoadLive(
continue;
}

// liveStatus: only computed when the spec declares live_activity.
// "unknown" wins whenever we can't tell (probe_ok says our side is
// broken, or the activity query returned no sample) — never falls
// through to "down" on ambiguous data, since a false red pill on
// a live provider is worse than a missing pill on a real outage.
let liveStatus: "healthy" | "down" | "unknown" | undefined;
if (q.live_activity) {
if (!trustLiveVerdicts || liveActivity == null) {
liveStatus = "unknown";
} else if (liveActivity > 0) {
liveStatus = "healthy";
} else {
liveStatus = "down";
}
}

liveResults.push({
name: p.name,
slug: p.slug,
Expand All @@ -750,6 +784,7 @@ async function tryLoadLive(
secondary: p.secondary,
query: q.p50,
formula: p.formula,
liveStatus,
});

if (q.series) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const ProviderResultSchema = z.object({
meta: StalenessMetaSchema.optional(),
query: z.string().optional(),
formula: z.string().optional(),
liveStatus: z.enum(["healthy", "down", "unknown"]).optional(),
});

const RegionPointSchema = z.object({
Expand Down
17 changes: 17 additions & 0 deletions src/lib/spec-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ const queries = z
success: promql.optional(),
sample_size: promql.optional(),
series: promql.optional(),
/** Short-window "is this provider's live feed producing new events
* right now" probe. Instant query returning a scalar count > 0 when
* fresh events arrived in the past few minutes, 0 when the source
* is silent. Distinct from `success` (24h rolling reliability),
* which is too slow to move on a 30-60 min outage. Rendered as a
* "Feed down" pill + top-of-page banner when 0 and probe_ok
* confirms our end is fine. */
live_activity: promql.optional(),
/** Optional slot-level companion queries. Solana-native benches set
* these to surface slot_delta p50/p99 alongside the ms columns. The
* ms numbers are wall-clock derived; slot_delta is the canonical
Expand Down Expand Up @@ -309,6 +317,15 @@ export const SpecSchema = z
.string()
.regex(/^[a-zA-Z_:][a-zA-Z0-9_:]*$/, "Must be a bare metric name")
.optional(),
/** Bench-level sanity check for the per-provider `live_activity`
* probe: an instant query that must be > 0 for the "Feed down"
* UI to trust its per-provider verdicts. Meant to answer "are
* ANY of our probes still emitting". Zero means our end
* (harness / Prom scrape) is broken, not the provider — so the
* UI suppresses all per-provider down badges to avoid falsely
* blaming every source at once. When omitted, per-provider
* activity is trusted unconditionally. */
probe_ok: promql.optional(),
})
.optional(),

Expand Down
14 changes: 14 additions & 0 deletions src/types/benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ export type ProviderResult = {
* headline value is computed. Rendered as the leaderboard-row
* hover tooltip. Authored per-bench in YAML (provider.formula). */
formula?: string;
/** Short-window liveness verdict derived at load time from the spec's
* `queries.live_activity` scalar and the bench-level `probe_ok`
* gate. Only populated when the spec declares those queries.
* - "healthy": recent events arrived (activity > 0).
* - "down": no recent events AND probe_ok confirmed our end is
* fine — the provider's live feed is silent, values
* shown are last-known. Renderers should badge it.
* - "unknown": probe_ok reports our side is broken (harness or
* Prom scrape), OR the activity query itself
* returned no sample. Suppress the badge either way
* so a probe hiccup can't fake-flag every provider.
* Absent when the spec doesn't declare live_activity; UI must
* behave identically to today for those benches. */
liveStatus?: "healthy" | "down" | "unknown";
};

/** One provider's standing inside a (chain, region) ranking cell. */
Expand Down
Loading