diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 44d889d04..9c3db7c31 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -1648,6 +1648,7 @@ [data-section="leaderboard"], [data-section="market-share"], [data-section="token-cost"], + [data-section="cache-ratio"], [data-section="session-cost"] ) { position: relative; @@ -2122,7 +2123,8 @@ white-space: nowrap; } -[data-page="stats"] [data-component="token-cost"] { +[data-page="stats"] [data-component="token-cost"], +[data-page="stats"] [data-component="cache-ratio"] { position: relative; display: grid; gap: 8px; @@ -2611,6 +2613,7 @@ [data-page="stats"] [data-section="leaderboard"], [data-page="stats"] [data-section="market-share"], [data-page="stats"] [data-section="token-cost"], + [data-page="stats"] [data-section="cache-ratio"], [data-page="stats"] [data-section="session-cost"] { padding: 64px 32px; } @@ -2742,6 +2745,7 @@ [data-page="stats"] [data-section="leaderboard"], [data-page="stats"] [data-section="market-share"], [data-page="stats"] [data-section="token-cost"], + [data-page="stats"] [data-section="cache-ratio"], [data-page="stats"] [data-section="session-cost"] { padding: 48px 24px; } diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index d5a5101c4..1cec0f629 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -9,6 +9,7 @@ import opencodeWordmarkDark from "../asset/logo-ornate-dark.svg" import statsUnfurlRankings from "../asset/unfurl-rankings.png?url" import { getStatsHomeData, + type CacheRatioEntry, type LeaderboardEntry, type MarketDay, type StatsHomeData, @@ -38,8 +39,9 @@ const statsUnfurlAlt = "OpenCode Stats wordmark on a dark patterned background" const headerLinks = [ { href: "#top-models", label: "Top Models" }, { href: "#leaderboard", label: "Leaderboard" }, - { href: "#token-cost", label: "Token Cost" }, { href: "#session-cost", label: "Session Cost" }, + { href: "#token-cost", label: "Token Cost" }, + { href: "#cache-ratio", label: "Cache Ratio" }, { href: "#market-share", label: "Market Share" }, ] as const const githubLink = { @@ -160,8 +162,9 @@ export default function StatsHome() { <> - + + )} @@ -1000,7 +1003,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) { setInspecting(false) }} > - + - + 0} @@ -1351,6 +1354,90 @@ function TokenCostChart(props: { ) } +function CacheRatioSection(props: { data: StatsHomeData["cacheRatio"] }) { + const [product, setProduct] = createSignal("Go") + const [activeIndex, setActiveIndex] = createSignal(2) + const data = createMemo(() => props.data[product()]) + const visible = createMemo(() => data().slice(0, 16)) + const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(visible().length - 1, 0))) + + return ( +
+ + + 0} + fallback={ + + } + > + + + +
+ ) +} + +function CacheRatioChart(props: { + data: CacheRatioEntry[] + activeIndex: number + onActiveIndexChange: (index: number) => void +}) { + const max = createMemo(() => Math.max(0, ...props.data.map((item) => item.ratio)) || 100) + const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0]) + + return ( +
+ + {(item, index) => ( + + )} + + + {(item) => ( +
+

+ Cache Ratio + {formatRatio(item().ratio)} +

+

+ Cached + {formatBillions(item().cached)} +

+

+ Uncached + {formatBillions(item().uncached)} +

+
+ )} +
+
+ ) +} + +function formatRatio(value: number) { + return `${value.toFixed(value > 0 && value < 10 ? 1 : 0)}%` +} + function formatDollars(value: number) { return `$${value.toFixed(2)}` } @@ -1378,7 +1465,7 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) { return (
- + 0} @@ -1635,8 +1722,9 @@ function Footer(props: { const modelStats = [ { href: "#top-models", label: "Top Models" }, { href: "#leaderboard", label: "Leaderboard" }, - { href: "#token-cost", label: "Token Cost" }, { href: "#session-cost", label: "Session Cost" }, + { href: "#token-cost", label: "Token Cost" }, + { href: "#cache-ratio", label: "Cache Ratio" }, { href: "#market-share", label: "Market Share" }, ] const legal = [ diff --git a/packages/stats/core/src/domain/home.ts b/packages/stats/core/src/domain/home.ts index db305a8ce..f93b5904d 100644 --- a/packages/stats/core/src/domain/home.ts +++ b/packages/stats/core/src/domain/home.ts @@ -11,6 +11,7 @@ export type UsagePoint = { date: string; segments: { model: string; value: numbe export type MarketDay = { date: string; total: number; authors: { author: string; share: number; tokens: number }[] } export type LeaderboardEntry = { model: string; author: string; tokens: number; change: number; rank: number } export type TokenCostEntry = { model: string; total: number; input: number; output: number; cached: number } +export type CacheRatioEntry = { model: string; ratio: number; cached: number; uncached: number; total: number } export type SessionCostEntry = { model: string; cost: number; tokens: number } export type CountryEntry = { country: string; continent: string; tokens: number; share: number; rank: number } export type StatsHomeData = { @@ -19,6 +20,7 @@ export type StatsHomeData = { leaderboard: Record> market: Record tokenCost: Record + cacheRatio: Record sessionCost: Record country: Record } @@ -99,6 +101,9 @@ function buildStatsHomeData( tokenCost: createTokenProductRecord((product) => buildTokenCost(normalized, product, getWindow("1W", earliest, latest)), ), + cacheRatio: createTokenProductRecord((product) => + buildCacheRatio(normalized, product, getWindow("1W", earliest, latest)), + ), sessionCost: createTokenProductRecord((product) => buildSessionCost(normalized, product, getWindow("1W", earliest, latest)), ), @@ -113,6 +118,7 @@ function emptyStatsHomeData(): StatsHomeData { leaderboard: createUsageProductRecord(() => createRangeRecord(() => [])), market: createRangeRecord(() => []), tokenCost: createTokenProductRecord(() => []), + cacheRatio: createTokenProductRecord(() => []), sessionCost: createTokenProductRecord(() => []), country: createRangeRecord(() => []), } @@ -224,6 +230,25 @@ function buildTokenCost(rows: StatMetricRow[], product: TokenProduct, window: Da .slice(0, 17) } +function buildCacheRatio(rows: StatMetricRow[], product: TokenProduct, window: DateWindow) { + return aggregateByModel(rowsForProduct(rows, product, window.start, window.end)) + .flatMap((item) => { + const total = item.inputTokens + item.cacheReadTokens + if (total === 0) return [] + return [ + { + model: item.model, + ratio: round((item.cacheReadTokens / total) * 100, 1), + cached: round(item.cacheReadTokens / 1_000_000_000, 1), + uncached: round(item.inputTokens / 1_000_000_000, 1), + total: round(total / 1_000_000_000, 1), + }, + ] + }) + .toSorted((a, b) => b.ratio - a.ratio || b.cached - a.cached) + .slice(0, 17) +} + function buildSessionCost(rows: StatMetricRow[], product: TokenProduct, window: DateWindow) { return aggregateByModel(rowsForProduct(rows, product, window.start, window.end)) .flatMap((item) => {