g1t/services/deployments/src/metering.ts

64 lines2,702 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1/**
2 * What a workspace's deployments cost in a month, from its meter: every
3 * request, CPU millisecond and custom domain, from the first, at what each
4 * costs g1t. Billing adds the margin and draws on the plan's included usage
5 * first. There are no allowances and no counts that stop anything: apps
6 * (production and previews) are not metered at all, since Cloudflare's
7 * Workers for Platforms includes far more scripts than g1t runs.
8 *
9 * No Workers imports, so it can be tested under Node.
10 */
11
12/** What each unit costs g1t, in millionths of a dollar. */
13export type UnitCosts = {
14 millionRequests: number;
15 millionCpuMs: number;
16 domainMonth: number;
17};
18
19/** A month's meter row, as the `meters` table keeps it. */
20export type MonthMeter = {
21 requests: number;
22 cpu_ms: number;
23 peak_domains: number | null;
24};
25
26/** What the month's traffic and domains cost g1t, and how much there was. */
27export type MonthCost = {
28 /** App requests and CPU time. */
29 traffic: { micros: number; detail: string | null };
30 /** Custom domains: the most the workspace had at once this month, each for the month. */
31 domains: { micros: number; detail: string | null };
32 /** Both, rounded up: what a month's close charges. */
33 micros: number;
34 /** For the ledger: `1,200,000 requests, 3,400,000 CPU ms, 2 custom domains`. */
35 description: string;
36};
37
38/** `1.2 million`, `840,000`. */
39export function amount(n: number): string {
40 if (n >= 1_000_000) return `${(Math.round(n / 100_000) / 10).toLocaleString("en-US")} million`;
41 return Math.round(n).toLocaleString("en-US");
42}
43
44export function monthCost(meter: MonthMeter, costs: UnitCosts): MonthCost {
45 const requests = Math.max(0, meter.requests ?? 0);
46 const cpuMs = Math.max(0, meter.cpu_ms ?? 0);
47 const domains = Math.max(0, meter.peak_domains ?? 0);
48 const traffic = Math.ceil((requests / 1_000_000) * costs.millionRequests + (cpuMs / 1_000_000) * costs.millionCpuMs);
49 const domainMicros = Math.ceil(domains * costs.domainMonth);
50 const plural = (n: number, word: string) => `${n.toLocaleString("en-US")} ${word}${n === 1 ? "" : "s"}`;
51 const trafficDetail = requests || cpuMs ? `${amount(requests)} requests and ${amount(cpuMs)} CPU ms` : null;
52 const domainDetail = domains ? plural(domains, "custom domain") : null;
53 const parts = [
54 requests && `${requests.toLocaleString("en-US")} requests`,
55 cpuMs && `${cpuMs.toLocaleString("en-US")} CPU ms`,
56 domains && plural(domains, "custom domain"),
57 ].filter(Boolean);
58 return {
59 traffic: { micros: traffic, detail: trafficDetail },
60 domains: { micros: domainMicros, detail: domainDetail },
61 micros: traffic + domainMicros,
62 description: parts.join(", "),
63 };
64}