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