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
102 changes: 39 additions & 63 deletions src/components/data-api-bench-groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,9 @@ function BenchRow({
{/* Region / chain breakdown */}
<td className="pl-3 pr-5 py-3.5 align-top hidden lg:table-cell">
{showRegionChain ? (
<div className="flex flex-col gap-2">
{hasRegions && (
<div className="flex flex-col gap-1">
<span className="label-mono text-[8px] text-ink-faint uppercase tracking-wider" style={{ fontFamily: "var(--font-mono, monospace)" }}>Region</span>
<RegionStrip leaders={bench.regionLeaders} unit={bench.unit} />
</div>
)}
{hasChains && (
<div className="flex flex-col gap-1">
<span className="label-mono text-[8px] text-ink-faint uppercase tracking-wider" style={{ fontFamily: "var(--font-mono, monospace)" }}>Chain</span>
<ChainStrip leaders={bench.chainLeaders} unit={bench.unit} />
</div>
)}
<div className="flex flex-wrap gap-1.5">
{hasRegions && <RegionStrip leaders={bench.regionLeaders} unit={bench.unit} />}
{hasChains && <ChainStrip leaders={bench.chainLeaders} unit={bench.unit} />}
</div>
) : (
<span className="text-ink-faint text-[11px]">global</span>
Expand All @@ -200,7 +190,7 @@ function RegionStrip({
);

return (
<div className="flex flex-wrap gap-1.5">
<>
{sorted.map((l) => (
<Link
key={l.region}
Expand All @@ -226,7 +216,7 @@ function RegionStrip({
</span>
</Link>
))}
</div>
</>
);
}

Expand All @@ -240,14 +230,14 @@ function ChainStrip({
const uniqueWinners = new Set(leaders.map((l) => l.providerSlug)).size;
const isSpecialist = uniqueWinners === leaders.length && leaders.length > 3;

if (isSpecialist) {
// Each chain has its own specialist — compact icon-only chips
const MAX = 5;
const visible = leaders.slice(0, MAX);
const overflow = leaders.length - MAX;
return (
<div className="flex flex-wrap gap-1 items-center">
{visible.map((l) => (
const MAX = isSpecialist ? 5 : 3;
const visible = leaders.slice(0, MAX);
const overflow = leaders.length - MAX;

return (
<>
{visible.map((l) =>
isSpecialist ? (
<Link
key={l.chain}
href={`/products/${l.providerSlug}`}
Expand All @@ -262,50 +252,36 @@ function ChainStrip({
</span>
<ProviderLogo slug={l.providerSlug} name={l.providerName} size={10} />
</Link>
))}
{overflow > 0 && (
<span className="text-[9px] text-ink-faint">+{overflow}</span>
)}
</div>
);
}

// Some provider dominates multiple chains — full pills capped at 3
const MAX = 3;
const visible = leaders.slice(0, MAX);
const overflow = leaders.length - MAX;

return (
<div className="flex flex-wrap gap-1.5 items-center">
{visible.map((l) => (
<Link
key={l.chain}
href={`/products/${l.providerSlug}`}
className="flex items-center gap-1 rounded-md border border-ink/8 bg-paper-soft/60 px-2 py-1 hover:border-ink/20 hover:bg-paper-soft transition-colors"
title={`${l.label}: ${l.providerName} — ${fmtDataValue(l.p50, unit)}`}
>
<span
className="label-mono text-[9px] text-ink-faint uppercase tracking-wide"
style={{ fontFamily: "var(--font-mono, monospace)" }}
>
{l.label}
</span>
<ProviderLogo slug={l.providerSlug} name={l.providerName} size={12} />
<span className="text-[10.5px] text-ink-soft font-medium">
{l.providerName.split(" ")[0]}
</span>
<span
className="label-mono text-[10px] text-ink-faint"
style={{ fontFamily: "var(--font-mono, monospace)" }}
) : (
<Link
key={l.chain}
href={`/products/${l.providerSlug}`}
className="flex items-center gap-1 rounded-md border border-ink/8 bg-paper-soft/60 px-2 py-1 hover:border-ink/20 hover:bg-paper-soft transition-colors"
title={`${l.label}: ${l.providerName} — ${fmtDataValue(l.p50, unit)}`}
>
{fmtDataValue(l.p50, unit)}
</span>
</Link>
))}
<span
className="label-mono text-[9px] text-ink-faint uppercase tracking-wide"
style={{ fontFamily: "var(--font-mono, monospace)" }}
>
{l.label}
</span>
<ProviderLogo slug={l.providerSlug} name={l.providerName} size={12} />
<span className="text-[10.5px] text-ink-soft font-medium">
{l.providerName.split(" ")[0]}
</span>
<span
className="label-mono text-[10px] text-ink-faint"
style={{ fontFamily: "var(--font-mono, monospace)" }}
>
{fmtDataValue(l.p50, unit)}
</span>
</Link>
)
)}
{overflow > 0 && (
<span className="text-[10px] text-ink-faint">+{overflow}</span>
)}
</div>
</>
);
}

Expand Down
57 changes: 49 additions & 8 deletions src/lib/perp-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,12 +314,13 @@ async function fetchPerpCohortRaw(): Promise<PerpCohortSummary | null> {
const snapshot = await readCohortSnapshot<PerpCohortSummary>(
PERP_COHORT_KEY,
);
// Only use the snapshot if it was written within the last 2 minutes.
// Without this check, the 24h KV TTL means stale Prometheus data is
// served indefinitely even though the harness publishes every minute.
// Fresh snapshot (written within the last 2 min) is the fast path.
if (snapshot && snapshot.ageMs < 2 * 60 * 1000) {
return snapshot.data;
}
// Snapshot stale or missing — try live Prom. On Vercel, promUrl()
// returns null (VPS Prom is not publicly exposed), so this only
// succeeds from the worker process.
const fresh = await fetchPerpCohortFresh();
if (fresh) {
try {
Expand All @@ -331,18 +332,41 @@ async function fetchPerpCohortRaw(): Promise<PerpCohortSummary | null> {
}`,
);
}
return fresh;
}
return fresh;
// Both paths failed. Serve the stale snapshot as a last resort so a
// brief worker hiccup or Vercel cache-miss race doesn't blank the
// page. Cap at 10 min: beyond that we surface "unavailable" so a
// sustained worker outage is still visible.
if (snapshot && snapshot.ageMs < 10 * 60 * 1000) {
console.warn(
`perp-cohort: serving stale snapshot (${Math.round(snapshot.ageMs / 1000)}s old)`,
);
return snapshot.data;
}
return null;
}

const fetchPerpCohortCached = unstable_cache(
fetchPerpCohortRaw,
async () => {
const data = await fetchPerpCohortRaw();
// Throw on null so unstable_cache does not store a cached null —
// a single worker hiccup would otherwise blank the page for the
// full 60 s revalidate window. The public wrapper catches and
// returns null so the page still renders its fallback banner.
if (data === null) throw new Error("perp-cohort-unavailable");
return data;
},
["perp-cohort-v1"],
{ revalidate: 60, tags: ["perp-cohort"] },
);

export async function fetchPerpCohort(): Promise<PerpCohortSummary | null> {
return fetchPerpCohortCached();
try {
return await fetchPerpCohortCached();
} catch {
return null;
}
}

/**
Expand Down Expand Up @@ -453,18 +477,35 @@ async function fetchPerpByAssetMatrixRaw(): Promise<PerpAssetRow[]> {
}`,
);
}
return fresh;
}
// Prom failed — serve stale snapshot up to 10 min rather than empty.
if (snapshot && snapshot.ageMs < 10 * 60 * 1000 && Array.isArray(snapshot.data) && snapshot.data.length > 0) {
console.warn(
`perp-by-asset: serving stale snapshot (${Math.round(snapshot.ageMs / 1000)}s old)`,
);
return snapshot.data;
}
return fresh;
}

const fetchPerpByAssetMatrixCached = unstable_cache(
fetchPerpByAssetMatrixRaw,
async () => {
const data = await fetchPerpByAssetMatrixRaw();
// Don't cache an empty result — same rationale as perp-cohort above.
if (data.length === 0) throw new Error("perp-by-asset-unavailable");
return data;
},
["perp-by-asset-matrix-v2"],
{ revalidate: 120, tags: ["perp-by-asset"] },
);

export async function fetchPerpByAssetMatrix(): Promise<PerpAssetRow[]> {
return fetchPerpByAssetMatrixCached();
try {
return await fetchPerpByAssetMatrixCached();
} catch {
return [];
}
}

/**
Expand Down
Loading