Просмотр исходного кода

feat(stats): add cache ratio section

Adam 3 месяцев назад
Родитель
Сommit
d93ca9ff60

+ 5 - 1
packages/stats/app/src/routes/index.css

@@ -1648,6 +1648,7 @@
     [data-section="leaderboard"],
     [data-section="leaderboard"],
     [data-section="market-share"],
     [data-section="market-share"],
     [data-section="token-cost"],
     [data-section="token-cost"],
+    [data-section="cache-ratio"],
     [data-section="session-cost"]
     [data-section="session-cost"]
   ) {
   ) {
   position: relative;
   position: relative;
@@ -2122,7 +2123,8 @@
   white-space: nowrap;
   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;
   position: relative;
   display: grid;
   display: grid;
   gap: 8px;
   gap: 8px;
@@ -2611,6 +2613,7 @@
   [data-page="stats"] [data-section="leaderboard"],
   [data-page="stats"] [data-section="leaderboard"],
   [data-page="stats"] [data-section="market-share"],
   [data-page="stats"] [data-section="market-share"],
   [data-page="stats"] [data-section="token-cost"],
   [data-page="stats"] [data-section="token-cost"],
+  [data-page="stats"] [data-section="cache-ratio"],
   [data-page="stats"] [data-section="session-cost"] {
   [data-page="stats"] [data-section="session-cost"] {
     padding: 64px 32px;
     padding: 64px 32px;
   }
   }
@@ -2742,6 +2745,7 @@
   [data-page="stats"] [data-section="leaderboard"],
   [data-page="stats"] [data-section="leaderboard"],
   [data-page="stats"] [data-section="market-share"],
   [data-page="stats"] [data-section="market-share"],
   [data-page="stats"] [data-section="token-cost"],
   [data-page="stats"] [data-section="token-cost"],
+  [data-page="stats"] [data-section="cache-ratio"],
   [data-page="stats"] [data-section="session-cost"] {
   [data-page="stats"] [data-section="session-cost"] {
     padding: 48px 24px;
     padding: 48px 24px;
   }
   }

+ 94 - 6
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 statsUnfurlRankings from "../asset/unfurl-rankings.png?url"
 import {
 import {
   getStatsHomeData,
   getStatsHomeData,
+  type CacheRatioEntry,
   type LeaderboardEntry,
   type LeaderboardEntry,
   type MarketDay,
   type MarketDay,
   type StatsHomeData,
   type StatsHomeData,
@@ -38,8 +39,9 @@ const statsUnfurlAlt = "OpenCode Stats wordmark on a dark patterned background"
 const headerLinks = [
 const headerLinks = [
   { href: "#top-models", label: "Top Models" },
   { href: "#top-models", label: "Top Models" },
   { href: "#leaderboard", label: "Leaderboard" },
   { href: "#leaderboard", label: "Leaderboard" },
-  { href: "#token-cost", label: "Token Cost" },
   { href: "#session-cost", label: "Session 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" },
   { href: "#market-share", label: "Market Share" },
 ] as const
 ] as const
 const githubLink = {
 const githubLink = {
@@ -160,8 +162,9 @@ export default function StatsHome() {
               <>
               <>
                 <Hero updatedAt={stats().updatedAt} />
                 <Hero updatedAt={stats().updatedAt} />
                 <TopModelsSection data={stats().usage} leaderboard={stats().leaderboard} />
                 <TopModelsSection data={stats().usage} leaderboard={stats().leaderboard} />
-                <TokenCostSection data={stats().tokenCost} />
                 <SessionCostSection data={stats().sessionCost} />
                 <SessionCostSection data={stats().sessionCost} />
+                <TokenCostSection data={stats().tokenCost} />
+                <CacheRatioSection data={stats().cacheRatio} />
                 <MarketShareSection data={stats().market} />
                 <MarketShareSection data={stats().market} />
               </>
               </>
             )}
             )}
@@ -1000,7 +1003,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
         setInspecting(false)
         setInspecting(false)
       }}
       }}
     >
     >
