fix(testkit): macOS compat + fix test 8c ordering (#24)
This commit is contained in:
118
artifacts/api-server/src/lib/metrics.ts
Normal file
118
artifacts/api-server/src/lib/metrics.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { db, jobs, invoices } from "@workspace/db";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { latencyHistogram, type BucketStats } from "./histogram.js";
|
||||
|
||||
export interface JobStateCounts {
|
||||
awaiting_eval: number;
|
||||
awaiting_work: number;
|
||||
complete: number;
|
||||
rejected: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
export interface MetricsSnapshot {
|
||||
uptime_s: number;
|
||||
jobs: {
|
||||
total: number;
|
||||
by_state: JobStateCounts;
|
||||
};
|
||||
invoices: {
|
||||
total: number;
|
||||
paid: number;
|
||||
conversion_rate: number | null;
|
||||
};
|
||||
earnings: {
|
||||
total_sats: number;
|
||||
};
|
||||
latency: {
|
||||
eval_phase: BucketStats | null;
|
||||
work_phase: BucketStats | null;
|
||||
routes: Record<string, BucketStats>;
|
||||
};
|
||||
}
|
||||
|
||||
const START_TIME = Date.now();
|
||||
|
||||
export class MetricsService {
|
||||
async snapshot(): Promise<MetricsSnapshot> {
|
||||
const [jobsByState, invoiceCounts, earningsRow] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
state: jobs.state,
|
||||
count: sql<number>`cast(count(*) as int)`,
|
||||
})
|
||||
.from(jobs)
|
||||
.groupBy(jobs.state),
|
||||
|
||||
db
|
||||
.select({
|
||||
total: sql<number>`cast(count(*) as int)`,
|
||||
paid: sql<number>`cast(sum(case when paid then 1 else 0 end) as int)`,
|
||||
})
|
||||
.from(invoices),
|
||||
|
||||
db
|
||||
.select({
|
||||
total_sats: sql<number>`cast(coalesce(sum(actual_amount_sats), 0) as int)`,
|
||||
})
|
||||
.from(jobs),
|
||||
]);
|
||||
|
||||
// Group raw DB states into operational state keys
|
||||
const rawCounts: Record<string, number> = {};
|
||||
let jobsTotal = 0;
|
||||
for (const row of jobsByState) {
|
||||
const n = Number(row.count);
|
||||
rawCounts[row.state] = (rawCounts[row.state] ?? 0) + n;
|
||||
jobsTotal += n;
|
||||
}
|
||||
|
||||
const byState: JobStateCounts = {
|
||||
awaiting_eval: (rawCounts["awaiting_eval_payment"] ?? 0) + (rawCounts["evaluating"] ?? 0),
|
||||
awaiting_work: (rawCounts["awaiting_work_payment"] ?? 0) + (rawCounts["executing"] ?? 0),
|
||||
complete: rawCounts["complete"] ?? 0,
|
||||
rejected: rawCounts["rejected"] ?? 0,
|
||||
failed: rawCounts["failed"] ?? 0,
|
||||
};
|
||||
|
||||
const invRow = invoiceCounts[0] ?? { total: 0, paid: 0 };
|
||||
const invTotal = Number(invRow.total);
|
||||
const invPaid = Number(invRow.paid);
|
||||
const conversionRate = invTotal > 0 ? invPaid / invTotal : null;
|
||||
|
||||
const totalSats = Number(earningsRow[0]?.total_sats ?? 0);
|
||||
|
||||
const allRoutes = latencyHistogram.snapshot();
|
||||
const evalPhase = allRoutes["eval_phase"] ?? null;
|
||||
const workPhase = allRoutes["work_phase"] ?? null;
|
||||
const routeLatency: Record<string, BucketStats> = {};
|
||||
for (const [key, stats] of Object.entries(allRoutes)) {
|
||||
if (key !== "eval_phase" && key !== "work_phase") {
|
||||
routeLatency[key] = stats;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
uptime_s: Math.floor((Date.now() - START_TIME) / 1000),
|
||||
jobs: {
|
||||
total: jobsTotal,
|
||||
by_state: byState,
|
||||
},
|
||||
invoices: {
|
||||
total: invTotal,
|
||||
paid: invPaid,
|
||||
conversion_rate: conversionRate,
|
||||
},
|
||||
earnings: {
|
||||
total_sats: totalSats,
|
||||
},
|
||||
latency: {
|
||||
eval_phase: evalPhase,
|
||||
work_phase: workPhase,
|
||||
routes: routeLatency,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const metricsService = new MetricsService();
|
||||
Reference in New Issue
Block a user