161 lines · 7.3 KB
import { env } from "cloudflare:workers";
import { all } from "../db";
import { accountDb, repoDb } from "../scoped-db";
import { ownerSiteScopes } from "./store";
import { counterTotals } from "../counters";
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 } 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 a scope may not have folded into usage_periods yet: today and the few before it. */
function recentDays(now: number): 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;
    days.push(day);
  }
  return days;
}

/** Counters each scope has but not yet folded (its `site_rollups` says which days it folded). */
async function unrolledTotals(accountId: string, days: string[]) {