cloudaxe-opencode/packages/stats/core/src/domain/model.ts
Adam 08a2a623dc
fix(data): cut athena stats sync cost
Compute all stat dimensions and both grains in one GROUPING SETS query
so each sync pass scans the source table once instead of eight times.
Hourly passes now only recompute the current ISO week; a daily full pass
refreshes the whole display window. Restarted daemons resume the hourly
cadence from the last completed sync instead of immediately re-running a
pass, and the workgroup kills any query scanning more than 2 TB.
2026-07-10 06:36:26 -05:00

262 lines
9.6 KiB
TypeScript

import { and, asc, eq, inArray, max, or } from "drizzle-orm"
import { Effect, Layer } from "effect"
import * as Context from "effect/Context"
import { DatabaseError, DrizzleClient } from "../database"
import { modelStat } from "../database/schema"
import { RETIRED_STAT_MODELS, RETIRED_STAT_PROVIDERS } from "./model-normalization"
import {
chunks,
collapseRows,
inserted,
isMissingUniqueUsersColumn,
omitUniqueUsers,
rankBy,
statPeriodKey,
statRowScope,
synthesizeAllTierRows,
toStatBaseRow,
UPSERT_CHUNK_SIZE,
type StatBaseAggregate,
} from "./stat"
export type ModelStatRow = typeof modelStat.$inferInsert
export type ModelStatAggregate = StatBaseAggregate & { provider: string; model: string; provider_model: string }
export type ModelStatMetric = {
periodKey: string
updatedAt: Date
tier: string
provider: string
model: string
sessions: number
uniqueUsers: number
inputTokens: number
outputTokens: number
reasoningTokens: number
cacheReadTokens: number
totalTokens: number
inputCostMicrocents: number
outputCostMicrocents: number
totalCostMicrocents: number
}
export declare namespace ModelStatRepo {
export interface Service {
readonly listDaily: () => Effect.Effect<ModelStatMetric[], DatabaseError>
readonly lastSyncedAt: () => Effect.Effect<Date | null, DatabaseError>
readonly upsert: (rows: ModelStatRow[]) => Effect.Effect<void, DatabaseError>
readonly deleteRetiredDimensions: (rows: ModelStatRow[]) => Effect.Effect<void, DatabaseError>
}
}
export class ModelStatRepo extends Context.Service<ModelStatRepo, ModelStatRepo.Service>()(
"@opencode/stats/ModelStatRepo",
) {
static readonly layer: Layer.Layer<ModelStatRepo, never, DrizzleClient> = Layer.effect(
ModelStatRepo,
Effect.gen(function* () {
const db = yield* DrizzleClient
const listDaily = Effect.fn("ModelStatRepo.listDaily")(function* () {
return yield* Effect.tryPromise({
try: async () => {
try {
return await db
.select({
periodKey: modelStat.period_key,
updatedAt: modelStat.updated_at,
tier: modelStat.tier,
provider: modelStat.provider,
model: modelStat.model,
sessions: modelStat.sessions,
uniqueUsers: modelStat.unique_users,
inputTokens: modelStat.input_tokens,
outputTokens: modelStat.output_tokens,
reasoningTokens: modelStat.reasoning_tokens,
cacheReadTokens: modelStat.cache_read_tokens,
totalTokens: modelStat.total_tokens,
inputCostMicrocents: modelStat.input_cost_microcents,
outputCostMicrocents: modelStat.output_cost_microcents,
totalCostMicrocents: modelStat.total_cost_microcents,
})
.from(modelStat)
.where(modelDailyScope())
.orderBy(asc(modelStat.period_key))
} catch (cause) {
if (!isMissingUniqueUsersColumn(cause)) throw cause
return (
await db
.select({
periodKey: modelStat.period_key,
updatedAt: modelStat.updated_at,
tier: modelStat.tier,
provider: modelStat.provider,
model: modelStat.model,
sessions: modelStat.sessions,
inputTokens: modelStat.input_tokens,
outputTokens: modelStat.output_tokens,
reasoningTokens: modelStat.reasoning_tokens,
cacheReadTokens: modelStat.cache_read_tokens,
totalTokens: modelStat.total_tokens,
inputCostMicrocents: modelStat.input_cost_microcents,
outputCostMicrocents: modelStat.output_cost_microcents,
totalCostMicrocents: modelStat.total_cost_microcents,
})
.from(modelStat)
.where(modelDailyScope())
.orderBy(asc(modelStat.period_key))
).map((row) => ({ ...row, uniqueUsers: 0 }))
}
},
catch: (cause) => DatabaseError.make({ cause }),
})
})
const lastSyncedAt = Effect.fn("ModelStatRepo.lastSyncedAt")(function* () {
const result = yield* Effect.tryPromise({
try: () => db.select({ value: max(modelStat.updated_at) }).from(modelStat),
catch: (cause) => DatabaseError.make({ cause }),
})
return result[0]?.value ?? null
})
const upsert = Effect.fn("ModelStatRepo.upsert")(function* (rows: ModelStatRow[]) {
yield* Effect.forEach(
chunks(rows, UPSERT_CHUNK_SIZE),
(chunk) =>
Effect.tryPromise({
try: async () => {
try {
return await upsertModelChunk(chunk, true)
} catch (cause) {
if (!isMissingUniqueUsersColumn(cause)) throw cause
return upsertModelChunk(chunk, false)
}
},
catch: (cause) => DatabaseError.make({ cause }),
}),
{ discard: true },
)
})
function upsertModelChunk(chunk: ModelStatRow[], includeUniqueUsers: boolean) {
return db
.insert(modelStat)
.values(includeUniqueUsers ? chunk : omitUniqueUsers(chunk))
.onDuplicateKeyUpdate({
set: {
provider_model: inserted("provider_model"),
sessions: inserted("sessions"),
requests: inserted("requests"),
...(includeUniqueUsers ? { unique_users: inserted("unique_users") } : {}),
input_tokens: inserted("input_tokens"),
output_tokens: inserted("output_tokens"),
reasoning_tokens: inserted("reasoning_tokens"),
cache_read_tokens: inserted("cache_read_tokens"),
total_tokens: inserted("total_tokens"),
input_cost_microcents: inserted("input_cost_microcents"),
output_cost_microcents: inserted("output_cost_microcents"),
total_cost_microcents: inserted("total_cost_microcents"),
avg_duration_ms: inserted("avg_duration_ms"),
p50_duration_ms: inserted("p50_duration_ms"),
p95_duration_ms: inserted("p95_duration_ms"),
avg_ttfb_ms: inserted("avg_ttfb_ms"),
p50_ttfb_ms: inserted("p50_ttfb_ms"),
p95_ttfb_ms: inserted("p95_ttfb_ms"),
avg_output_tps: inserted("avg_output_tps"),
success_count: inserted("success_count"),
error_count: inserted("error_count"),
sample_count: inserted("sample_count"),
rank_by_tokens: inserted("rank_by_tokens"),
rank_by_requests: inserted("rank_by_requests"),
rank_by_cost: inserted("rank_by_cost"),
},
})
}
const deleteRetiredDimensions = Effect.fn("ModelStatRepo.deleteRetiredDimensions")(function* (
rows: ModelStatRow[],
) {
const scope = statRowScope(rows)
if (!scope) return
yield* Effect.tryPromise({
try: () =>
db
.delete(modelStat)
.where(
and(
inArray(modelStat.grain, scope.grains),
inArray(modelStat.period_key, scope.periodKeys),
inArray(modelStat.dataset, scope.datasets),
inArray(modelStat.client, scope.clients),
inArray(modelStat.source, scope.sources),
or(
inArray(modelStat.provider, RETIRED_STAT_PROVIDERS),
inArray(modelStat.model, RETIRED_STAT_MODELS),
),
),
),
catch: (cause) => DatabaseError.make({ cause }),
})
})
return ModelStatRepo.of({ listDaily, lastSyncedAt, upsert, deleteRetiredDimensions })
}),
)
}
function modelDailyScope() {
return and(
eq(modelStat.grain, "day"),
eq(modelStat.client, "all"),
eq(modelStat.source, "all"),
inArray(modelStat.tier, ["Go", "go"]),
)
}
export function rowsFromAggregates(aggregates: ModelStatAggregate[]) {
return rankRows([
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "week").map(toRow), dimensionKey),
dimensionKey,
),
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "day").map(toRow), dimensionKey),
dimensionKey,
),
])
}
function toRow(data: ModelStatAggregate): ModelStatRow {
return {
...toStatBaseRow(data),
provider: data.provider,
model: data.model,
provider_model: data.provider_model,
}
}
function rankRows(rows: ModelStatRow[]) {
return Object.values(
rows.reduce<Record<string, ModelStatRow[]>>((result, row) => {
const key = statPeriodKey(row)
result[key] = [...(result[key] ?? []), row]
return result
}, {}),
).flatMap((group) => {
const tokenRanks = rankBy(group, (row) => row.total_tokens ?? 0)
const requestRanks = rankBy(group, (row) => row.requests ?? 0)
const costRanks = rankBy(group, (row) => row.total_cost_microcents ?? 0)
return group.map((row) => ({
...row,
rank_by_tokens: tokenRanks.get(row) ?? null,
rank_by_requests: requestRanks.get(row) ?? null,
rank_by_cost: costRanks.get(row) ?? null,
}))
})
}
function dimensionKey(row: ModelStatRow) {
return [row.provider, row.model].join("\u0000")
}