| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- import { Client } from "@planetscale/database"
- import { readdir } from "node:fs/promises"
- import path from "node:path"
- import { drizzle } from "drizzle-orm/planetscale-serverless"
- import { geoStat, modelStat, providerStat } from "./database/schema"
- import { statModel, statProvider } from "./domain/model-normalization"
- import {
- chunks,
- collapseRows,
- inserted,
- isoWeekId,
- normalizeCountry,
- normalizeTier,
- periodKeyFor,
- rankBy,
- rankRowsWithMarketShare,
- startOfIsoWeek,
- startOfUtcDay,
- statPeriodKey,
- synthesizeAllTierRows,
- toStatBaseRow,
- type StatBaseAggregate,
- } from "./domain/stat"
- const DAY_MS = 86_400_000
- const DEFAULT_UPSERT_CHUNK_SIZE = 100
- const DEFAULT_TIERS = ["Go", "Free", "Paid"]
- const FREE_MODELS = new Set(["gpt-5-nano", "grok-code", "big-pickle"])
- type Grain = "day" | "week"
- type MetricDimension = "model" | "provider" | "geo" | "geo-model"
- type LookupDimension = "model-provider-model" | "geo-continent"
- type ImportKey = `${MetricDimension | LookupDimension}-${Grain}`
- type QuerySpec = {
- name: string
- importKey: ImportKey
- importFlag: `--${ImportKey}`
- query: ReturnType<typeof metricQuery>
- }
- type RawRow = Record<string, string>
- type ImportOptions = {
- dataset: string
- databaseUrl: string | undefined
- directories: string[]
- dryRun: boolean
- periodStart: Date | undefined
- upsertChunkSize: number
- files: Partial<Record<ImportKey, string[]>>
- }
- type ModelAggregate = StatBaseAggregate & { provider: string; model: string; provider_model: string }
- type ProviderAggregate = StatBaseAggregate & { provider: string }
- type GeoAggregate = StatBaseAggregate & { provider: string; model: string; country: string; continent: string }
- type ModelStatRow = typeof modelStat.$inferInsert
- type ProviderStatRow = typeof providerStat.$inferInsert
- type GeoStatRow = typeof geoStat.$inferInsert
- const inputKeys = [
- "model-day",
- "model-week",
- "model-provider-model-day",
- "model-provider-model-week",
- "provider-day",
- "provider-week",
- "geo-day",
- "geo-week",
- "geo-model-day",
- "geo-model-week",
- "geo-continent-day",
- "geo-continent-week",
- ] as const satisfies ImportKey[]
- if (import.meta.main) await main()
- async function main() {
- const command = process.argv[2]
- if (command === "queries") return printQueries(process.argv.slice(3))
- if (command === "import") return importFiles(process.argv.slice(3))
- usage()
- }
- function printQueries(args: string[]) {
- const flags = parseFlags(args)
- const limit = parseIntegerFlag(flags, "limit") ?? 1000
- const tiers = parseListFlag(flags, "tiers") ?? DEFAULT_TIERS
- const queries = buildQueries(limit, tiers)
- const only = flags.get("only")?.[0]
- if (only) {
- const item = queries.find((query) => query.name === only)
- if (!item) fail(`Unknown --only ${only}. Expected one of: ${queries.map((query) => query.name).join(", ")}`)
- console.log(JSON.stringify(item.query, null, 2))
- return
- }
- console.log(
- JSON.stringify(
- {
- tiers,
- import_hint: "bun src/honeycomb-backfill.ts import --dir downloads",
- queries,
- },
- null,
- 2,
- ),
- )
- }
- async function importFiles(args: string[]) {
- const parsed = parseImportOptions(args)
- const opts = { ...parsed, files: mergeFiles(parsed.files, await discoverFiles(parsed.directories)) }
- if (!inputKeys.some((key) => opts.files[key]?.length)) fail("No CSV or JSON import files were provided or discovered")
- const providerModelLookup = new Map([
- ...(await lookupRows(opts.files["model-provider-model-day"], "day", opts, modelProviderModelLookup)),
- ...(await lookupRows(opts.files["model-provider-model-week"], "week", opts, modelProviderModelLookup)),
- ])
- const continentLookup = new Map([
- ...(await lookupRows(opts.files["geo-continent-day"], "day", opts, geoContinentLookup)),
- ...(await lookupRows(opts.files["geo-continent-week"], "week", opts, geoContinentLookup)),
- ])
- const modelAggregates = [
- ...(await metricRows(opts.files["model-day"], "day", opts, (row, base) =>
- modelAggregate(row, base, providerModelLookup),
- )),
- ...(await metricRows(opts.files["model-week"], "week", opts, (row, base) =>
- modelAggregate(row, base, providerModelLookup),
- )),
- ]
- const modelRows = modelRowsFromAggregates(modelAggregates)
- const providerRows = providerRowsFromAggregates([
- ...(await metricRows(opts.files["provider-day"], "day", opts, (row, base) => ({
- ...base,
- provider: provider(row) ?? "unknown",
- }))),
- ...(await metricRows(opts.files["provider-week"], "week", opts, (row, base) => ({
- ...base,
- provider: provider(row) ?? "unknown",
- }))),
- ])
- const geoRows = geoRowsFromAggregates([
- ...(await metricRows(opts.files["geo-day"], "day", opts, (row, base) => ({
- ...base,
- provider: "all",
- model: "all",
- country: country(row),
- continent: continentLookup.get(lookupKey(base, country(row))) ?? continent(row),
- }))),
- ...(await metricRows(opts.files["geo-week"], "week", opts, (row, base) => ({
- ...base,
- provider: "all",
- model: "all",
- country: country(row),
- continent: continentLookup.get(lookupKey(base, country(row))) ?? continent(row),
- }))),
- ...(await metricRows(opts.files["geo-model-day"], "day", opts, (row, base) =>
- geoModelAggregate(row, base, continentLookup),
- )),
- ...(await metricRows(opts.files["geo-model-week"], "week", opts, (row, base) =>
- geoModelAggregate(row, base, continentLookup),
- )),
- ])
- console.log(
- JSON.stringify(
- {
- inputs: Object.fromEntries(
- inputKeys.flatMap((key) => (opts.files[key]?.length ? [[key, opts.files[key].length]] : [])),
- ),
- modelRows: modelRows.length,
- providerRows: providerRows.length,
- geoRows: geoRows.length,
- dryRun: opts.dryRun,
- upsertChunkSize: opts.upsertChunkSize,
- },
- null,
- 2,
- ),
- )
- if (opts.dryRun) return
- if (!opts.databaseUrl) fail("DATABASE_URL is required unless --dry-run is set")
- const db = drizzle({ client: new Client({ url: opts.databaseUrl }) })
- await upsertModelRows(db, modelRows, opts.upsertChunkSize)
- await upsertProviderRows(db, providerRows, opts.upsertChunkSize)
- await upsertGeoRows(db, geoRows, opts.upsertChunkSize)
- }
- function buildQueries(limit: number, tiers: string[]): QuerySpec[] {
- const daily = tiers.flatMap((tier) => [
- querySpec(
- "model-day",
- tier,
- metricQuery(["date", "tier", "stat_provider_2", "stat_model_2"], limit, tierFilters(tier)),
- ),
- querySpec("provider-day", tier, metricQuery(["date", "tier", "stat_provider_2"], limit, tierFilters(tier))),
- querySpec("geo-day", tier, metricQuery(["date", "tier", "country", "continent"], limit, tierFilters(tier))),
- querySpec(
- "geo-model-day",
- tier,
- metricQuery(
- ["date", "tier", "stat_provider_2", "stat_model_2", "country", "continent"],
- limit,
- tierFilters(tier),
- ),
- ),
- ])
- const weekly = tiers.flatMap((tier) => [
- querySpec(
- "model-week",
- tier,
- metricQuery(["week", "tier", "stat_provider_2", "stat_model_2"], limit, tierFilters(tier)),
- ),
- querySpec("provider-week", tier, metricQuery(["week", "tier", "stat_provider_2"], limit, tierFilters(tier))),
- querySpec("geo-week", tier, metricQuery(["week", "tier", "country", "continent"], limit, tierFilters(tier))),
- querySpec(
- "geo-model-week",
- tier,
- metricQuery(
- ["week", "tier", "stat_provider_2", "stat_model_2", "country", "continent"],
- limit,
- tierFilters(tier),
- ),
- ),
- ])
- return [...daily, ...weekly]
- }
- function querySpec(importKey: ImportKey, tier: string, query: ReturnType<typeof metricQuery>) {
- return {
- name: `${importKey}-${queryNameSegment(tier)}`,
- importKey,
- importFlag: `--${importKey}` as const,
- query,
- }
- }
- function metricQuery(breakdowns: string[], limit: number, filters: ReturnType<typeof commonFilters> = []) {
- return {
- granularity: 0,
- breakdowns,
- calculations: [
- { op: "COUNT_DISTINCT", column: "session" },
- { op: "COUNT" },
- { op: "COUNT_DISTINCT", column: "workspace" },
- { op: "SUM", column: "tokens.input" },
- { op: "SUM", column: "tokens.output" },
- { op: "SUM", column: "tokens.reasoning" },
- { op: "SUM", column: "tokens.cache_read" },
- { op: "SUM", column: "tokens" },
- { op: "SUM", column: "cost.input.microcents" },
- { op: "SUM", column: "cost.output.microcents" },
- { op: "SUM", column: "cost.total.microcents" },
- { op: "AVG", column: "duration" },
- { op: "P50", column: "duration" },
- { op: "P95", column: "duration" },
- { op: "AVG", column: "time_to_first_byte" },
- { op: "P50", column: "time_to_first_byte" },
- { op: "P95", column: "time_to_first_byte" },
- { op: "AVG", column: "tps.output" },
- ],
- filters: [...commonFilters(), ...filters],
- filter_combination: "AND",
- orders: [{ column: "tokens", op: "SUM", order: "descending" }],
- havings: [],
- limit,
- formulas: [],
- }
- }
- function tierFilters(tier: string) {
- if (tier === "all") return []
- return [{ column: "tier", op: "=", value: tier }]
- }
- function queryNameSegment(value: string) {
- return (
- value
- .toLowerCase()
- .replace(/[^a-z0-9]+/g, "-")
- .replace(/^-|-$/g, "") || "all"
- )
- }
- function commonFilters() {
- return [
- { column: "event_type", op: "=", value: "completions" },
- { column: "model", op: "exists" },
- { column: "model", op: "!=", value: "" },
- { column: "model", op: "!=", value: "alpha-gpt-next" },
- ]
- }
- function metricRows<T extends StatBaseAggregate>(
- files: string[] | undefined,
- grain: Grain,
- opts: ImportOptions,
- map: (row: RawRow, base: StatBaseAggregate) => T | T[],
- ) {
- if (!files) return Promise.resolve([])
- return readFiles(files).then((rows) => rows.flatMap((row) => map(row, baseAggregate(row, grain, opts))))
- }
- function lookupRows(
- files: string[] | undefined,
- grain: Grain,
- opts: ImportOptions,
- map: (row: RawRow, grain: Grain, opts: ImportOptions) => readonly (readonly [string, string])[],
- ) {
- if (!files) return Promise.resolve([])
- return readFiles(files).then((rows) =>
- Array.from(
- rows
- .flatMap((row) => map(row, grain, opts))
- .reduce((result, [key, value]) => {
- if (value && value > (result.get(key) ?? "")) result.set(key, value)
- return result
- }, new Map<string, string>()),
- ),
- )
- }
- async function readFiles(files: string[]) {
- return (await Promise.all(files.map(readRows))).flat()
- }
- async function discoverFiles(directories: string[]) {
- const classified = await Promise.all(
- (await Promise.all(directories.map(filesInDirectory))).flat().map(async (file) => ({
- file,
- key: classifyRows(file, await readRows(file)),
- })),
- )
- return classified.reduce<Partial<Record<ImportKey, string[]>>>((result, item) => {
- return { ...result, [item.key]: [...(result[item.key] ?? []), item.file] }
- }, {})
- }
- async function filesInDirectory(directory: string): Promise<string[]> {
- return (
- await Promise.all(
- (await readdir(directory, { withFileTypes: true })).map((entry) => {
- const file = path.join(directory, entry.name)
- if (entry.isDirectory()) return filesInDirectory(file)
- if (entry.isFile() && /\.(csv|json)$/i.test(entry.name)) return Promise.resolve([file])
- return Promise.resolve([])
- }),
- )
- ).flat()
- }
- function classifyRows(file: string, rows: RawRow[]): ImportKey {
- if (rows.length === 0) fail(`Cannot classify empty export: ${file}`)
- const headers = new Set(rows.flatMap((row) => Object.keys(row).map(normalizeHeader)))
- const grain: Grain = headers.has("date") ? "day" : "week"
- if (hasHeader(headers, ["country", "cf.country"])) {
- if (hasHeader(headers, ["model", "stat_model", "stat_model_2"]) && hasMetricHeaders(headers))
- return `geo-model-${grain}`
- return hasMetricHeaders(headers) ? `geo-${grain}` : `geo-continent-${grain}`
- }
- if (hasHeader(headers, ["model", "stat_model", "stat_model_2"]))
- return hasMetricHeaders(headers) ? `model-${grain}` : `model-provider-model-${grain}`
- if (
- hasHeader(headers, [
- "provider",
- "provider.normalized",
- "stat_provider",
- "stat_provider_2",
- "provider.model",
- "provider_model",
- ])
- )
- return `provider-${grain}`
- fail(`Cannot classify export from columns in ${file}`)
- }
- function hasMetricHeaders(headers: Set<string>) {
- return [
- "sumtokens",
- "sumtokensinput",
- "inputtokens",
- "totaltokens",
- "avgduration",
- "countdistinctsession",
- "countdistinctworkspace",
- ].some((header) => headers.has(header))
- }
- function hasHeader(headers: Set<string>, names: string[]) {
- return names.some((name) => headers.has(normalizeHeader(name)))
- }
- function mergeFiles(left: Partial<Record<ImportKey, string[]>>, right: Partial<Record<ImportKey, string[]>>) {
- return inputKeys.reduce<Partial<Record<ImportKey, string[]>>>((result, key) => {
- const files = [...(left[key] ?? []), ...(right[key] ?? [])]
- if (files.length === 0) return result
- return { ...result, [key]: files }
- }, {})
- }
- function modelProviderModelLookup(row: RawRow, grain: Grain, opts: ImportOptions): [string, string][] {
- const base = basePeriod(row, grain, opts)
- const value = providerModel(row)
- const author = provider(row)
- if (!value || !author) return []
- return [[lookupKey({ ...base, dataset: opts.dataset, tier: tier(row), grain }, author, model(row)), value]]
- }
- function modelAggregate(
- row: RawRow,
- base: StatBaseAggregate,
- providerModelLookup: Map<string, string>,
- ): ModelAggregate[] {
- const author = provider(row)
- if (!author) return []
- return [
- {
- ...base,
- provider: author,
- model: model(row),
- provider_model: providerModelLookup.get(lookupKey(base, author, model(row))) ?? providerModel(row),
- },
- ]
- }
- function geoContinentLookup(row: RawRow, grain: Grain, opts: ImportOptions): [string, string][] {
- const base = basePeriod(row, grain, opts)
- const value = continent(row)
- if (!value) return []
- return [[lookupKey({ ...base, dataset: opts.dataset, tier: tier(row), grain }, country(row)), value]]
- }
- function geoModelAggregate(row: RawRow, base: StatBaseAggregate, continentLookup: Map<string, string>): GeoAggregate[] {
- const author = provider(row)
- if (!author) return []
- return [
- {
- ...base,
- provider: author,
- model: model(row),
- country: country(row),
- continent: continentLookup.get(lookupKey(base, country(row))) ?? continent(row),
- },
- ]
- }
- function baseAggregate(row: RawRow, grain: Grain, opts: ImportOptions): StatBaseAggregate {
- return {
- ...basePeriod(row, grain, opts),
- grain,
- dataset: opts.dataset,
- tier: tier(row),
- sessions: integer(row, "sessions", ["COUNT_DISTINCT(session)"]),
- requests: integer(row, "requests", ["COUNT", "COUNT()"]),
- unique_users: integer(row, "unique_users", [
- "COUNT_DISTINCT(user_id)",
- "COUNT_DISTINCT(workspace)",
- "COUNT_DISTINCT(api_key)",
- ]),
- input_tokens: integer(row, "input_tokens", ["SUM(tokens.input)", "SUM(tokens_input)"]),
- output_tokens: integer(row, "output_tokens", ["SUM(tokens.output)", "SUM(tokens_output)"]),
- reasoning_tokens: integer(row, "reasoning_tokens", ["SUM(tokens.reasoning)", "SUM(tokens_reasoning)"]),
- cache_read_tokens: integer(row, "cache_read_tokens", ["SUM(tokens.cache_read)", "SUM(tokens_cache_read)"]),
- total_tokens: integer(row, "total_tokens", ["SUM(stat_tokens_total)", "SUM(tokens)", "SUM(tokens_total)"]),
- input_cost_microcents: integer(row, "input_cost_microcents", [
- "SUM(cost.input.microcents)",
- "SUM(stat_cost_input_microcents)",
- ]),
- output_cost_microcents: integer(row, "output_cost_microcents", [
- "SUM(cost.output.microcents)",
- "SUM(stat_cost_output_microcents)",
- ]),
- total_cost_microcents: integer(row, "total_cost_microcents", [
- "SUM(cost.total.microcents)",
- "SUM(stat_cost_total_microcents)",
- ]),
- avg_duration_ms: nullableNumber(row, "avg_duration_ms", ["AVG(duration)", "AVG(duration_ms)"]),
- p50_duration_ms: nullableInteger(row, "p50_duration_ms", ["P50(duration)", "P50(duration_ms)"]),
- p95_duration_ms: nullableInteger(row, "p95_duration_ms", ["P95(duration)", "P95(duration_ms)"]),
- avg_ttfb_ms: nullableNumber(row, "avg_ttfb_ms", ["AVG(time_to_first_byte)", "AVG(ttfb_ms)"]),
- p50_ttfb_ms: nullableInteger(row, "p50_ttfb_ms", ["P50(time_to_first_byte)", "P50(ttfb_ms)"]),
- p95_ttfb_ms: nullableInteger(row, "p95_ttfb_ms", ["P95(time_to_first_byte)", "P95(ttfb_ms)"]),
- avg_output_tps: nullableNumber(row, "avg_output_tps", ["AVG(tps.output)", "AVG(stat_output_tps)"]),
- success_count: integer(row, "success_count", ["SUM(success)", "SUM(is_success)", "SUM(stat_success)"]),
- error_count: integer(row, "error_count", ["SUM(error)", "SUM(is_error)", "SUM(stat_error)"]),
- sample_count: integer(row, "sample_count", ["COUNT", "COUNT()"]),
- }
- }
- function basePeriod(row: RawRow, grain: Grain, opts: ImportOptions) {
- return { period_key: periodKey(row, grain, opts) }
- }
- function periodKey(row: RawRow, grain: Grain, opts: ImportOptions) {
- if (grain === "week") {
- const week = parseWeek(row)
- if (week) return week
- fail("weekly imports require a week or period_key column")
- }
- const time = parseTime(row)
- const start = time ? startOfUtcDay(time) : opts.periodStart
- if (!start) fail("daily imports require a time column or --period-start")
- return periodKeyFor("day", start)
- }
- function modelRowsFromAggregates(aggregates: ModelAggregate[]) {
- return rankModelRows([
- ...synthesizeAllTierRows(
- collapseRows(aggregates.filter((item) => item.grain === "week").map(toModelRow), modelDimensionKey),
- modelDimensionKey,
- ),
- ...synthesizeAllTierRows(
- collapseRows(aggregates.filter((item) => item.grain === "day").map(toModelRow), modelDimensionKey),
- modelDimensionKey,
- ),
- ])
- }
- function providerRowsFromAggregates(aggregates: ProviderAggregate[]) {
- return rankRowsWithMarketShare([
- ...synthesizeAllTierRows(
- collapseRows(aggregates.filter((item) => item.grain === "week").map(toProviderRow), providerDimensionKey),
- providerDimensionKey,
- ),
- ...synthesizeAllTierRows(
- collapseRows(aggregates.filter((item) => item.grain === "day").map(toProviderRow), providerDimensionKey),
- providerDimensionKey,
- ),
- ])
- }
- function geoRowsFromAggregates(aggregates: GeoAggregate[]) {
- return rankRowsWithMarketShare(
- [
- ...synthesizeAllTierRows(
- collapseRows(aggregates.filter((item) => item.grain === "week").map(toGeoRow), geoDimensionKey),
- geoDimensionKey,
- ),
- ...synthesizeAllTierRows(
- collapseRows(aggregates.filter((item) => item.grain === "day").map(toGeoRow), geoDimensionKey),
- geoDimensionKey,
- ),
- ],
- geoMarketShareKey,
- )
- }
- function toModelRow(data: ModelAggregate): ModelStatRow {
- return { ...toStatBaseRow(data), provider: data.provider, model: data.model, provider_model: data.provider_model }
- }
- function toProviderRow(data: ProviderAggregate): ProviderStatRow {
- return { ...toStatBaseRow(data), provider: data.provider }
- }
- function toGeoRow(data: GeoAggregate): GeoStatRow {
- return {
- ...toStatBaseRow(data),
- provider: data.provider,
- model: data.model,
- country: data.country,
- continent: data.continent,
- }
- }
- function rankModelRows(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 modelDimensionKey(row: ModelStatRow) {
- return [row.provider, row.model].join("\u0000")
- }
- function providerDimensionKey(row: ProviderStatRow) {
- return row.provider
- }
- function geoDimensionKey(row: GeoStatRow) {
- return [row.provider, row.model, row.country].join("\u0000")
- }
- function geoMarketShareKey(row: GeoStatRow) {
- return [statPeriodKey(row), row.provider, row.model].join("\u0000")
- }
- function lookupKey(base: { grain: string; period_key: string; dataset: string; tier: string }, ...dimension: string[]) {
- return [base.grain, base.period_key, base.dataset, base.tier, ...dimension].join("\u0000")
- }
- function tier(row: RawRow) {
- return normalizeTier(cell(row, ["stat_tier", "tier"]) || deriveTier(row))
- }
- function deriveTier(row: RawRow) {
- const source = cell(row, ["source"])
- const value = model(row)
- if (source === "lite") return "Go"
- if (FREE_MODELS.has(value) || /-free(:global)?$/.test(rawModel(row))) return "Free"
- return "Zen"
- }
- function provider(row: RawRow) {
- return statProvider(model(row), providerModel(row), cell(row, ["stat_provider_2", "stat_provider"]))
- }
- function model(row: RawRow) {
- return statModel(cell(row, ["stat_model_2", "stat_model"]) || rawModel(row), providerModel(row))
- }
- function rawModel(row: RawRow) {
- return cell(row, ["model"]) || "unknown"
- }
- function providerModel(row: RawRow) {
- return cell(row, ["provider.model", "provider_model"]) || ""
- }
- function country(row: RawRow) {
- return normalizeCountry(cell(row, ["stat_country", "cf.country", "cf_country", "country"]))
- }
- function continent(row: RawRow) {
- return cell(row, ["cf.continent", "cf_continent", "continent"]) || ""
- }
- function integer(row: RawRow, name: string, aliases: string[] = []) {
- return Math.round(number(row, name, aliases))
- }
- function nullableInteger(row: RawRow, name: string, aliases: string[] = []) {
- if (!hasCell(row, [name, ...aliases])) return null
- return Math.round(number(row, name, aliases))
- }
- function nullableNumber(row: RawRow, name: string, aliases: string[] = []) {
- if (!hasCell(row, [name, ...aliases])) return null
- return Number(number(row, name, aliases).toFixed(2))
- }
- function number(row: RawRow, name: string, aliases: string[] = []) {
- const value = Number(cell(row, [name, ...aliases]).replace(/,/g, ""))
- return Number.isFinite(value) ? value : 0
- }
- function hasCell(row: RawRow, names: string[]) {
- return names.some((name) => row[name] !== undefined && row[name] !== "")
- }
- function cell(row: RawRow, names: string[]) {
- const normalized = normalizedCells(row)
- return (
- names.flatMap((name) => [row[name], normalized.get(normalizeHeader(name))]).find((value) => value !== undefined) ??
- ""
- )
- }
- function normalizedCells(row: RawRow) {
- return new Map(Object.entries(row).map(([key, value]) => [normalizeHeader(key), value]))
- }
- function normalizeHeader(value: string) {
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "")
- }
- function parseTime(row: RawRow) {
- const value = cell(row, ["date", "time", "timestamp", "datetime", "bucket"])
- if (!value) return undefined
- const numeric = Number(value)
- const date = Number.isFinite(numeric)
- ? new Date(numeric > 10_000_000_000 ? numeric : numeric * 1000)
- : new Date(value)
- if (Number.isNaN(date.getTime())) fail(`Invalid time value: ${value}`)
- return date
- }
- function parseWeek(row: RawRow) {
- const value = cell(row, ["period_key", "week", "stat_week"])
- if (!value) return undefined
- const match = /^(\d{4})-W(\d{1,2})$/.exec(value)
- if (!match) fail(`Invalid week value: ${value}`)
- const year = Number(match[1])
- const week = Number(match[2])
- if (week < 1 || week > 53) fail(`Invalid week value: ${value}`)
- const start = new Date(startOfIsoWeek(new Date(Date.UTC(year, 0, 4))).getTime() + (week - 1) * 7 * DAY_MS)
- const id = `${year}-W${String(week).padStart(2, "0")}`
- if (isoWeekId(start) !== id) fail(`Invalid week value: ${value}`)
- return id
- }
- async function readRows(file: string) {
- const text = await Bun.file(file).text()
- if (file.toLowerCase().endsWith(".json")) {
- const parsed: unknown = JSON.parse(text)
- return rowsFromJson(parsed)
- }
- return rowsFromCsv(text)
- }
- function rowsFromJson(value: unknown): RawRow[] {
- if (Array.isArray(value)) return value.flatMap(rowFromUnknown)
- if (!isRecord(value)) fail("JSON imports must be an array of rows or an object with results/data/rows")
- const rows = [value.results, value.data, value.rows].flatMap((candidate) =>
- Array.isArray(candidate) ? candidate.flatMap(rowFromUnknown) : [],
- )
- if (rows.length === 0) fail("JSON import did not contain rows")
- return rows
- }
- function rowFromUnknown(value: unknown): RawRow[] {
- if (!isRecord(value)) return []
- const nested = isRecord(value.data) ? value.data : {}
- return [
- Object.fromEntries(
- Object.entries({ ...value, ...nested }).flatMap(([key, item]) => {
- if (key === "data") return []
- return [[key, cellValue(item)]]
- }),
- ),
- ]
- }
- function rowsFromCsv(text: string): RawRow[] {
- const [headers, ...rows] = csvRecords(text).filter((row) => row.some((value) => value.trim() !== ""))
- if (!headers) return []
- return rows.map((row) =>
- Object.fromEntries(headers.map((header, index) => [header.trim(), row[index]?.trim() ?? ""])),
- )
- }
- function csvRecords(text: string) {
- const rows: string[][] = []
- let row: string[] = []
- let field = ""
- let quoted = false
- for (let index = 0; index < text.length; index++) {
- const char = text[index]
- const next = text[index + 1]
- if (quoted) {
- if (char === '"' && next === '"') {
- field += '"'
- index++
- continue
- }
- if (char === '"') {
- quoted = false
- continue
- }
- field += char
- continue
- }
- if (char === '"') {
- quoted = true
- continue
- }
- if (char === ",") {
- row.push(field)
- field = ""
- continue
- }
- if (char === "\n") {
- row.push(field)
- rows.push(row)
- row = []
- field = ""
- continue
- }
- if (char === "\r") continue
- field += char
- }
- row.push(field)
- rows.push(row)
- return rows
- }
- function cellValue(value: unknown) {
- if (value === null || value === undefined) return ""
- if (typeof value === "string") return value
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value)
- return JSON.stringify(value) ?? ""
- }
- function isRecord(value: unknown): value is Record<string, unknown> {
- return typeof value === "object" && value !== null && !Array.isArray(value)
- }
- async function upsertModelRows(db: ReturnType<typeof drizzle>, rows: ModelStatRow[], chunkSize: number) {
- const batches = chunks(rows, chunkSize)
- console.log(JSON.stringify({ table: "model_stat", batches: batches.length, chunkSize }))
- for (const chunk of batches) {
- await db
- .insert(modelStat)
- .values(chunk)
- .onDuplicateKeyUpdate({
- set: {
- provider_model: inserted("provider_model"),
- sessions: inserted("sessions"),
- requests: inserted("requests"),
- 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"),
- },
- })
- }
- }
- async function upsertProviderRows(db: ReturnType<typeof drizzle>, rows: ProviderStatRow[], chunkSize: number) {
- const batches = chunks(rows, chunkSize)
- console.log(JSON.stringify({ table: "provider_stat", batches: batches.length, chunkSize }))
- for (const chunk of batches) {
- await db
- .insert(providerStat)
- .values(chunk)
- .onDuplicateKeyUpdate({
- set: {
- sessions: inserted("sessions"),
- requests: inserted("requests"),
- 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"),
- market_share_tokens: inserted("market_share_tokens"),
- market_share_requests: inserted("market_share_requests"),
- market_share_sessions: inserted("market_share_sessions"),
- rank_by_tokens: inserted("rank_by_tokens"),
- rank_by_requests: inserted("rank_by_requests"),
- rank_by_sessions: inserted("rank_by_sessions"),
- rank_by_cost: inserted("rank_by_cost"),
- },
- })
- }
- }
- async function upsertGeoRows(db: ReturnType<typeof drizzle>, rows: GeoStatRow[], chunkSize: number) {
- const batches = chunks(rows, chunkSize)
- console.log(JSON.stringify({ table: "geo_stat", batches: batches.length, chunkSize }))
- for (const chunk of batches) {
- await db
- .insert(geoStat)
- .values(chunk)
- .onDuplicateKeyUpdate({
- set: {
- continent: inserted("continent"),
- sessions: inserted("sessions"),
- requests: inserted("requests"),
- 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"),
- market_share_tokens: inserted("market_share_tokens"),
- market_share_requests: inserted("market_share_requests"),
- market_share_sessions: inserted("market_share_sessions"),
- rank_by_tokens: inserted("rank_by_tokens"),
- rank_by_requests: inserted("rank_by_requests"),
- rank_by_sessions: inserted("rank_by_sessions"),
- rank_by_cost: inserted("rank_by_cost"),
- },
- })
- }
- }
- function parseImportOptions(args: string[]): ImportOptions {
- const flags = parseFlags(args)
- const files = inputKeys.reduce<Partial<Record<ImportKey, string[]>>>((result, key) => {
- const values = flags.get(key)
- if (!values) return result
- return { ...result, [key]: values }
- }, {})
- return {
- dataset: flags.get("dataset")?.[0] ?? "zen",
- databaseUrl: flags.get("database-url")?.[0] ?? process.env.DATABASE_URL,
- directories: flags.get("dir") ?? flags.get("directory") ?? [],
- dryRun: flags.has("dry-run"),
- periodStart: parseDateFlag(flags, "period-start"),
- upsertChunkSize: parseIntegerFlag(flags, "upsert-chunk-size") ?? DEFAULT_UPSERT_CHUNK_SIZE,
- files,
- }
- }
- function parseFlags(args: string[]) {
- const result = new Map<string, string[]>()
- for (let index = 0; index < args.length; index++) {
- const arg = args[index]
- if (!arg.startsWith("--")) fail(`Unexpected argument: ${arg}`)
- const name = arg.slice(2)
- if (name === "dry-run" || name === "include-weekly") {
- result.set(name, ["true"])
- continue
- }
- const nextFlag = args.findIndex((value, valueIndex) => valueIndex > index && value.startsWith("--"))
- const values = args.slice(index + 1, nextFlag === -1 ? args.length : nextFlag)
- if (values.length === 0) fail(`Missing value for --${name}`)
- result.set(name, [...(result.get(name) ?? []), ...values])
- index += values.length
- }
- return result
- }
- function parseDateFlag(flags: Map<string, string[]>, name: string) {
- const value = flags.get(name)?.[0]
- if (!value) return undefined
- const date = new Date(value)
- if (Number.isNaN(date.getTime())) fail(`Invalid --${name}: ${value}`)
- return date
- }
- function parseIntegerFlag(flags: Map<string, string[]>, name: string) {
- const value = flags.get(name)?.[0]
- if (!value) return undefined
- const parsed = Number(value)
- if (!Number.isInteger(parsed) || parsed <= 0) fail(`Invalid --${name}: ${value}`)
- return parsed
- }
- function parseListFlag(flags: Map<string, string[]>, name: string) {
- const value = flags.get(name)?.[0]
- if (!value) return undefined
- if (value === "all") return ["all"]
- return value
- .split(",")
- .map((item) => item.trim())
- .filter(Boolean)
- }
- function usage(): never {
- fail(`Usage:
- bun src/honeycomb-backfill.ts queries [--tiers Go,Free,Paid] [--limit 1000]
- bun src/honeycomb-backfill.ts import [--dry-run] [--upsert-chunk-size 100] [--database-url URL] --dir downloads
- bun src/honeycomb-backfill.ts import [--dry-run] [--upsert-chunk-size 100] [--database-url URL] --model-day file.csv [--model-day more.csv] ...`)
- }
- function fail(message: string): never {
- console.error(message)
- process.exit(1)
- }
|