-      <SectionBridge label="SESSION COST" href="#session-cost" />
+      <SectionBridge label="CACHE RATIO" href="#cache-ratio" />
       <SectionTitle title="Market Share" description="Compare token share by model author." />
       <SectionTitle title="Market Share" description="Compare token share by model author." />
       <Show
       <Show
         when={activeDay()}
         when={activeDay()}
@@ -1280,7 +1283,7 @@ function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) {
 
 
   return (
   return (
     <section id="token-cost" data-section="token-cost">
     <section id="token-cost" data-section="token-cost">
-      <SectionBridge label="LEADERBOARD" href="#leaderboard" />
+      <SectionBridge label="SESSION COST" href="#session-cost" />
       <SectionTitle title="Token Cost" description="Price per 1M tokens." />
       <SectionTitle title="Token Cost" description="Price per 1M tokens." />
       <Show
       <Show
         when={visible().length > 0}
         when={visible().length > 0}
@@ -1351,6 +1354,90 @@ function TokenCostChart(props: {
   )
   )
 }
 }
 
 
+function CacheRatioSection(props: { data: StatsHomeData["cacheRatio"] }) {
+  const [product, setProduct] = createSignal<TokenProduct>("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 (
+    <section id="cache-ratio" data-section="cache-ratio">
+      <SectionBridge label="TOKEN COST" href="#token-cost" />
+      <SectionTitle title="Cache Ratio" description="Share of input tokens served from cache." />
+      <Show
+        when={visible().length > 0}
+        fallback={
+          <EmptyState title="No cache ratio data" description="No input-token model_stat rows matched this product." />
+        }
+      >
+        <CacheRatioChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
+      </Show>
+      <div data-slot="token-footer" hidden>
+        <FilterPills
+          items={tokenProducts}
+          selected={product()}
+          label="Product filter"
+          variant="product"
+          onSelect={setProduct}
+        />
+        <LiveIndicator />
+      </div>
+    </section>
+  )
+}
+
+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 (
+    <div data-component="cache-ratio">
+      <For each={props.data}>
+        {(item, index) => (
+          <button
+            type="button"
+            data-component="token-row"
+            data-active={props.activeIndex === index() ? "true" : undefined}
+            onClick={() => props.onActiveIndexChange(index())}
+            onPointerEnter={() => props.onActiveIndexChange(index())}
+          >
+            <strong>{formatRatio(item.ratio)}</strong>
+            <span>{item.model}</span>
+            <MetricBar value={item.ratio} max={max()} active={props.activeIndex === index()} />
+          </button>
+        )}
+      </For>
+      <Show when={active()}>
+        {(item) => (
+          <div data-component="token-tooltip" style={{ top: `${props.activeIndex * 36 + 2}px` }}>
+            <p>
+              <span>Cache Ratio</span>
+              <strong>{formatRatio(item().ratio)}</strong>
+            </p>
+            <p>
+              <span>Cached</span>
+              <strong>{formatBillions(item().cached)}</strong>
+            </p>
+            <p>
+              <span>Uncached</span>
+              <strong>{formatBillions(item().uncached)}</strong>
+            </p>
+          </div>
+        )}
+      </Show>
+    </div>
+  )
+}
+
+function formatRatio(value: number) {
+  return `${value.toFixed(value > 0 && value < 10 ? 1 : 0)}%`
+}
+
 function formatDollars(value: number) {
 function formatDollars(value: number) {
   return `$${value.toFixed(2)}`
   return `$${value.toFixed(2)}`
 }
 }
