Skip to content
89 linesCodeBlameRaw
1/**
2 * What a workspace's projects may send, and what past it costs.
3 *
4 * Every workspace has `FREE_TELEMETRY_BYTES_PER_MONTH` (1 GiB, a wrangler
5 * var; `0` is no cap) a month across all its projects, counted by the
6 * workspace's meter (./meter.ts). A workspace without the g1t plan is held
7 * to it a day at a time, as a thirtieth of it, so a burst cannot spend the
8 * month in an hour. Past a day's share, OTLP is answered `429` until
9 * midnight UTC, and Cloudflare's tail events of its apps keep their
10 * requests (what the charts are counted from) without their logs. When
11 * billing cannot say which plan the workspace is on, the plan it last
12 * said holds, and a workspace it never said anything about is free.
13 *
14 * A workspace on the plan is never refused: what all its projects send
15 * past the free amount is charged by the GB (the price book's
16 * `telemetry_gb`), reported to billing through the month (`note_pending`,
17 * source `telemetry`) and charged once the month is over.
18 *
19 * Bytes are what arrived: an OTLP body once unzipped, and a tail event's
20 * rows as they are kept.
21 */
22
23export const DEFAULT_FREE_TELEMETRY_BYTES_PER_MONTH = 1024 * 1024 * 1024;
24const DAYS_PER_MONTH = 30;
25
26/** A gigabyte, as Cloudflare bills and the price book prices `telemetry_gb`. */
27export const GB = 1_000_000_000;
28
29/**
30 * What a GB past the free amount costs g1t when the price book cannot be
31 * read: $8.50 (services/billing migration 0059 says how it is made up).
32 */
33export const TELEMETRY_MICROS_PER_GB = 8_500_000;
34
35/** The monthly allowance from its var: the default when unset or not a whole number; 0 is none. */
36export function freeMonthlyBytes(raw: string | undefined): number {
37 const text = (raw ?? "").trim();
38 const value = Number(text);
39 return text !== "" && Number.isInteger(value) && value >= 0 ? value : DEFAULT_FREE_TELEMETRY_BYTES_PER_MONTH;
40}
41
42/** A day's share of a monthly allowance; null for none. */
43export function dailyCap(monthly: number): number | null {
44 return monthly > 0 ? Math.ceil(monthly / DAYS_PER_MONTH) : null;
45}
46
47/** Which plan a workspace is on, as far as the cap goes. */
48export type Standing = "free" | "paid" | "held";
49
50/**
51 * A workspace's daily cap: a held workspace takes nothing (0), a free one
52 * its day's share, one on the plan none. Unknown is free.
53 */
54export function capOf(standing: Standing | null, daily: number | null): number | null {
55 if (standing === "held") return 0;
56 if (standing === "paid") return null;
57 return daily;
58}
59
60/**
61 * Whether `adding` more bytes today would go past `cap`. A cap of 0 takes
62 * nothing: a workspace g1t staff put a hold on (or suspended).
63 */
64export function overCap(today: number, adding: number, cap: number | null | undefined): boolean {
65 return cap != null && cap >= 0 && today + adding > cap;
66}
67
68/** What a month's `bytes` cost g1t past the free amount, at `microsPerGb`, rounded up. */
69export function telemetryCost(bytes: number, freeBytes: number, microsPerGb: number): number {
70 const past = Math.max(0, bytes - Math.max(0, freeBytes));
71 return Math.ceil((past / GB) * microsPerGb);
72}
73
74/** How much was sent, for the Billing page: `2.40 GB sent, 1.07 GB free`. */
75export function telemetryDetail(bytes: number, freeBytes: number): string {
76 return `${(bytes / GB).toFixed(2)} GB sent, ${(freeBytes / GB).toFixed(2)} GB free`;
77}
78
79/** The size of a row as it is kept: its JSON. */
80export function sizeOf(value: unknown): number {
81 return JSON.stringify(value)?.length ?? 0;
82}
83
84/** What OTLP past the day's share is told; with a cap of 0, that the workspace is held. */
85export function overCapMessage(cap: number): string {
86 if (cap === 0) return "g1t staff put a hold on this project's workspace, so its telemetry is not taken. Write to hey@flagon.io if you think this is a mistake.";
87 const mb = Math.round(cap / (1024 * 1024));
88 return `This project's workspace has sent its ${mb} MB for today across its projects, a workspace on the free plan's daily share of its monthly observability. Send less, or start the g1t plan; more is taken from 00:00 UTC.`;
89}