88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import postgres from "postgres";
|
|
import { db } from "../db/manager";
|
|
import { config } from "../lib/config";
|
|
import { getSecret } from "../lib/k8s";
|
|
import { getSpacesUsage } from "./spaces";
|
|
|
|
async function collectSizes() {
|
|
const customers = await db`SELECT slug, spaces_key FROM customers WHERE status = 'provisioned'`;
|
|
if (customers.length === 0) return;
|
|
|
|
for (const customer of customers) {
|
|
const { slug } = customer;
|
|
try {
|
|
// DB size
|
|
let dbSizeBytes: number | null = null;
|
|
try {
|
|
const sql = postgres(config.doadminDbUrl.replace(/\/([^/?]+)(\?|$)/, `/${slug}$2`), { max: 1 });
|
|
try {
|
|
const [row] = await sql<[{ bytes: string }]>`SELECT pg_database_size(${slug}::text)::text AS bytes`;
|
|
dbSizeBytes = Number(row.bytes);
|
|
} finally {
|
|
await sql.end();
|
|
}
|
|
} catch {}
|
|
|
|
// Spaces size
|
|
let spacesSizeBytes: number | null = null;
|
|
let spacesObjectCount: number | null = null;
|
|
if (customer.spaces_key) {
|
|
try {
|
|
const namespace = `customer-${slug}`;
|
|
const secrets = await getSecret(namespace, "lunarfront-secrets");
|
|
const result = await getSpacesUsage(
|
|
customer.spaces_key,
|
|
secrets["spaces-secret"],
|
|
config.spacesBucket,
|
|
config.spacesRegion,
|
|
`${slug}/`,
|
|
);
|
|
spacesSizeBytes = result.sizeBytes;
|
|
spacesObjectCount = result.objectCount;
|
|
} catch {}
|
|
}
|
|
|
|
// Upsert today's snapshot (one row per day per customer)
|
|
await db`
|
|
INSERT INTO customer_size_snapshots (slug, recorded_at, db_size_bytes, spaces_size_bytes, spaces_object_count)
|
|
VALUES (${slug}, CURRENT_DATE, ${dbSizeBytes}, ${spacesSizeBytes}, ${spacesObjectCount})
|
|
ON CONFLICT (slug, recorded_at) DO UPDATE SET
|
|
db_size_bytes = EXCLUDED.db_size_bytes,
|
|
spaces_size_bytes = EXCLUDED.spaces_size_bytes,
|
|
spaces_object_count = EXCLUDED.spaces_object_count
|
|
`;
|
|
} catch (err) {
|
|
console.error(`[sizeCollector] failed for ${slug}:`, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
function msUntilNext12h(): number {
|
|
const now = new Date();
|
|
const next = new Date(now);
|
|
const h = now.getUTCHours();
|
|
// next run at 00:00 or 12:00 UTC, whichever is sooner
|
|
if (h < 12) {
|
|
next.setUTCHours(12, 0, 0, 0);
|
|
} else {
|
|
next.setUTCDate(next.getUTCDate() + 1);
|
|
next.setUTCHours(0, 0, 0, 0);
|
|
}
|
|
return next.getTime() - now.getTime();
|
|
}
|
|
|
|
export function startSizeCollector(log: { info: (msg: string) => void }) {
|
|
// Run immediately on startup
|
|
collectSizes().then(() => log.info("Initial size snapshot collected")).catch(() => {});
|
|
|
|
// Then schedule for 00:00 and 12:00 UTC
|
|
function scheduleNext() {
|
|
const delay = msUntilNext12h();
|
|
setTimeout(() => {
|
|
collectSizes().then(() => log.info("Size snapshot collected")).catch(() => {});
|
|
scheduleNext();
|
|
}, delay);
|
|
}
|
|
scheduleNext();
|
|
}
|