@bytesbrains/cruise 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{budget-FIRNSN67.mjs → budget-IIC4NJUU.mjs} +6 -5
- package/dist/{chunk-FGLMW5IY.mjs → chunk-2Z62Z53P.mjs} +2 -2
- package/dist/chunk-5AFYJTLS.mjs +62 -0
- package/dist/{chunk-WS6NFRIU.mjs → chunk-5LRJO4Z3.mjs} +1 -1
- package/dist/chunk-7ZUKADIS.mjs +131 -0
- package/dist/{chunk-LALZUKEC.mjs → chunk-A4DIVNYV.mjs} +1 -0
- package/dist/chunk-CDBZLSL7.mjs +65 -0
- package/dist/chunk-D3K25SBO.mjs +69 -0
- package/dist/chunk-NR4K7AUA.mjs +17 -0
- package/dist/{chunk-GUZNI3NS.mjs → chunk-PNJVYZ3Y.mjs} +3 -56
- package/dist/chunk-TJR2U73N.mjs +12 -0
- package/dist/{chunk-THEXUHQC.mjs → chunk-UBM65ZNR.mjs} +1 -0
- package/dist/{chunk-JZPUTXM7.mjs → chunk-Y2I46CTD.mjs} +113 -1
- package/dist/{compress-3DWG35BY.mjs → compress-QAEEBNVF.mjs} +1 -2
- package/dist/{conformance-NPFDYCQL.mjs → conformance-H2DRKMVX.mjs} +4 -5
- package/dist/cruise.mjs +132 -32
- package/dist/demo-seed-PRVDIWCM.mjs +59 -0
- package/dist/doctor-LVMXEX35.mjs +802 -0
- package/dist/{fallback-P5KK55M4.mjs → fallback-HRVI5XCK.mjs} +7 -6
- package/dist/journey-JXQ3VYCT.mjs +136 -0
- package/dist/{keys-FWPQPMTW.mjs → keys-S5QOOVMS.mjs} +3 -4
- package/dist/{ledger-XBGWXY6C.mjs → ledger-DWOMA7WD.mjs} +2 -3
- package/dist/{member-ZBV4YG2M.mjs → member-R7HUJK45.mjs} +1 -2
- package/dist/metrics-LCO4YNPW.mjs +144 -0
- package/dist/{report-VRYJ3W5L.mjs → report-CLNJPK6Z.mjs} +2 -3
- package/dist/secrets-check-6PWXXJM7.mjs +65 -0
- package/dist/{service-key-N2RZ7FMZ.mjs → service-key-E6SGTLWA.mjs} +1 -2
- package/dist/{smoke-ZNYX6RO5.mjs → smoke-DFNUPTZ7.mjs} +1 -2
- package/dist/staging-seed-647DRUW5.mjs +41 -0
- package/dist/{sweep-SZX7OIDK.mjs → sweep-5O32ZU4T.mjs} +2 -3
- package/dist/{tenant-4YDGI23Q.mjs → tenant-DRN6BAOI.mjs} +13 -9
- package/dist/{tier-4L3SPLFP.mjs → tier-JSY5OU65.mjs} +1 -2
- package/package.json +3 -3
- package/dist/chunk-7NQHBXVZ.mjs +0 -104
- package/dist/chunk-ICJVJSTC.mjs +0 -42
- package/dist/chunk-N3YKWKOA.mjs +0 -9
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
// Built by packages/cli/build.ts.
|
|
3
3
|
import {
|
|
4
4
|
MAX_CHAIN
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2Z62Z53P.mjs";
|
|
6
6
|
import "./chunk-4447IUTF.mjs";
|
|
7
7
|
import "./chunk-XOH3XKM3.mjs";
|
|
8
8
|
import "./chunk-42ERHAJD.mjs";
|
|
9
9
|
import "./chunk-BNP63KB2.mjs";
|
|
10
|
-
import "./chunk-ICJVJSTC.mjs";
|
|
11
10
|
import "./chunk-UNXM6FSM.mjs";
|
|
12
11
|
import "./chunk-SYTYOTO4.mjs";
|
|
13
12
|
import "./chunk-O7TNRKSA.mjs";
|
|
13
|
+
import "./chunk-O7TNRKSA.mjs";
|
|
14
|
+
import "./chunk-7ZUKADIS.mjs";
|
|
14
15
|
import "./chunk-T3ICD43U.mjs";
|
|
15
16
|
import "./chunk-RT4R2W6H.mjs";
|
|
16
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-NR4K7AUA.mjs";
|
|
18
|
+
import "./chunk-UBM65ZNR.mjs";
|
|
17
19
|
import {
|
|
18
20
|
lookupModel
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-A4DIVNYV.mjs";
|
|
20
22
|
import "./chunk-5TVD3ZAI.mjs";
|
|
21
23
|
import {
|
|
22
24
|
connect,
|
|
@@ -24,8 +26,7 @@ import {
|
|
|
24
26
|
parseArgs,
|
|
25
27
|
requireString,
|
|
26
28
|
run
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-JZPUTXM7.mjs";
|
|
29
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
29
30
|
|
|
30
31
|
// src/fallbacks-write.ts
|
|
31
32
|
async function setFallbacks(db, modelId, fallbacks, actor) {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Built by packages/cli/build.ts.
|
|
3
|
+
import {
|
|
4
|
+
formatUsd
|
|
5
|
+
} from "./chunk-UBM65ZNR.mjs";
|
|
6
|
+
import {
|
|
7
|
+
connect,
|
|
8
|
+
environmentOf,
|
|
9
|
+
optionalNumber,
|
|
10
|
+
parseArgs,
|
|
11
|
+
run
|
|
12
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
13
|
+
|
|
14
|
+
// src/journey.ts
|
|
15
|
+
async function tenantJourneys(db) {
|
|
16
|
+
const { results } = await db.prepare(
|
|
17
|
+
`SELECT t.id AS tenantId,
|
|
18
|
+
t.name AS name,
|
|
19
|
+
t.created_at AS provisionedAt,
|
|
20
|
+
(SELECT min(k.created_at) FROM virtual_keys k
|
|
21
|
+
JOIN projects p ON p.id = k.project_id WHERE p.tenant_id = t.id) AS firstKeyAt,
|
|
22
|
+
(SELECT min(l.at) FROM ledger l WHERE l.tenant_id = t.id) AS firstRequestAt,
|
|
23
|
+
(SELECT min(l.at) FROM ledger l WHERE l.tenant_id = t.id AND l.outcome = 'served') AS firstServedAt,
|
|
24
|
+
(SELECT min(l.at) FROM ledger l WHERE l.tenant_id = t.id
|
|
25
|
+
AND date(l.at) > (SELECT date(min(l2.at)) FROM ledger l2 WHERE l2.tenant_id = t.id)) AS returnedAt,
|
|
26
|
+
(SELECT min(n.created_at) FROM lanes n WHERE n.tenant_id = t.id) AS firstLaneAt,
|
|
27
|
+
(SELECT max(l.at) FROM ledger l WHERE l.tenant_id = t.id) AS lastRequestAt,
|
|
28
|
+
(SELECT count(*) FROM ledger l WHERE l.tenant_id = t.id) AS requests,
|
|
29
|
+
(SELECT count(*) FROM ledger l WHERE l.tenant_id = t.id AND l.outcome = 'served') AS served,
|
|
30
|
+
(SELECT coalesce(sum(l.cost_micros), 0) FROM ledger l WHERE l.tenant_id = t.id) AS spendMicros,
|
|
31
|
+
(SELECT count(*) FROM projects p WHERE p.tenant_id = t.id
|
|
32
|
+
AND NOT EXISTS (SELECT 1 FROM budgets b WHERE b.scope = 'project' AND b.subject = p.id)) AS uncappedProjects
|
|
33
|
+
FROM tenants t
|
|
34
|
+
ORDER BY t.created_at ASC`
|
|
35
|
+
).all();
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
var STAGES = [
|
|
39
|
+
{ key: "provisioned", of: (j) => j.provisionedAt },
|
|
40
|
+
{ key: "key issued", of: (j) => j.firstKeyAt },
|
|
41
|
+
{ key: "first request", of: (j) => j.firstRequestAt },
|
|
42
|
+
{ key: "first served", of: (j) => j.firstServedAt },
|
|
43
|
+
{ key: "came back", of: (j) => j.returnedAt }
|
|
44
|
+
];
|
|
45
|
+
function funnel(journeys) {
|
|
46
|
+
let previous = null;
|
|
47
|
+
return STAGES.map(({ key, of }) => {
|
|
48
|
+
const tenants = journeys.filter((journey) => of(journey) !== null).length;
|
|
49
|
+
const stage = { stage: key, tenants, lost: previous === null ? 0 : previous - tenants };
|
|
50
|
+
previous = tenants;
|
|
51
|
+
return stage;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
var STALL_HOURS = 24;
|
|
55
|
+
function stalled(journeys, now, thresholdHours = STALL_HOURS) {
|
|
56
|
+
const stalls = [];
|
|
57
|
+
for (const journey of journeys) {
|
|
58
|
+
let reachedAt = null;
|
|
59
|
+
let waitingFor = null;
|
|
60
|
+
for (const { key, of } of STAGES) {
|
|
61
|
+
const at2 = of(journey);
|
|
62
|
+
if (at2 !== null) {
|
|
63
|
+
reachedAt = at2;
|
|
64
|
+
waitingFor = null;
|
|
65
|
+
} else if (waitingFor === null) waitingFor = key;
|
|
66
|
+
}
|
|
67
|
+
if (waitingFor === null || reachedAt === null) continue;
|
|
68
|
+
const since = Date.parse(`${reachedAt.replace(" ", "T")}Z`);
|
|
69
|
+
const hours = Math.floor((now.getTime() - since) / 36e5);
|
|
70
|
+
if (hours >= thresholdHours) {
|
|
71
|
+
stalls.push({ tenantId: journey.tenantId, name: journey.name, waitingFor, hours });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return stalls.sort((a, b) => b.hours - a.hours);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// scripts/journey.ts
|
|
78
|
+
var at = (value) => value ?? "-";
|
|
79
|
+
run(async () => {
|
|
80
|
+
const args = parseArgs(process.argv.slice(2));
|
|
81
|
+
const db = connect(environmentOf(args));
|
|
82
|
+
const thresholdHours = optionalNumber(args, "stall-hours") ?? STALL_HOURS;
|
|
83
|
+
const journeys = await tenantJourneys(db);
|
|
84
|
+
const now = /* @__PURE__ */ new Date();
|
|
85
|
+
console.log("Cruise \u2014 tenant journeys\n");
|
|
86
|
+
console.log("funnel \u2014 each step's population contains the next");
|
|
87
|
+
console.log("step tenants lost here");
|
|
88
|
+
for (const stage of funnel(journeys)) {
|
|
89
|
+
console.log(
|
|
90
|
+
`${stage.stage.padEnd(18)} ${String(stage.tenants).padStart(7)} ${(stage.lost === 0 ? "-" : String(stage.lost)).padStart(9)}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
console.log("\n `came back` is a request on a later UTC day than the first. Anyone can make");
|
|
94
|
+
console.log(" one call; coming back is the thing a trial does not do.");
|
|
95
|
+
console.log(" A lane of their own is not a step here \u2014 most tenants use the global lanes,");
|
|
96
|
+
console.log(" and a step most successful tenants skip is a feature, not a funnel.");
|
|
97
|
+
const stalls = stalled(journeys, now, thresholdHours);
|
|
98
|
+
console.log(`
|
|
99
|
+
|
|
100
|
+
stalled \u2014 at the same step for ${thresholdHours}h or more`);
|
|
101
|
+
console.log("tenant waiting for for");
|
|
102
|
+
if (stalls.length === 0) console.log(" (nobody)");
|
|
103
|
+
for (const stall of stalls) {
|
|
104
|
+
console.log(
|
|
105
|
+
`${stall.tenantId.slice(0, 20).padEnd(21)} ${stall.waitingFor.padEnd(18)} ${`${stall.hours}h`.padStart(6)}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
console.log("\n A tenant that has passed every step is never listed here however quiet it");
|
|
109
|
+
console.log(" has since gone: churn is a different question, and `last request` below is");
|
|
110
|
+
console.log(" the column that answers it.");
|
|
111
|
+
console.log("\n\nmilestones, oldest tenant first");
|
|
112
|
+
console.log("tenant provisioned first key first served last request reqs spend");
|
|
113
|
+
if (journeys.length === 0) console.log(" (no tenants)");
|
|
114
|
+
for (const journey of journeys) {
|
|
115
|
+
console.log(
|
|
116
|
+
`${journey.tenantId.slice(0, 20).padEnd(21)} ${at(journey.provisionedAt).padEnd(20)} ${at(journey.firstKeyAt).padEnd(20)} ${at(journey.firstServedAt).padEnd(20)} ${at(journey.lastRequestAt).padEnd(20)} ${String(journey.requests).padStart(9)} ${formatUsd(journey.spendMicros, 4).padStart(9)}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const uncapped = journeys.filter((journey) => journey.uncappedProjects > 0);
|
|
120
|
+
if (uncapped.length > 0) {
|
|
121
|
+
console.log("\n\n \u26A0 uncapped projects \u2014 no `budgets` row, so serving with no hard stop");
|
|
122
|
+
for (const journey of uncapped) {
|
|
123
|
+
console.log(` ${journey.tenantId}: ${journey.uncappedProjects}`);
|
|
124
|
+
}
|
|
125
|
+
console.log(" `npm run budget:set` gives each one a cap. Provisioning writes one in the");
|
|
126
|
+
console.log(" same batch, so these are tenants provisioned before it did, or by hand.");
|
|
127
|
+
}
|
|
128
|
+
const served = journeys.filter((journey) => journey.firstServedAt !== null).length;
|
|
129
|
+
console.log(
|
|
130
|
+
`
|
|
131
|
+
|
|
132
|
+
${journeys.length} tenant${journeys.length === 1 ? "" : "s"}, ${served} of which ${served === 1 ? "has" : "have"} ever had a request served.`
|
|
133
|
+
);
|
|
134
|
+
console.log("Where the ones that never did stalled is `npm run metrics` \u2014 refusals carry no");
|
|
135
|
+
console.log("tenant, so the two reports are read beside each other rather than joined.");
|
|
136
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Built by packages/cli/build.ts.
|
|
3
3
|
import {
|
|
4
4
|
listFlag
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-5LRJO4Z3.mjs";
|
|
6
6
|
import {
|
|
7
7
|
issueKey,
|
|
8
8
|
rateLimitProblem,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
DEFAULT_CACHE_POOL,
|
|
14
14
|
isCachePool
|
|
15
15
|
} from "./chunk-T3ICD43U.mjs";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-A4DIVNYV.mjs";
|
|
17
17
|
import {
|
|
18
18
|
keyEnvironmentFor
|
|
19
19
|
} from "./chunk-5TVD3ZAI.mjs";
|
|
@@ -24,8 +24,7 @@ import {
|
|
|
24
24
|
parseArgs,
|
|
25
25
|
requireString,
|
|
26
26
|
run
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-JZPUTXM7.mjs";
|
|
27
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
29
28
|
|
|
30
29
|
// scripts/keys.ts
|
|
31
30
|
run(async () => {
|
|
@@ -15,15 +15,14 @@ import "./chunk-O7TNRKSA.mjs";
|
|
|
15
15
|
import "./chunk-T3ICD43U.mjs";
|
|
16
16
|
import {
|
|
17
17
|
formatUsd
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UBM65ZNR.mjs";
|
|
19
19
|
import "./chunk-5TVD3ZAI.mjs";
|
|
20
20
|
import {
|
|
21
21
|
connect,
|
|
22
22
|
environmentOf,
|
|
23
23
|
parseArgs,
|
|
24
24
|
run
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-JZPUTXM7.mjs";
|
|
25
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
27
26
|
|
|
28
27
|
// scripts/ledger.ts
|
|
29
28
|
run(async () => {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Built by packages/cli/build.ts.
|
|
3
|
+
import {
|
|
4
|
+
formatUsd
|
|
5
|
+
} from "./chunk-UBM65ZNR.mjs";
|
|
6
|
+
import {
|
|
7
|
+
environmentOf,
|
|
8
|
+
loadWranglerConfig,
|
|
9
|
+
parseArgs,
|
|
10
|
+
resolveDataset,
|
|
11
|
+
run,
|
|
12
|
+
workerScript
|
|
13
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
14
|
+
|
|
15
|
+
// src/analytics-sql.ts
|
|
16
|
+
var OUTCOMES = "'served', 'refusal', 'empty', 'unpriced', 'no_usage', 'upstream_error'";
|
|
17
|
+
function window(days) {
|
|
18
|
+
const whole = Math.max(1, Math.floor(days));
|
|
19
|
+
return `timestamp > NOW() - INTERVAL '${whole}' DAY`;
|
|
20
|
+
}
|
|
21
|
+
var DATASET = /^[a-z][a-z0-9_]{0,63}$/;
|
|
22
|
+
function assertDataset(dataset) {
|
|
23
|
+
if (!DATASET.test(dataset)) throw new Error(`Not a dataset name: ${dataset}`);
|
|
24
|
+
return dataset;
|
|
25
|
+
}
|
|
26
|
+
function controlPlaneLatencySql(dataset, days) {
|
|
27
|
+
return `SELECT blob2 AS route,
|
|
28
|
+
blob3 AS method,
|
|
29
|
+
blob5 AS warmth,
|
|
30
|
+
sum(_sample_interval) AS requests,
|
|
31
|
+
quantileWeighted(0.50)(double1, _sample_interval) AS p50_ms,
|
|
32
|
+
quantileWeighted(0.95)(double1, _sample_interval) AS p95_ms,
|
|
33
|
+
quantileWeighted(0.99)(double1, _sample_interval) AS p99_ms
|
|
34
|
+
FROM ${assertDataset(dataset)}
|
|
35
|
+
WHERE blob1 = 'control-plane' AND ${window(days)}
|
|
36
|
+
GROUP BY route, method, warmth
|
|
37
|
+
ORDER BY requests DESC
|
|
38
|
+
FORMAT JSON`;
|
|
39
|
+
}
|
|
40
|
+
function refusalsSql(dataset, days) {
|
|
41
|
+
return `SELECT blob3 AS code,
|
|
42
|
+
blob2 AS route,
|
|
43
|
+
blob4 AS status,
|
|
44
|
+
sum(_sample_interval) AS refusals
|
|
45
|
+
FROM ${assertDataset(dataset)}
|
|
46
|
+
WHERE blob1 = 'cruise:refusal' AND ${window(days)}
|
|
47
|
+
GROUP BY code, route, status
|
|
48
|
+
ORDER BY refusals DESC
|
|
49
|
+
FORMAT JSON`;
|
|
50
|
+
}
|
|
51
|
+
function spendByModelSql(dataset, days) {
|
|
52
|
+
return `SELECT blob3 AS model,
|
|
53
|
+
blob7 AS outcome,
|
|
54
|
+
sum(_sample_interval) AS requests,
|
|
55
|
+
sum(double1 * _sample_interval) AS micros,
|
|
56
|
+
quantileWeighted(0.95)(double4, _sample_interval) AS upstream_p95_ms
|
|
57
|
+
FROM ${assertDataset(dataset)}
|
|
58
|
+
WHERE blob7 IN (${OUTCOMES}) AND ${window(days)}
|
|
59
|
+
GROUP BY model, outcome
|
|
60
|
+
ORDER BY requests DESC
|
|
61
|
+
FORMAT JSON`;
|
|
62
|
+
}
|
|
63
|
+
function trafficByProjectSql(dataset, days) {
|
|
64
|
+
return `SELECT blob2 AS project,
|
|
65
|
+
blob5 AS traffic_class,
|
|
66
|
+
sum(_sample_interval) AS requests,
|
|
67
|
+
sum(double1 * _sample_interval) AS micros
|
|
68
|
+
FROM ${assertDataset(dataset)}
|
|
69
|
+
WHERE blob7 IN (${OUTCOMES}) AND ${window(days)}
|
|
70
|
+
GROUP BY project, traffic_class
|
|
71
|
+
ORDER BY requests DESC
|
|
72
|
+
FORMAT JSON`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// scripts/metrics.ts
|
|
76
|
+
var DEFAULT_DAYS = 7;
|
|
77
|
+
async function query(accountId, token, sql) {
|
|
78
|
+
const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/analytics_engine/sql`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "text/plain" },
|
|
81
|
+
body: sql
|
|
82
|
+
});
|
|
83
|
+
const text = await response.text();
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Analytics Engine SQL ${response.status}: ${text.trim()}
|
|
87
|
+
|
|
88
|
+
` + (response.status === 403 ? "That is usually the token: this endpoint needs Account Analytics: Read.\n\n" : "") + sql
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return JSON.parse(text);
|
|
92
|
+
}
|
|
93
|
+
function table(result, empty) {
|
|
94
|
+
if (result.data.length === 0) {
|
|
95
|
+
console.log(` ${empty}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const columns = result.meta.map((column) => column.name);
|
|
99
|
+
const width = new Map(
|
|
100
|
+
columns.map((name) => [
|
|
101
|
+
name,
|
|
102
|
+
Math.max(name.length, ...result.data.map((row) => String(row[name] ?? "-").length))
|
|
103
|
+
])
|
|
104
|
+
);
|
|
105
|
+
console.log(columns.map((name) => name.padEnd(width.get(name))).join(" "));
|
|
106
|
+
for (const row of result.data) {
|
|
107
|
+
console.log(columns.map((name) => String(row[name] ?? "-").padEnd(width.get(name))).join(" "));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
run(async () => {
|
|
111
|
+
const args = parseArgs(process.argv.slice(2));
|
|
112
|
+
const environment = environmentOf(args);
|
|
113
|
+
const { accountId, token } = workerScript(environment);
|
|
114
|
+
const dataset = resolveDataset(loadWranglerConfig(), environment);
|
|
115
|
+
const days = typeof args.days === "string" ? Number(args.days) : DEFAULT_DAYS;
|
|
116
|
+
console.log(`Cruise \u2014 Analytics Engine, ${environment}
|
|
117
|
+
`);
|
|
118
|
+
console.log(`dataset ${dataset}`);
|
|
119
|
+
console.log(`window last ${days} day${days === 1 ? "" : "s"}`);
|
|
120
|
+
console.log("\n Counts are re-weighted by `_sample_interval`: the dataset samples under");
|
|
121
|
+
console.log(" load, and an unweighted count reads lowest exactly when traffic is highest.");
|
|
122
|
+
console.log(" Money here is approximate for the same reason \u2014 the ledger in D1 is the");
|
|
123
|
+
console.log(" store of record, and `npm run report` is what an invoice is checked against.");
|
|
124
|
+
console.log("\n\ncontrol-plane latency, by route \u2014 cold and warm never blended (#146)");
|
|
125
|
+
table(await query(accountId, token, controlPlaneLatencySql(dataset, days)), "(no control-plane traffic in the window)");
|
|
126
|
+
console.log("\n A cold isolate is our first call after idle. The dashboard's own cold start");
|
|
127
|
+
console.log(" sits on top of it and is not visible here; the two are additive.");
|
|
128
|
+
console.log("\n\nrefusals, by code \u2014 where callers stall (#490)");
|
|
129
|
+
table(await query(accountId, token, refusalsSql(dataset, days)), "(nothing has been refused in the window)");
|
|
130
|
+
console.log("\n Ordered by count, which is not ordered by importance: a rate-limit storm is");
|
|
131
|
+
console.log(" one caller's bad afternoon, and a single `measurement_stale` is a model that");
|
|
132
|
+
console.log(" left the catalogue under everybody. No tenant is recorded here by design \u2014");
|
|
133
|
+
console.log(" `npm run journey` answers who, from the ledger.");
|
|
134
|
+
console.log("\n\nserved traffic, by model and outcome");
|
|
135
|
+
table(await query(accountId, token, spendByModelSql(dataset, days)), "(no served traffic in the window)");
|
|
136
|
+
console.log("\n\ntraffic by project and class");
|
|
137
|
+
const traffic = await query(accountId, token, trafficByProjectSql(dataset, days));
|
|
138
|
+
table(traffic, "(no served traffic in the window)");
|
|
139
|
+
const micros = traffic.data.reduce((total, row) => total + Number(row.micros ?? 0), 0);
|
|
140
|
+
if (traffic.data.length > 0) {
|
|
141
|
+
console.log(`
|
|
142
|
+
\u2248 ${formatUsd(Math.round(micros), 4)} over the window, sampled. The ledger is exact.`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
@@ -15,15 +15,14 @@ import "./chunk-O7TNRKSA.mjs";
|
|
|
15
15
|
import "./chunk-T3ICD43U.mjs";
|
|
16
16
|
import {
|
|
17
17
|
formatUsd
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UBM65ZNR.mjs";
|
|
19
19
|
import "./chunk-5TVD3ZAI.mjs";
|
|
20
20
|
import {
|
|
21
21
|
connect,
|
|
22
22
|
environmentOf,
|
|
23
23
|
parseArgs,
|
|
24
24
|
run
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-JZPUTXM7.mjs";
|
|
25
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
27
26
|
|
|
28
27
|
// src/report-cache.ts
|
|
29
28
|
async function byLaneCache(db, month, tenantId) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Built by packages/cli/build.ts.
|
|
3
|
+
import {
|
|
4
|
+
HEALTH_BILLING_SECRETS
|
|
5
|
+
} from "./chunk-TJR2U73N.mjs";
|
|
6
|
+
import "./chunk-O7TNRKSA.mjs";
|
|
7
|
+
import {
|
|
8
|
+
CREDENTIAL_FREE_ENVIRONMENTS,
|
|
9
|
+
PROVIDER_KEY_PREFIX,
|
|
10
|
+
REQUIRED_SECRETS
|
|
11
|
+
} from "./chunk-NR4K7AUA.mjs";
|
|
12
|
+
import "./chunk-UBM65ZNR.mjs";
|
|
13
|
+
import "./chunk-A4DIVNYV.mjs";
|
|
14
|
+
import "./chunk-5TVD3ZAI.mjs";
|
|
15
|
+
import {
|
|
16
|
+
environmentOf,
|
|
17
|
+
parseArgs,
|
|
18
|
+
run,
|
|
19
|
+
workerScript
|
|
20
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
21
|
+
|
|
22
|
+
// src/upstream-credits.ts
|
|
23
|
+
var BILLING_OPENAI = "PROVIDER_BILLING_KEY_OPENAI";
|
|
24
|
+
var BILLING_ANTHROPIC = "PROVIDER_BILLING_KEY_ANTHROPIC";
|
|
25
|
+
var BILLING_CF = "CF_BILLING_TOKEN";
|
|
26
|
+
var CREDITS_BILLING_SECRETS = [BILLING_OPENAI, BILLING_ANTHROPIC, BILLING_CF];
|
|
27
|
+
|
|
28
|
+
// scripts/secrets-check.ts
|
|
29
|
+
run(async () => {
|
|
30
|
+
const args = parseArgs(process.argv.slice(2));
|
|
31
|
+
const environment = environmentOf(args);
|
|
32
|
+
const { accountId, scriptName, token } = workerScript(environment);
|
|
33
|
+
const response = await fetch(
|
|
34
|
+
`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts/${scriptName}/secrets`,
|
|
35
|
+
{ headers: { authorization: `Bearer ${token}` } }
|
|
36
|
+
);
|
|
37
|
+
const body = await response.json();
|
|
38
|
+
if (!response.ok || !body.success) {
|
|
39
|
+
const detail = body.errors?.map((e) => e.message).join("; ") ?? `HTTP ${response.status}`;
|
|
40
|
+
throw new Error(`Could not list secrets for ${scriptName}: ${detail}`);
|
|
41
|
+
}
|
|
42
|
+
const present = new Set((body.result ?? []).map((secret) => secret.name));
|
|
43
|
+
const missing = REQUIRED_SECRETS.filter((name) => !present.has(name));
|
|
44
|
+
if (missing.length > 0) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`${scriptName} is missing ${missing.length} required secret(s): ${missing.join(", ")}
|
|
47
|
+
` + missing.map((name) => ` npx wrangler secret put ${name} --env ${environment}`).join("\n") + `
|
|
48
|
+
See docs/secrets.md for what each one is and what breaks without it.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const credentialFree = CREDENTIAL_FREE_ENVIRONMENTS.includes(environment);
|
|
52
|
+
const billingForbidden = /* @__PURE__ */ new Set([...CREDITS_BILLING_SECRETS, ...HEALTH_BILLING_SECRETS]);
|
|
53
|
+
const forbidden = [...present].filter((name) => name.startsWith(PROVIDER_KEY_PREFIX) || billingForbidden.has(name)).sort();
|
|
54
|
+
if (credentialFree && forbidden.length > 0) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`${scriptName} holds ${forbidden.length} provider credential(s) it must not: ${forbidden.join(", ")}
|
|
57
|
+
` + forbidden.map((name) => ` npx wrangler secret delete ${name} --env ${environment}`).join("\n") + `
|
|
58
|
+
The demo answers from the emulator and calls no provider. Being unable to
|
|
59
|
+
spend is the property the deployment rests on, and it is this absence.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
console.log(
|
|
63
|
+
`${scriptName}: all ${REQUIRED_SECRETS.length} required secret(s) present (${REQUIRED_SECRETS.join(", ")})` + (credentialFree ? `, and no ${PROVIDER_KEY_PREFIX}* / billing tokens \u2014 it can reach no provider` : "")
|
|
64
|
+
);
|
|
65
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Built by packages/cli/build.ts.
|
|
3
|
+
import {
|
|
4
|
+
STAGING_ENVIRONMENT,
|
|
5
|
+
mirrorStaging,
|
|
6
|
+
productionRoutable,
|
|
7
|
+
requireSource
|
|
8
|
+
} from "./chunk-CDBZLSL7.mjs";
|
|
9
|
+
import "./chunk-A4DIVNYV.mjs";
|
|
10
|
+
import {
|
|
11
|
+
connect,
|
|
12
|
+
environmentOf,
|
|
13
|
+
parseArgs,
|
|
14
|
+
run
|
|
15
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
16
|
+
|
|
17
|
+
// scripts/staging-seed.ts
|
|
18
|
+
var SOURCE = "production";
|
|
19
|
+
run(async () => {
|
|
20
|
+
const args = parseArgs(process.argv.slice(2));
|
|
21
|
+
const environment = environmentOf(args);
|
|
22
|
+
if (environment !== STAGING_ENVIRONMENT) {
|
|
23
|
+
throw new Error(`staging:seed refuses --env ${environment}. Pass --env ${STAGING_ENVIRONMENT}, or omit it.`);
|
|
24
|
+
}
|
|
25
|
+
const measured = requireSource(await productionRoutable(connect(SOURCE)), SOURCE);
|
|
26
|
+
console.log(`Cruise \u2014 staging measurements, from ${SOURCE}
|
|
27
|
+
`);
|
|
28
|
+
console.log(` routable ${measured.length} model(s) in ${SOURCE}`);
|
|
29
|
+
if (args["dry-run"] === true) {
|
|
30
|
+
for (const m of measured) console.log(` ${m.measured_at} ${m.modality.padEnd(10)} ${m.id}`);
|
|
31
|
+
console.log("\n --dry-run: nothing written.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const summary = await mirrorStaging(connect(environment), measured);
|
|
35
|
+
console.log(` measured ${summary.routable.length} model(s) now carry ${SOURCE}'s measurement on staging`);
|
|
36
|
+
if (summary.absent.length > 0) {
|
|
37
|
+
console.log(`
|
|
38
|
+
${summary.absent.length} model(s) ${SOURCE} serves are NOT ENABLED HERE \u2014 run migrate:staging:`);
|
|
39
|
+
for (const id of summary.absent) console.log(` ${id}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -9,15 +9,14 @@ import {
|
|
|
9
9
|
import "./chunk-SYTYOTO4.mjs";
|
|
10
10
|
import "./chunk-O7TNRKSA.mjs";
|
|
11
11
|
import "./chunk-T3ICD43U.mjs";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-UBM65ZNR.mjs";
|
|
13
13
|
import "./chunk-5TVD3ZAI.mjs";
|
|
14
14
|
import {
|
|
15
15
|
connect,
|
|
16
16
|
environmentOf,
|
|
17
17
|
parseArgs,
|
|
18
18
|
run
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-JZPUTXM7.mjs";
|
|
19
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
21
20
|
|
|
22
21
|
// scripts/sweep.ts
|
|
23
22
|
run(async () => {
|
|
@@ -3,29 +3,34 @@
|
|
|
3
3
|
import {
|
|
4
4
|
assertGateway,
|
|
5
5
|
listTenants,
|
|
6
|
+
provisionTenant
|
|
7
|
+
} from "./chunk-PNJVYZ3Y.mjs";
|
|
8
|
+
import {
|
|
6
9
|
prepaidOn,
|
|
7
10
|
prepaySignups,
|
|
8
|
-
provisionTenant,
|
|
9
11
|
unprepaidSignups
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-5AFYJTLS.mjs";
|
|
13
|
+
import "./chunk-D3K25SBO.mjs";
|
|
14
|
+
import "./chunk-CDBZLSL7.mjs";
|
|
12
15
|
import "./chunk-2NVDAK5J.mjs";
|
|
13
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-2Z62Z53P.mjs";
|
|
14
17
|
import "./chunk-4447IUTF.mjs";
|
|
15
18
|
import "./chunk-XOH3XKM3.mjs";
|
|
16
19
|
import "./chunk-42ERHAJD.mjs";
|
|
17
20
|
import "./chunk-BNP63KB2.mjs";
|
|
18
|
-
import "./chunk-ICJVJSTC.mjs";
|
|
19
21
|
import "./chunk-UNXM6FSM.mjs";
|
|
20
22
|
import "./chunk-SYTYOTO4.mjs";
|
|
21
23
|
import "./chunk-O7TNRKSA.mjs";
|
|
24
|
+
import "./chunk-O7TNRKSA.mjs";
|
|
25
|
+
import "./chunk-7ZUKADIS.mjs";
|
|
22
26
|
import "./chunk-T3ICD43U.mjs";
|
|
23
27
|
import "./chunk-RT4R2W6H.mjs";
|
|
28
|
+
import "./chunk-NR4K7AUA.mjs";
|
|
24
29
|
import {
|
|
25
30
|
formatUsd,
|
|
26
31
|
usdToMicros
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-UBM65ZNR.mjs";
|
|
33
|
+
import "./chunk-A4DIVNYV.mjs";
|
|
29
34
|
import "./chunk-5TVD3ZAI.mjs";
|
|
30
35
|
import {
|
|
31
36
|
connect,
|
|
@@ -34,8 +39,7 @@ import {
|
|
|
34
39
|
parseArgs,
|
|
35
40
|
requireString,
|
|
36
41
|
run
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-JZPUTXM7.mjs";
|
|
42
|
+
} from "./chunk-Y2I46CTD.mjs";
|
|
39
43
|
|
|
40
44
|
// scripts/tenant.ts
|
|
41
45
|
function describe(tenant) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytesbrains/cruise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Run Cruise — one OpenAI-compatible endpoint in front of every model provider, with budgets that stop — in Docker or on your own Cloudflare account.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://gitlab.com/bytesbrains-oss/cruise.git",
|
|
21
|
+
"url": "git+https://gitlab.com/bytesbrains-oss/cruise.git",
|
|
22
22
|
"directory": "packages/cli"
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://gitlab.com/bytesbrains-oss/cruise",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"wrangler": "4.135.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@bytesbrains/cruise-worker": "0.
|
|
38
|
+
"@bytesbrains/cruise-worker": "0.13.0"
|
|
39
39
|
}
|
|
40
40
|
}
|