| 1 | // The platform usage report, from GraphQL answers as Cloudflare gives them, |
| 2 | // without the network. |
| 3 | |
| 4 | import assert from "node:assert/strict"; |
| 5 | import { test } from "node:test"; |
| 6 | |
| 7 | import { |
| 8 | DATASETS, |
| 9 | WATCHER_QUERIES, |
| 10 | assemble, |
| 11 | buildQuery, |
| 12 | format, |
| 13 | lastFullHour, |
| 14 | logsDataset, |
| 15 | parseArgs, |
| 16 | parseGroups, |
| 17 | snake, |
| 18 | verdict, |
| 19 | watcherQuery, |
| 20 | windows, |
| 21 | } from "./platform-usage.mjs"; |
| 22 | |
| 23 | const NOW = new Date("2026-10-08T15:30:00.000Z"); |
| 24 | const answer = (rows) => ({ data: { viewer: { accounts: [{ rows }] } } }); |
| 25 | const variantOf = (key, at = 0) => DATASETS.find((spec) => spec.key === key).variants[at]; |
| 26 | |
| 27 | test("the windows are the UTC month so far and the last 24 hours", () => { |
| 28 | const [month, day] = windows(NOW); |
| 29 | assert.deepEqual(month, { key: "month_to_date", label: "Month to date (UTC)", start: "2026-10-01T00:00:00.000Z", end: "2026-10-08T15:30:00.000Z" }); |
| 30 | assert.equal(day.key, "last_24h"); |
| 31 | assert.equal(day.start, "2026-10-07T15:30:00.000Z"); |
| 32 | assert.equal(day.end, NOW.toISOString()); |
| 33 | // Just after midnight on the 1st, the month has only begun. |
| 34 | assert.equal(windows(new Date("2026-11-01T00:05:00Z"))[0].start, "2026-11-01T00:00:00.000Z"); |
| 35 | }); |
| 36 | |
| 37 | test("a query asks for the variant's sums and dimensions under one alias", () => { |
| 38 | const query = buildQuery("kvOperationsAdaptiveGroups", variantOf("kv")); |
| 39 | assert.match(query, /rows: kvOperationsAdaptiveGroups\(limit: 10000, filter: \{ datetime_geq: \$start, datetime_leq: \$end \}\)/); |
| 40 | assert.match(query, /sum \{ requests \}/); |
| 41 | assert.match(query, /dimensions \{ namespaceId actionType \}/); |
| 42 | assert.match(query, /accounts\(filter: \{ accountTag: \$accountTag \}\)/); |
| 43 | const artifacts = buildQuery("artifactsEventsAdaptiveGroups", variantOf("artifacts")); |
| 44 | assert.match(artifacts, /\bcount\b/); |
| 45 | const minute = buildQuery("durableObjectsPeriodicGroups", variantOf("durable_objects_periodic", 2)); |
| 46 | assert.match(minute, /datetimeMinute_geq: \$start, datetimeMinute_leq: \$end/); |
| 47 | }); |
| 48 | |
| 49 | test("Workers Logs are read from whichever dataset the schema has, or skipped", () => { |
| 50 | assert.equal(logsDataset(["workersInvocationsAdaptive", "workersObservabilityEventsAdaptiveGroups"]), "workersObservabilityEventsAdaptiveGroups"); |
| 51 | assert.equal(logsDataset(["workersLogsSomethingGroups"]), "workersLogsSomethingGroups"); |
| 52 | assert.equal(logsDataset(["workersInvocationsAdaptive", "kvOperationsAdaptiveGroups"]), null); |
| 53 | }); |
| 54 | |
| 55 | test("groups are summed per name, named from the listing, sorted largest first, with totals", () => { |
| 56 | const body = answer([ |
| 57 | { sum: { rowsRead: 10, rowsWritten: 1, readQueries: 2, writeQueries: 1 }, dimensions: { databaseId: "aaa" } }, |
| 58 | { sum: { rowsRead: 500, rowsWritten: 20, readQueries: 9, writeQueries: 3 }, dimensions: { databaseId: "bbb" } }, |
| 59 | { sum: { rowsRead: 5, rowsWritten: 0, readQueries: 1, writeQueries: 0 }, dimensions: { databaseId: "aaa" } }, |
| 60 | ]); |
| 61 | const parsed = parseGroups(body, variantOf("d1"), { bbb: "g1t-repos" }); |
| 62 | assert.deepEqual(parsed.metrics, ["rows_read", "rows_written", "read_queries", "write_queries"]); |
| 63 | assert.deepEqual( |
| 64 | parsed.rows.map((row) => [row.name, row.rows_read]), |
| 65 | [ |
| 66 | ["g1t-repos", 500], |
| 67 | ["aaa", 15], |
| 68 | ], |
| 69 | ); |
| 70 | assert.deepEqual(parsed.totals, { rows_read: 515, rows_written: 21, read_queries: 12, write_queries: 4 }); |
| 71 | const kv = parseGroups( |
| 72 | answer([ |
| 73 | { sum: { requests: 3 }, dimensions: { namespaceId: "n1", actionType: "write" } }, |
| 74 | { sum: { requests: 40 }, dimensions: { namespaceId: "n1", actionType: "read" } }, |
| 75 | ]), |
| 76 | variantOf("kv"), |
| 77 | { n1: "SESSIONS" }, |
| 78 | ); |
| 79 | assert.deepEqual( |
| 80 | kv.rows.map((row) => row.name), |
| 81 | ["SESSIONS / read", "SESSIONS / write"], |
| 82 | ); |
| 83 | assert.throws(() => parseGroups({ errors: [{ message: "unknown field cpuTimeUs" }] }, variantOf("workers")), /cpuTimeUs/); |
| 84 | assert.equal(snake("billableOperations"), "billable_operations"); |
| 85 | }); |
| 86 | |
| 87 | test("a dataset that errors or is missing is a note, and the others still report", () => { |
| 88 | const windowList = windows(NOW); |
| 89 | const outcomes = { |
| 90 | month_to_date: { |
| 91 | workers: { |
| 92 | body: answer([{ sum: { requests: 7, errors: 0, cpuTimeUs: 1200 }, dimensions: { scriptName: "web" } }]), |
| 93 | variant: variantOf("workers"), |
| 94 | dataset: "workersInvocationsAdaptive", |
| 95 | }, |
| 96 | d1: { body: { errors: [{ message: 'unknown field "readQueries"' }] }, variant: variantOf("d1"), dataset: "d1AnalyticsAdaptiveGroups" }, |
| 97 | queues: { skipped: "queueMessageOperationsAdaptiveGroups is not in this account's schema" }, |
| 98 | kv: { error: new Error("fetch failed") }, |
| 99 | artifacts: { |
| 100 | body: answer([{ count: 4, sum: { durationMs: 80 }, dimensions: { repositoryNamespace: "g1t", eventType: "pull" } }]), |
| 101 | variant: variantOf("artifacts"), |
| 102 | dataset: "artifactsEventsAdaptiveGroups", |
| 103 | }, |
| 104 | }, |
| 105 | last_24h: {}, |
| 106 | }; |
| 107 | const report = assemble({ account: "acct", now: NOW, windowList, outcomes }); |
| 108 | const month = report.windows[0]; |
| 109 | assert.deepEqual( |
| 110 | month.datasets.map((set) => set.key), |
| 111 | ["workers", "artifacts"], |
| 112 | ); |
| 113 | assert.equal(month.notes.length, 3); |
| 114 | assert.match(month.notes.join("\n"), /D1 rows.*readQueries/); |
| 115 | assert.match(month.notes.join("\n"), /not in this account's schema/); |
| 116 | assert.match(month.notes.join("\n"), /fetch failed/); |
| 117 | assert.deepEqual(report.windows[1].datasets, []); |
| 118 | const text = format(report, 10); |
| 119 | assert.match(text, /Workers invocations, by script/); |
| 120 | assert.match(text, /web\s+7\s+0\s+1,200/); |
| 121 | assert.match(text, /Notes:/); |
| 122 | }); |
| 123 | |
| 124 | test("the JSON report is one snake_case object with every row", () => { |
| 125 | const windowList = windows(NOW); |
| 126 | const rows = Array.from({ length: 15 }, (_, at) => ({ sum: { requests: at + 1 }, dimensions: { scriptName: `s${at}` } })); |
| 127 | const outcomes = { |
| 128 | month_to_date: { durable_objects: { body: answer(rows), variant: variantOf("durable_objects", 1), dataset: "durableObjectsInvocationsAdaptiveGroups" } }, |
| 129 | last_24h: {}, |
| 130 | }; |
| 131 | const report = JSON.parse(JSON.stringify(assemble({ account: "acct", now: NOW, windowList, outcomes }))); |
| 132 | assert.deepEqual(Object.keys(report), ["account", "generated_at", "windows"]); |
| 133 | assert.deepEqual(Object.keys(report.windows[0]), ["key", "label", "start", "end", "datasets", "notes", "errors"]); |
| 134 | const set = report.windows[0].datasets[0]; |
| 135 | assert.deepEqual(Object.keys(set), ["key", "label", "dataset", "metrics", "rows", "totals"]); |
| 136 | assert.equal(set.rows.length, 15); |
| 137 | assert.equal(set.rows[0].name, "s14"); |
| 138 | assert.equal(set.totals.requests, 120); |
| 139 | const keys = JSON.stringify(report) |
| 140 | .match(/"([^"]+)":/g) |
| 141 | .map((key) => key.slice(1, -2)); |
| 142 | for (const key of keys) assert.match(key, /^[a-z0-9_]+$/, key); |
| 143 | // The tables show only the top. |
| 144 | assert.match(format(assemble({ account: "acct", now: NOW, windowList, outcomes }), 10), /\.\.\. and 5 more/); |
| 145 | }); |
| 146 | |
| 147 | test("the command line reads --json, --top and the account", () => { |
| 148 | assert.deepEqual(parseArgs(["--json", "--top", "3", "--account", "abc"], {}), { help: false, json: true, top: 3, account: "abc" }); |
| 149 | assert.equal(parseArgs([], { CLOUDFLARE_ACCOUNT_ID: "env" }).account, "env"); |
| 150 | assert.equal(parseArgs(["--help"], {}).help, true); |
| 151 | assert.equal(parseArgs([], {}).top, 10); |
| 152 | }); |
| 153 | |
| 154 | test("errors, fallbacks, failed watcher queries and an all-empty account fail the check", () => { |
| 155 | const windowList = windows(NOW); |
| 156 | const ok = { |
| 157 | month_to_date: { |
| 158 | workers: { |
| 159 | body: answer([{ sum: { requests: 7, errors: 0, cpuTimeUs: 1 }, dimensions: { scriptName: "web" } }]), |
| 160 | variant: variantOf("workers"), |
| 161 | dataset: "workersInvocationsAdaptive", |
| 162 | }, |
| 163 | }, |
| 164 | last_24h: {}, |
| 165 | }; |
| 166 | assert.deepEqual(verdict(assemble({ account: "acct", now: NOW, windowList, outcomes: ok }), { workers: { rows: 3 } }), []); |
| 167 | const broken = { |
| 168 | month_to_date: { |
| 169 | workers: { ...ok.month_to_date.workers, fellBack: 'unknown field "cpuTimeUs"' }, |
| 170 | kv: { error: new Error("fetch failed") }, |
| 171 | }, |
| 172 | last_24h: {}, |
| 173 | }; |
| 174 | const problems = verdict(assemble({ account: "acct", now: NOW, windowList, outcomes: broken }), { |
| 175 | do_sql: { error: 'unknown field "rowsWritten"' }, |
| 176 | d1: { rows: 2 }, |
| 177 | }); |
| 178 | assert.equal(problems.length, 3); |
| 179 | assert.match(problems.join("\n"), /fell back to fewer fields: unknown field "cpuTimeUs"/); |
| 180 | assert.match(problems.join("\n"), /fetch failed/); |
| 181 | assert.match(problems.join("\n"), /watcher query do_sql \(durableObjectsPeriodicGroups\): unknown field "rowsWritten"/); |
| 182 | const empty = { month_to_date: { workers: { body: answer([]), variant: variantOf("workers"), dataset: "workersInvocationsAdaptive" } }, last_24h: {} }; |
| 183 | assert.match( |
| 184 | verdict(assemble({ account: "acct", now: NOW, windowList, outcomes: empty }), { kv: { rows: 0 } }).join("\n"), |
| 185 | /every dataset answered with no rows/, |
| 186 | ); |
| 187 | }); |
| 188 | |
| 189 | test("the watcher queries are billing's, field for field, over the last full hour", async () => { |
| 190 | const { readFile } = await import("node:fs/promises"); |
| 191 | const rust = await readFile(new URL("../../services/billing/src/platform.rs", import.meta.url), "utf8"); |
| 192 | const theirs = [...rust.matchAll(/Query \{ key: "([^"]+)", dataset: "([^"]+)", select: "([^"]+)", dimensions: "([^"]+)", hour_filter: "([^"]+)" \}/g)].map( |
| 193 | ([, key, dataset, select, dimensions, hourFilter]) => ({ key, dataset, select, dimensions, hourFilter }), |
| 194 | ); |
| 195 | assert.deepEqual(WATCHER_QUERIES, theirs); |
| 196 | assert.deepEqual(lastFullHour(NOW), { since: "2026-10-08T14:00:00Z", until: "2026-10-08T15:00:00Z" }); |
| 197 | const d1 = watcherQuery(WATCHER_QUERIES.find((q) => q.key === "d1")); |
| 198 | assert.match(d1, /rows: d1AnalyticsAdaptiveGroups\(limit: 10000, filter: \{ datetimeHour_geq: \$since, datetimeHour_lt: \$until \}\)/); |
| 199 | assert.match(d1, /\$since: Time!/); |
| 200 | }); |