File
Blob: src/worker/do/repo/debug/state.ts
| 1 | import type { RepoStateSchema } from "../repoState"; |
| 2 | import type { DebugStateSnapshot } from "./types"; |
| 3 | |
| 4 | import { asTypedStorage } from "../repoState"; |
| 5 | import { doPrefix, r2LooseKey } from "@/worker/keys"; |
| 6 | import { getDb, listPackCatalog } from "../db"; |
| 7 | import { activeLeaseOrUndefined, getActivePackCatalogSnapshot } from "../catalog"; |
| 8 | import { toDebugPackState } from "./types"; |
| 9 | |
| 10 | async function listLooseSample(ctx: DurableObjectState): Promise<string[]> { |
| 11 | const out: string[] = []; |
| 12 | try { |
| 13 | const it = await ctx.storage.list({ prefix: "obj:", limit: 10 }); |
| 14 | for (const key of it.keys()) out.push(String(key).slice(4)); |
| 15 | } catch {} |
| 16 | return out; |
| 17 | } |
| 18 | |
| 19 | function getDatabaseSize(ctx: DurableObjectState): number | undefined { |
| 20 | try { |
| 21 | const size = ctx.storage.sql.databaseSize; |
| 22 | return typeof size === "number" ? size : undefined; |
| 23 | } catch { |
| 24 | return undefined; |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | async function sampleR2Loose( |
| 29 | prefix: string, |
| 30 | env: Env |
| 31 | ): Promise<{ bytes?: number; count?: number; truncated?: boolean }> { |
| 32 | try { |
| 33 | const list = await env.REPO_BUCKET.list({ prefix: r2LooseKey(prefix, ""), limit: 250 }); |
| 34 | let bytes = 0; |
| 35 | for (const obj of list.objects || []) bytes += obj.size || 0; |
| 36 | return { |
| 37 | bytes, |
| 38 | count: (list.objects || []).length, |
| 39 | truncated: !!list.truncated, |
| 40 | }; |
| 41 | } catch { |
| 42 | return {}; |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | export async function debugState(ctx: DurableObjectState, env: Env): Promise<DebugStateSnapshot> { |
| 47 | const store = asTypedStorage<RepoStateSchema>(ctx.storage); |
| 48 | const db = getDb(ctx.storage); |
| 49 | const refs = (await store.get("refs")) ?? []; |
| 50 | const head = await store.get("head"); |
| 51 | const prefix = doPrefix(ctx.id.toString()); |
| 52 | const now = Date.now(); |
| 53 | |
| 54 | const activeCatalogRows = await getActivePackCatalogSnapshot(ctx); |
| 55 | const catalogRows = await listPackCatalog(db); |
| 56 | const activePacks = activeCatalogRows.map(toDebugPackState); |
| 57 | const supersededPacks = catalogRows |
| 58 | .filter((row) => row.state === "superseded") |
| 59 | .map(toDebugPackState); |
| 60 | const packStats = catalogRows.map(toDebugPackState); |
| 61 | |
| 62 | const receiveLease = activeLeaseOrUndefined(await store.get("receiveLease"), now); |
| 63 | const compactLease = activeLeaseOrUndefined(await store.get("compactLease"), now); |
| 64 | const compactionWantedAt = await store.get("compactionWantedAt"); |
| 65 | |
| 66 | const looseSample = await listLooseSample(ctx); |
| 67 | const dbSizeBytes = getDatabaseSize(ctx); |
| 68 | const { |
| 69 | bytes: looseR2SampleBytes, |
| 70 | count: looseR2SampleCount, |
| 71 | truncated: looseR2Truncated, |
| 72 | } = await sampleR2Loose(prefix, env); |
| 73 | |
| 74 | return { |
| 75 | meta: { doId: ctx.id.toString(), prefix }, |
| 76 | head, |
| 77 | refsCount: refs.length, |
| 78 | refs: refs.slice(0, 20), |
| 79 | packStats: packStats.length > 0 ? packStats : undefined, |
| 80 | activePacks, |
| 81 | supersededPacks, |
| 82 | packCatalogVersion: (await store.get("packsetVersion")) || 0, |
| 83 | receiveLease, |
| 84 | compaction: { |
| 85 | running: !!compactLease, |
| 86 | queued: typeof compactionWantedAt === "number", |
| 87 | startedAt: compactLease?.createdAt, |
| 88 | wantedAt: compactionWantedAt, |
| 89 | lease: compactLease, |
| 90 | }, |
| 91 | looseSample, |
| 92 | dbSizeBytes, |
| 93 | looseR2SampleBytes, |
| 94 | looseR2SampleCount, |
| 95 | looseR2Truncated, |
| 96 | }; |
| 97 | } |