186 lines · 7.7 KB
import { env } from "cloudflare:workers";
import { accountDb, repoDb, type ScopedStatement } from "../scoped-db";
import { claimScratchUsage, eventStatements, periodEnd, periodOf, resolveAccount } from "../metering/record";
import { billingSettings, hardCap, siteCapKey } from "../metering/quota";
import { scratchSiteAccount } from "../metering/attribution";
import type { MeterId } from "~/lib/meters";
import { overFreeTier, recordUsage, usageDay } from "./usage";
import type { SiteMeta } from "./config";

/**
 * The only place Sites talks to metering. Per-request counters stay in `site_usage` (Durable Object counters,
 * flushed to D1) so the hot path never writes D1; `rollupScopeSiteUsage` folds each finished UTC day into the shared
 * meters, billed to the owner or to `scratch:site:<id>` (redirected to whoever claimed it).
 */
type Metered = Pick<SiteMeta, "id" | "ownerId" | "scratch" | "repoId">;

const ROLLUP_PAGE = 500;
const ROLLUP_MAX_PAGES = 40;
const CAP_FLAG_CACHE_SECONDS = 60;
/** Every hosting meter whose hard cap stops an owner's Sites from serving. */
export const HOSTING_METERS: MeterId[] = ["compute.site_requests", "compute.site_cpu", "network.sites"];
export const ROLLUP_DAYS = 3;

/** One request and its response bytes, after the response finished streaming. */
export function meterSiteRequest(ctx: ExecutionContext, site: Metered, input: { bytes: number; rayId: string }): void {
  ctx.waitUntil(recordUsage(site, { requests: 1, bytesOut: input.bytes }));
}

/** CPU time of a dynamic site's isolate, from its tail events. */
export async function meterSiteCpu(site: Metered, cpuMs: number): Promise<void> {
  await recordUsage(site, { cpuMs });
}

/** Before serving a scratch site: false once its free tier is used up (the dispatcher answers 429). */
export async function scratchWithinQuota(site: Metered): Promise<boolean> {
  return !site.scratch || !(await overFreeTier(site.id));
}

/** Set by the admin "suspend hosting" action; unlike the cap flag, plan changes never clear it. */
export function hostingSuspendedKey(accountId: string): string {