179 lines · 8.2 KB
import { env } from "cloudflare:workers";
import { all } from "../db";
import { accountDb, repoDb } from "../scoped-db";
import { ownerSiteScopes, siteScopes } from "./store";
import { periodEnd, periodOf } from "../metering/record";
import { billingSettings, hardCap, siteCapKey } from "../metering/quota";
import { notifyUsers } from "../notify/deliver";
import { publicUrl } from "../events/emit";
import type { MeterId } from "~/lib/meters";
import { HOSTING_METERS, ROLLUP_DAYS, rollupDoneKey } from "./metering";
import { usageDay } from "./usage";

const DAY_MS = 24 * 60 * 60 * 1000;
/** Per isolate: how often one account's caps or the global ceiling are re-read after counter flushes. */
const ACCOUNT_CHECK_MS = 30_000;
const GLOBAL_CHECK_MS = 60_000;
const FLAG_CACHE_SECONDS = 60;
const GLOBAL_FLAG_TTL_SECONDS = 2 * 24 * 60 * 60;
const MIN_TTL_SECONDS = 60;
const MAX_TRACKED_ACCOUNTS = 1000;
const lastChecked = new Map<string, number>();
let lastGlobalCheck = 0;

type Totals = { requests: number; cpu_ms: number; bytes_out: number };

/** Days of this period whose counters are not in usage_periods yet: today plus any recent day not rolled up. */
async function unrolledDays(source: "site-usage", now: number): Promise<string[]> {
  const period = periodOf(now);
  const days = [usageDay(now)];
  for (let back = 1; back <= ROLLUP_DAYS; back += 1) {
    const day = usageDay(now - back * DAY_MS);
    if (!day.startsWith(period)) break;
    if (!(await env.KV.get(rollupDoneKey(source, day)))) days.push(day);
  }
  return days;
}

async function unrolledTotals(accountId: string, days: string[]) {
  const list = days.map(() => "?").join(", ");
  const sql = `SELECT COALESCE(SUM(requests), 0) AS requests, COALESCE(SUM(cpu_ms), 0) AS cpu_ms, COALESCE(SUM(bytes_out), 0) AS bytes_out