Skip to content
File

Blob: src/worker/do/repo/debug/state.ts

typescript98 lines
1import type { RepoStateSchema } from "../repoState";
2import type { DebugStateSnapshot } from "./types";
3 
4import { asTypedStorage } from "../repoState";
5import { doPrefix, r2LooseKey } from "@/worker/keys";
6import { getDb, listPackCatalog } from "../db";
7import { activeLeaseOrUndefined, getActivePackCatalogSnapshot } from "../catalog";
8import { toDebugPackState } from "./types";
9 
10async 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 
19function 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 
28async 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 
46export 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}