Skip to content

Commit c9bdb41

Browse files
committed
wip: stats
1 parent 6cba38f commit c9bdb41

20 files changed

Lines changed: 93 additions & 96 deletions

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/console.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { domain } from "./stage"
22
import { EMAILOCTOPUS_API_KEY } from "./app"
33
import { SECRET } from "./secret"
44
import { lakeIngest } from "./lake"
5-
import { inferenceEventLake } from "./stats"
65

76
////////////////
87
// DATABASE
@@ -242,7 +241,7 @@ const SALESFORCE_INSTANCE_URL = new sst.Secret("SALESFORCE_INSTANCE_URL")
242241

243242
const logProcessor = new sst.cloudflare.Worker("LogProcessor", {
244243
handler: "packages/console/function/src/log-processor.ts",
245-
link: [SECRET.HoneycombApiKey, lakeIngest, inferenceEventLake],
244+
link: [SECRET.HoneycombApiKey, lakeIngest],
246245
})
247246

248247
new sst.cloudflare.x.SolidStart("Console", {

infra/lake.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const s3TablesCatalog = new aws.cloudcontrol.Resource(
5757
const athenaResultsBucket = new aws.s3.Bucket(
5858
"LakeAthenaResults",
5959
{
60-
bucket: `opencode-${$app.stage}-datalake-athena-results`,
60+
bucket: `opencode-${$app.stage}-lake-athena-results`,
6161
forceDestroy: $app.stage !== "production",
6262
},
6363
)
@@ -73,7 +73,7 @@ const firehoseErrorBucket = new aws.s3.Bucket(
7373
const athenaWorkgroup = new aws.athena.Workgroup(
7474
"LakeAthenaWorkgroup",
7575
{
76-
name: `opencode-${$app.stage}-datalake`,
76+
name: `opencode-${$app.stage}-lake-workgroup`,
7777
forceDestroy: $app.stage !== "production",
7878
configuration: {
7979
enforceWorkgroupConfiguration: true,
@@ -248,7 +248,7 @@ const ingestService = new sst.aws.Service("LakeIngestService", {
248248
},
249249
loadBalancer: {
250250
domain: {
251-
name: `lake-ingest.${domain}`,
251+
name: `lake.${domain}`,
252252
dns: sst.cloudflare.dns(),
253253
},
254254
rules: [

infra/stats.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const inferenceEventTable = new aws.s3tables.Table("LakeInferenceEventTable", {
7272
},
7373
})
7474

75-
export const inferenceEventLake = new sst.Linkable("InferenceEventLake", {
75+
export const inferenceEvent = new sst.Linkable("InferenceEvent", {
7676
properties: {
7777
region: lakeRegion,
7878
catalog: lakeCatalog,
@@ -144,13 +144,10 @@ new sst.x.DevCommand("StatsStudio", {
144144
// APP
145145
////////////////
146146

147-
export const app = new sst.aws.SolidStart("Stats", {
147+
export const app = new sst.cloudflare.x.SolidStart("Stats", {
148148
path: "packages/stats/app",
149149
buildCommand: "bun run build",
150-
domain: {
151-
name: domain,
152-
dns: sst.cloudflare.dns(),
153-
},
150+
domain,
154151
link: [database],
155152
environment: {
156153
PUBLIC_URL: `https://${domain}`,
@@ -177,7 +174,7 @@ export const statSync = new sst.aws.Service("StatsSyncService", {
177174
dockerfile: "packages/stats/server/Dockerfile",
178175
},
179176
command: ["bun", "src/stat-sync.ts"],
180-
link: [database, inferenceEventLake, statsSyncConfig],
177+
link: [database, inferenceEvent, statsSyncConfig],
181178
permissions: lakeQueryPermissions,
182179
scaling: {
183180
min: 1,

packages/console/function/src/log-processor.ts

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { Resource } from "@opencode-ai/console-resource"
22
import type { TraceItem } from "@cloudflare/workers-types"
33

4-
type MetricData = Record<string, unknown>
5-
type MetricEvent = { time: string; data: MetricData }
6-
74
export default {
85
async tail(events: TraceItem[]) {
96
for (const event of events) {
@@ -24,7 +21,7 @@ export default {
2421
)
2522
continue
2623

27-
let data: MetricData = {
24+
let data: Record<string, unknown> = {
2825
"cf.continent": event.event.request.cf?.continent,
2926
"cf.country": event.event.request.cf?.country,
3027
"cf.city": event.event.request.cf?.city,
@@ -38,18 +35,20 @@ export default {
3835
ip: event.event.request.headers["x-real-ip"],
3936
}
4037
const time = new Date(event.eventTimestamp ?? Date.now()).toISOString()
41-
const events: MetricEvent[] = []
42-
for (const log of event.logs) {
43-
for (const message of log.message) {
44-
if (!message.startsWith("_metric:")) continue
45-
const json = JSON.parse(message.slice(8)) as MetricData
46-
data = { ...data, ...json }
47-
if ("llm.error.code" in json) {
48-
events.push({ time, data: { ...data, event_type: "llm.error" } })
49-
}
50-
}
51-
}
52-
events.push({ time, data: { ...data, event_type: "completions" } })
38+
const events = [
39+
...event.logs.flatMap((log) =>
40+
log.message.flatMap((message: string) => {
41+
if (!message.startsWith("_metric:")) return []
42+
const json = JSON.parse(message.slice(8)) as Record<string, unknown>
43+
data = { ...data, ...json }
44+
if ("llm.error.code" in json) {
45+
return [{ time, data: { ...data, event_type: "llm.error" } }]
46+
}
47+
return []
48+
}),
49+
),
50+
{ time, data: { ...data, event_type: "completions" } },
51+
]
5352
console.log(JSON.stringify(data, null, 2))
5453

5554
const [honeycomb, lake] = await Promise.all([
@@ -78,7 +77,7 @@ export default {
7877
},
7978
}
8079

81-
function toLakeEvent(time: string, data: MetricData) {
80+
function toLakeEvent(time: string, data: Record<string, unknown>) {
8281
const tokensInput = integer(data, "tokens.input")
8382
const tokensOutput = integer(data, "tokens.output")
8483
const tokensReasoning = integer(data, "tokens.reasoning")
@@ -90,16 +89,14 @@ function toLakeEvent(time: string, data: MetricData) {
9089
const source = string(data, "source")
9190

9291
return {
93-
_lake_database: Resource.InferenceEventLake.database,
94-
_lake_table: Resource.InferenceEventLake.table,
95-
_lake_operation: "insert",
92+
type: "inference.event",
9693
event_timestamp: time,
9794
event_date: time.slice(0, 10),
9895
event_type: string(data, "event_type"),
9996
dataset: "zen",
10097
client: string(data, "client"),
10198
source,
102-
tier: tier(source),
99+
tier: source,
103100
provider: string(data, "provider"),
104101
provider_model: string(data, "provider.model"),
105102
model: string(data, "model"),
@@ -145,40 +142,32 @@ function toLakeEvent(time: string, data: MetricData) {
145142
}
146143
}
147144

148-
function tier(source: string | undefined) {
149-
if (source === "anonymous" || source === "free") return "Free"
150-
if (source === "lite") return "Go"
151-
if (source === "subscription" || source === "balance") return "Zen"
152-
if (source === "byok") return "BYOK"
153-
return undefined
154-
}
155-
156145
function outputTps(tokens: number | undefined, firstByte: number | undefined, lastByte: number | undefined) {
157146
if (!tokens || !firstByte || !lastByte || lastByte <= firstByte) return undefined
158147
return Number(((tokens / (lastByte - firstByte)) * 1000).toFixed(6))
159148
}
160149

161-
function string(data: MetricData, key: string) {
150+
function string(data: Record<string, unknown>, key: string) {
162151
const value = data[key]
163152
if (typeof value === "string") return value
164153
if (typeof value === "number" || typeof value === "boolean") return String(value)
165154
return undefined
166155
}
167156

168-
function boolean(data: MetricData, key: string) {
157+
function boolean(data: Record<string, unknown>, key: string) {
169158
const value = data[key]
170159
if (typeof value === "boolean") return value
171160
if (typeof value === "string") return value === "true" ? true : value === "false" ? false : undefined
172161
return undefined
173162
}
174163

175-
function integer(data: MetricData, key: string) {
164+
function integer(data: Record<string, unknown>, key: string) {
176165
const value = number(data, key)
177166
if (value === undefined) return undefined
178167
return Math.round(value)
179168
}
180169

181-
function number(data: MetricData, key: string) {
170+
function number(data: Record<string, unknown>, key: string) {
182171
const value = data[key]
183172
if (typeof value === "number") return Number.isFinite(value) ? value : undefined
184173
if (typeof value === "string") {

packages/console/function/src/resource.d.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

packages/stats/app/app.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default {
22
server: {
3-
preset: "aws-lambda",
3+
preset: "cloudflare-module",
44
},
55
}

packages/stats/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"vite": "catalog:"
2525
},
2626
"devDependencies": {
27+
"@cloudflare/workers-types": "catalog:",
2728
"@types/bun": "catalog:",
2829
"@types/d3-scale": "4.0.9",
2930
"@typescript/native-preview": "catalog:",

packages/stats/app/vite.config.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { nitro } from "nitro/vite"
33
import { defineConfig, type PluginOption } from "vite"
44

55
export default defineConfig({
6-
plugins: [solidStart() as PluginOption, nitro({ preset: "aws-lambda" })],
6+
plugins: [
7+
solidStart() as PluginOption,
8+
nitro({
9+
compatibilityDate: "2024-09-19",
10+
preset: "cloudflare-module",
11+
cloudflare: {
12+
nodeCompat: true,
13+
},
14+
}),
15+
],
716
server: {
817
allowedHosts: true,
918
},

packages/stats/core/drizzle.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Resource } from "sst"
1+
import { Resource } from "sst/resource"
22
import { defineConfig } from "drizzle-kit"
33

44
export default defineConfig({

0 commit comments

Comments
 (0)