@@ -1378,7 +1465,7 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
 
 
   return (
   return (
     <section id="session-cost" data-section="session-cost">
     <section id="session-cost" data-section="session-cost">
-      <SectionBridge label="TOKEN COST" href="#token-cost" />
+      <SectionBridge label="LEADERBOARD" href="#leaderboard" />
       <SectionTitle title="Session Cost" description="Average cost per session." />
       <SectionTitle title="Session Cost" description="Average cost per session." />
       <Show
       <Show
         when={visible().length > 0}
         when={visible().length > 0}
@@ -1635,8 +1722,9 @@ function Footer(props: {
   const modelStats = [
   const modelStats = [
     { href: "#top-models", label: "Top Models" },
     { href: "#top-models", label: "Top Models" },
     { href: "#leaderboard", label: "Leaderboard" },
     { href: "#leaderboard", label: "Leaderboard" },
-    { href: "#token-cost", label: "Token Cost" },
     { href: "#session-cost", label: "Session 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" },
     { href: "#market-share", label: "Market Share" },
   ]
   ]
   const legal = [
   const legal = [

+ 25 - 0
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 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 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 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 SessionCostEntry = { model: string; cost: number; tokens: number }
 export type CountryEntry = { country: string; continent: string; tokens: number; share: number; rank: number }
 export type CountryEntry = { country: string; continent: string; tokens: number; share: number; rank: number }
 export type StatsHomeData = {
 export type StatsHomeData = {
@@ -19,6 +20,7 @@ export type StatsHomeData = {
   leaderboard: Record<UsageProduct, Record<UsageRange, LeaderboardEntry[]>>
   leaderboard: Record<UsageProduct, Record<UsageRange, LeaderboardEntry[]>>
   market: Record<UsageRange, MarketDay[]>
   market: Record<UsageRange, MarketDay[]>
   tokenCost: Record<TokenProduct, TokenCostEntry[]>
   tokenCost: Record<TokenProduct, TokenCostEntry[]>
+  cacheRatio: Record<TokenProduct, CacheRatioEntry[]>
   sessionCost: Record<TokenProduct, SessionCostEntry[]>
   sessionCost: Record<TokenProduct, SessionCostEntry[]>
   country: Record<UsageRange, CountryEntry[]>
   country: Record<UsageRange, CountryEntry[]>
 }
 }
@@ -99,6 +101,9 @@ function buildStatsHomeData(
     tokenCost: createTokenProductRecord((product) =>
     tokenCost: createTokenProductRecord((product) =>
       buildTokenCost(normalized, product, getWindow("1W", earliest, latest)),
       buildTokenCost(normalized, product, getWindow("1W", earliest, latest)),
     ),
     ),
+    cacheRatio: createTokenProductRecord((product) =>
+      buildCacheRatio(normalized, product, getWindow("1W", earliest, latest)),
+    ),
     sessionCost: createTokenProductRecord((product) =>
     sessionCost: createTokenProductRecord((product) =>
       buildSessionCost(normalized, product, getWindow("1W", earliest, latest)),
       buildSessionCost(normalized, product, getWindow("1W", earliest, latest)),
     ),
     ),
@@ -113,6 +118,7 @@ function emptyStatsHomeData(): StatsHomeData {
     leaderboard: createUsageProductRecord(() => createRangeRecord(() => [])),
     leaderboard: createUsageProductRecord(() => createRangeRecord(() => [])),
     market: createRangeRecord(() => []),
     market: createRangeRecord(() => []),
     tokenCost: createTokenProductRecord(() => []),
     tokenCost: createTokenProductRecord(() => []),
+    cacheRatio: createTokenProductRecord(() => []),
     sessionCost: createTokenProductRecord(() => []),
     sessionCost: createTokenProductRecord(() => []),
     country: createRangeRecord(() => []),
     country: createRangeRecord(() => []),
   }
   }
@@ -224,6 +230,25 @@ function buildTokenCost(rows: StatMetricRow[], product: TokenProduct, window: Da
     .slice(0, 17)
     .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) {
 function buildSessionCost(rows: StatMetricRow[], product: TokenProduct, window: DateWindow) {
   return aggregateByModel(rowsForProduct(rows, product, window.start, window.end))
   return aggregateByModel(rowsForProduct(rows, product, window.start, window.end))
     .flatMap((item) => {
     .flatMap((item) => {