@framedash/cli 0.1.1 → 0.1.2
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/README.md +23 -0
- package/dist/commands/builds.d.ts +2 -0
- package/dist/commands/builds.d.ts.map +1 -0
- package/dist/commands/builds.js +31 -0
- package/dist/commands/builds.js.map +1 -0
- package/dist/commands/content.d.ts.map +1 -1
- package/dist/commands/content.js +2 -2
- package/dist/commands/content.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +3 -3
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/funnel.d.ts.map +1 -1
- package/dist/commands/funnel.js +7 -8
- package/dist/commands/funnel.js.map +1 -1
- package/dist/commands/map-capture.d.ts.map +1 -1
- package/dist/commands/map-capture.js +5 -4
- package/dist/commands/map-capture.js.map +1 -1
- package/dist/commands/perf-diff.d.ts +2 -0
- package/dist/commands/perf-diff.d.ts.map +1 -0
- package/dist/commands/perf-diff.js +125 -0
- package/dist/commands/perf-diff.js.map +1 -0
- package/dist/commands/retention.d.ts.map +1 -1
- package/dist/commands/retention.js +3 -3
- package/dist/commands/retention.js.map +1 -1
- package/dist/commands/run-profile-test.d.ts +7 -0
- package/dist/commands/run-profile-test.d.ts.map +1 -0
- package/dist/commands/run-profile-test.js +386 -0
- package/dist/commands/run-profile-test.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/create-client.d.ts +9 -1
- package/dist/lib/create-client.d.ts.map +1 -1
- package/dist/lib/create-client.js +17 -1
- package/dist/lib/create-client.js.map +1 -1
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +4 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/perf-diff-eval.d.ts +58 -0
- package/dist/lib/perf-diff-eval.d.ts.map +1 -0
- package/dist/lib/perf-diff-eval.js +49 -0
- package/dist/lib/perf-diff-eval.js.map +1 -0
- package/dist/lib/run-profile-test-lib.d.ts +117 -0
- package/dist/lib/run-profile-test-lib.d.ts.map +1 -0
- package/dist/lib/run-profile-test-lib.js +124 -0
- package/dist/lib/run-profile-test-lib.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import { constants as osConstants } from "node:os";
|
|
3
|
+
import { buildBuildComparePath, buildBuildsPath } from "@framedash/api-client";
|
|
4
|
+
import { createClient } from "../lib/create-client.js";
|
|
5
|
+
import { formatOutput } from "../lib/formatters.js";
|
|
6
|
+
import { error, log, success, warn } from "../lib/logger.js";
|
|
7
|
+
import { evaluateRegression, formatMetricDiff, isRegressionMetric, } from "../lib/perf-diff-eval.js";
|
|
8
|
+
import { runCommand } from "../lib/run-command.js";
|
|
9
|
+
import { buildEventCount, buildSessionEnv, resolveProfileIdentity, SESSION_ENV_KEYS, waitForIngest, } from "../lib/run-profile-test-lib.js";
|
|
10
|
+
const DEFAULT_INGEST_TIMEOUT_S = 180;
|
|
11
|
+
const DEFAULT_POLL_INTERVAL_S = 5;
|
|
12
|
+
/** Best-effort `git` lookup; returns undefined when git is absent or fails. */
|
|
13
|
+
function gitOutput(args) {
|
|
14
|
+
try {
|
|
15
|
+
const out = execFileSync("git", args, {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18
|
+
}).trim();
|
|
19
|
+
return out.length > 0 ? out : undefined;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Parse a positive-seconds flag with a descriptive error, or fall back. */
|
|
26
|
+
function parseSeconds(value, flag, fallback) {
|
|
27
|
+
// Only a string carries a real value; undefined falls back, and a boolean
|
|
28
|
+
// (unreachable for these string flags, but typed) must not coerce to 1.
|
|
29
|
+
if (typeof value !== "string")
|
|
30
|
+
return fallback;
|
|
31
|
+
const n = Number(value);
|
|
32
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
33
|
+
error(`--${flag} must be a positive number of seconds`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Forward the project id and base URL so a CI game build that reads them from the
|
|
40
|
+
* environment targets the same project/endpoint the gate queries (they must match
|
|
41
|
+
* or the gate would read a different project's builds).
|
|
42
|
+
*
|
|
43
|
+
* Deliberately does NOT forward the API key: the gate's key needs analytics:read,
|
|
44
|
+
* while the game's SDK needs a separate events:write ingest key (the presets keep
|
|
45
|
+
* ingest a separate plane). Overwriting the child's FRAMEDASH_API_KEY would make
|
|
46
|
+
* the game send events with a read-only key and 403 at ingest, so the game keeps
|
|
47
|
+
* whatever ingest key the CI environment already set for it.
|
|
48
|
+
*/
|
|
49
|
+
function passthroughProjectEnv(config) {
|
|
50
|
+
return {
|
|
51
|
+
FRAMEDASH_PROJECT_ID: config.projectId,
|
|
52
|
+
FRAMEDASH_BASE_URL: config.baseUrl,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Validate and resolve the regression-gate flags, exiting on a misconfig. */
|
|
56
|
+
function resolveGateOptions(values, candidateBuildId) {
|
|
57
|
+
let metric;
|
|
58
|
+
if (values.metric !== undefined) {
|
|
59
|
+
const raw = values.metric.trim();
|
|
60
|
+
if (!isRegressionMetric(raw)) {
|
|
61
|
+
error(`Invalid --metric '${raw}'. Allowed: frame_time, memory, gpu_time`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
metric = raw;
|
|
65
|
+
}
|
|
66
|
+
let thresholdPct = 0;
|
|
67
|
+
if (values.threshold !== undefined) {
|
|
68
|
+
thresholdPct = Number(values.threshold);
|
|
69
|
+
if (!Number.isFinite(thresholdPct) || thresholdPct < 0) {
|
|
70
|
+
error("--threshold must be a non-negative number (percent)");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const baseline = values.baseline?.trim();
|
|
75
|
+
// A build compared against itself is a zero diff that would silently pass.
|
|
76
|
+
if (baseline && baseline === candidateBuildId) {
|
|
77
|
+
error("--baseline must differ from the candidate build id (the build under test).");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (values["fail-on-regression"] && !baseline) {
|
|
81
|
+
error("--fail-on-regression requires --baseline (the known-good build to compare against).");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
return { metric, thresholdPct, baseline };
|
|
85
|
+
}
|
|
86
|
+
/** Spawn the profiling command (inheriting stdio) and exit on any failure. */
|
|
87
|
+
function launchProfilingRun(command, env) {
|
|
88
|
+
const child = spawnSync(command, { shell: true, stdio: "inherit", env });
|
|
89
|
+
if (child.error) {
|
|
90
|
+
error(`Failed to launch --command: ${child.error.message}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
if (child.signal) {
|
|
94
|
+
error(`Profiling command was terminated by signal ${child.signal}.`);
|
|
95
|
+
// Conventional 128 + signal number so CI sees a meaningful non-zero code.
|
|
96
|
+
process.exit(128 + (osConstants.signals[child.signal] ?? 0));
|
|
97
|
+
}
|
|
98
|
+
if (typeof child.status === "number" && child.status !== 0) {
|
|
99
|
+
error(`Profiling command exited with code ${child.status}; not gating on incomplete data.`);
|
|
100
|
+
process.exit(child.status);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Fetch the candidate build's list row (the suffix the pre-run snapshot and poll
|
|
105
|
+
* share). Scopes to the candidate buildId so the server's newest-50 cap cannot
|
|
106
|
+
* hide a re-run of an older build, and uses fresh=1 to bypass the aggregation
|
|
107
|
+
* cache so the wait reads live state (a cached, stale-low count could let an old
|
|
108
|
+
* run's data pass the wait).
|
|
109
|
+
*/
|
|
110
|
+
function fetchBuilds(client, values, buildId) {
|
|
111
|
+
return client.get(client.projectPath(buildBuildsPath({ days: values.days, buildId, fresh: true })));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The candidate's perf-event count right now. Read BEFORE the run so the ingest
|
|
115
|
+
* wait can require the count to grow -- a CI re-run for the same build_id must
|
|
116
|
+
* not be satisfied by the previous run's data. Fails closed if the snapshot
|
|
117
|
+
* cannot be read: without a reliable baseline the wait could pass on old data
|
|
118
|
+
* (a false-green gate), so abort before launch rather than guess 0.
|
|
119
|
+
*/
|
|
120
|
+
async function readPriorEventCount(client, values, buildId) {
|
|
121
|
+
let builds;
|
|
122
|
+
try {
|
|
123
|
+
builds = await fetchBuilds(client, values, buildId);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
error(`Could not read the build's pre-run event count (${err instanceof Error ? err.message : String(err)}). Aborting before launch; pass --skip-wait if your SDK flushes synchronously.`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
// A non-error response that is not an array means we cannot trust the baseline
|
|
130
|
+
// (it could read as 0 and let an old run's data satisfy the wait), so fail closed.
|
|
131
|
+
if (!Array.isArray(builds)) {
|
|
132
|
+
error("The builds API returned an unexpected response (no build list); cannot establish a " +
|
|
133
|
+
"pre-run baseline. Aborting before launch; pass --skip-wait if your SDK flushes synchronously.");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
return buildEventCount(builds, buildId);
|
|
137
|
+
}
|
|
138
|
+
/** Poll the builds list until the candidate gains fresh events; exit on timeout. */
|
|
139
|
+
async function awaitIngest(client, values, buildId, priorEventCount, timeoutMs, intervalMs) {
|
|
140
|
+
success(`Waiting up to ${timeoutMs / 1000}s for build '${buildId}' to ingest...`);
|
|
141
|
+
const landed = await waitForIngest({
|
|
142
|
+
buildId,
|
|
143
|
+
minEventCount: priorEventCount,
|
|
144
|
+
timeoutMs,
|
|
145
|
+
intervalMs,
|
|
146
|
+
now: () => Date.now(),
|
|
147
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
148
|
+
fetchBuilds: () => fetchBuilds(client, values, buildId),
|
|
149
|
+
});
|
|
150
|
+
if (!landed) {
|
|
151
|
+
error(`Build '${buildId}' did not ingest fresh events within ${timeoutMs / 1000}s. ` +
|
|
152
|
+
"The SDK may not have flushed, or ingest/aggregation is lagging. " +
|
|
153
|
+
"Increase --ingest-timeout, or check with 'framedash builds'.");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
success(`Build '${buildId}' ingested.`);
|
|
157
|
+
}
|
|
158
|
+
/** Fetch the build comparison, print it, and (with --fail-on-regression) gate. */
|
|
159
|
+
async function runRegressionGate(client, config, values, gate) {
|
|
160
|
+
const comparison = await client.get(client.projectPath(buildBuildComparePath({
|
|
161
|
+
baseline: gate.baseline,
|
|
162
|
+
candidate: gate.candidate,
|
|
163
|
+
days: values.days,
|
|
164
|
+
mapId: values.map,
|
|
165
|
+
platform: values.platform,
|
|
166
|
+
// Bypass the compare cache so a re-run gates on the just-ingested data.
|
|
167
|
+
fresh: true,
|
|
168
|
+
})));
|
|
169
|
+
// A non-error 2xx with a malformed body must fail closed, not crash the gate.
|
|
170
|
+
if (!comparison || !Array.isArray(comparison.diffs)) {
|
|
171
|
+
error("Unexpected response from the builds/compare API (no comparison data).");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
log(config.format === "json"
|
|
175
|
+
? formatOutput(comparison, "json")
|
|
176
|
+
: formatOutput(comparison.diffs, config.format));
|
|
177
|
+
const verdict = evaluateRegression(comparison, {
|
|
178
|
+
metric: gate.metric,
|
|
179
|
+
thresholdPct: gate.thresholdPct,
|
|
180
|
+
});
|
|
181
|
+
// Report-only (no --fail-on-regression): never exit non-zero, but still warn --
|
|
182
|
+
// on a regression, AND when nothing was comparable -- so a misconfigured CI job
|
|
183
|
+
// (wrong build IDs, or a metric with no data) does not silently go green.
|
|
184
|
+
if (!values["fail-on-regression"]) {
|
|
185
|
+
if (verdict.evaluated.length === 0) {
|
|
186
|
+
warn(gate.metric
|
|
187
|
+
? `No comparable '${gate.metric}' data between these builds; the gate evaluated nothing (check the build IDs / metric).`
|
|
188
|
+
: "No comparable performance data between these builds; the gate evaluated nothing (check the build IDs).");
|
|
189
|
+
}
|
|
190
|
+
else if (verdict.failed) {
|
|
191
|
+
warn(`Regression detected (threshold ${gate.thresholdPct}%): ${verdict.offenders
|
|
192
|
+
.map(formatMetricDiff)
|
|
193
|
+
.join("; ")}. Pass --fail-on-regression to fail the build.`);
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Fail closed: nothing comparable means the gate is unmeasured, not passed.
|
|
198
|
+
if (verdict.evaluated.length === 0) {
|
|
199
|
+
error(gate.metric
|
|
200
|
+
? `No comparable '${gate.metric}' data between these builds; cannot evaluate a regression.`
|
|
201
|
+
: "No comparable performance data between these builds; cannot evaluate a regression.");
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
if (verdict.failed) {
|
|
205
|
+
error(`Performance regression detected (threshold ${gate.thresholdPct}%): ${verdict.offenders
|
|
206
|
+
.map(formatMetricDiff)
|
|
207
|
+
.join("; ")}`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
success(`No performance regression beyond ${gate.thresholdPct}% (${verdict.evaluated.length} metric(s) checked).`);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Turnkey CI profiling gate: export the FRAMEDASH_* session contract, launch the
|
|
214
|
+
* configured game/profiling command, wait for its perf data to ingest, then run
|
|
215
|
+
* the perf-diff regression gate against a baseline build.
|
|
216
|
+
*/
|
|
217
|
+
export async function runProfileTest(args) {
|
|
218
|
+
await runCommand({
|
|
219
|
+
args,
|
|
220
|
+
help: HELP,
|
|
221
|
+
options: {
|
|
222
|
+
command: { type: "string" },
|
|
223
|
+
"build-id": { type: "string" },
|
|
224
|
+
branch: { type: "string" },
|
|
225
|
+
commit: { type: "string" },
|
|
226
|
+
scenario: { type: "string" },
|
|
227
|
+
"ingest-timeout": { type: "string" },
|
|
228
|
+
"poll-interval": { type: "string" },
|
|
229
|
+
"skip-wait": { type: "boolean" },
|
|
230
|
+
baseline: { type: "string" },
|
|
231
|
+
metric: { type: "string" },
|
|
232
|
+
threshold: { type: "string" },
|
|
233
|
+
days: { type: "string" },
|
|
234
|
+
map: { type: "string" },
|
|
235
|
+
platform: { type: "string" },
|
|
236
|
+
"fail-on-regression": { type: "boolean" },
|
|
237
|
+
},
|
|
238
|
+
}, async ({ client, config, values }) => {
|
|
239
|
+
const command = values.command?.trim();
|
|
240
|
+
if (!command) {
|
|
241
|
+
error("--command is required (the game/profiling command to launch)");
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
// Resolve the build identity: explicit flags win, else fall back to git.
|
|
245
|
+
// Only spawn `git` for a field the caller did not provide.
|
|
246
|
+
const branchFlag = values.branch;
|
|
247
|
+
const commitFlag = values.commit;
|
|
248
|
+
const identity = resolveProfileIdentity({
|
|
249
|
+
buildId: values["build-id"],
|
|
250
|
+
branch: branchFlag,
|
|
251
|
+
commit: commitFlag,
|
|
252
|
+
scenario: values.scenario,
|
|
253
|
+
}, {
|
|
254
|
+
branch: branchFlag ? undefined : gitOutput(["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
255
|
+
commit: commitFlag ? undefined : gitOutput(["rev-parse", "HEAD"]),
|
|
256
|
+
});
|
|
257
|
+
if (!identity) {
|
|
258
|
+
error("Could not determine a build id: pass --build-id or --commit, or run inside a git repo.");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
// Validate everything up front so a misconfig fails before we spend minutes
|
|
262
|
+
// running the game only to reject the result.
|
|
263
|
+
const gate = resolveGateOptions(values, identity.buildId);
|
|
264
|
+
const ingestTimeoutMs = parseSeconds(values["ingest-timeout"], "ingest-timeout", DEFAULT_INGEST_TIMEOUT_S) * 1000;
|
|
265
|
+
const pollIntervalMs = parseSeconds(values["poll-interval"], "poll-interval", DEFAULT_POLL_INTERVAL_S) * 1000;
|
|
266
|
+
// The ingest snapshot/poll run in a retry loop, so they need a client that
|
|
267
|
+
// THROWS on a transient 429/5xx (the default client exits the process,
|
|
268
|
+
// which would kill the wait on a temporary blip). The gate compare keeps
|
|
269
|
+
// the default exit-on-error client (a compare failure should fail the gate).
|
|
270
|
+
const skipWait = Boolean(values["skip-wait"]);
|
|
271
|
+
const pollClient = skipWait
|
|
272
|
+
? client
|
|
273
|
+
: createClient(config.baseUrl, config.apiKey, config.projectId, { throwOnError: true });
|
|
274
|
+
// Snapshot the candidate's event count BEFORE the run (only when we will
|
|
275
|
+
// wait), so the ingest wait can require fresh events rather than accepting
|
|
276
|
+
// a prior run's data for the same build_id.
|
|
277
|
+
const priorEventCount = skipWait
|
|
278
|
+
? 0
|
|
279
|
+
: await readPriorEventCount(pollClient, values, identity.buildId);
|
|
280
|
+
// 1) Launch the profiling command with the FRAMEDASH_* contract exported, so
|
|
281
|
+
// the SDK's BeginAutomatedSessionFromEnvironment() stamps build_id + ci.* tags
|
|
282
|
+
// onto every event (including the perf_heartbeat the gate reads).
|
|
283
|
+
success(`Running profiling build '${identity.buildId}'${identity.scenario ? ` (scenario: ${identity.scenario})` : ""}`);
|
|
284
|
+
// Build the child environment. Two safeguards:
|
|
285
|
+
// 1) The runner owns the whole FRAMEDASH_* session contract: clear any
|
|
286
|
+
// contract key it did not set this run, so the game cannot inherit stale
|
|
287
|
+
// branch/commit/scenario left in the environment by a prior CI step.
|
|
288
|
+
// 2) The gate key (analytics:read) must not be handed to the game as its key
|
|
289
|
+
// -- the game needs a separate events:write ingest key. When the gate key
|
|
290
|
+
// came from FRAMEDASH_API_KEY in the environment (no --api-key flag), strip
|
|
291
|
+
// it from the child so the game cannot inherit a read key.
|
|
292
|
+
const sessionEnv = buildSessionEnv(identity);
|
|
293
|
+
const childEnv = {
|
|
294
|
+
...process.env,
|
|
295
|
+
...passthroughProjectEnv(config),
|
|
296
|
+
...sessionEnv,
|
|
297
|
+
};
|
|
298
|
+
for (const key of SESSION_ENV_KEYS) {
|
|
299
|
+
if (!(key in sessionEnv))
|
|
300
|
+
delete childEnv[key];
|
|
301
|
+
}
|
|
302
|
+
if (!values["api-key"] && !values["api-key-file"] && process.env.FRAMEDASH_API_KEY) {
|
|
303
|
+
delete childEnv.FRAMEDASH_API_KEY;
|
|
304
|
+
warn("Removed the gate's FRAMEDASH_API_KEY (an analytics:read key) from the launched " +
|
|
305
|
+
"game's environment so it is not used as the game's ingest key. Set the game's " +
|
|
306
|
+
"events:write key separately, or pass the gate key via --api-key/--api-key-file.");
|
|
307
|
+
}
|
|
308
|
+
launchProfilingRun(command, childEnv);
|
|
309
|
+
// 2) Wait for the candidate build's perf events to land (unless skipped).
|
|
310
|
+
if (!skipWait) {
|
|
311
|
+
await awaitIngest(pollClient, values, identity.buildId, priorEventCount, ingestTimeoutMs, pollIntervalMs);
|
|
312
|
+
}
|
|
313
|
+
// 3) Run the perf-diff gate when a baseline is given; otherwise the run is
|
|
314
|
+
// complete (the SDK reported the build; the caller can compare it later).
|
|
315
|
+
if (!gate.baseline) {
|
|
316
|
+
success("Profiling run complete. Pass --baseline to gate on a build-over-build regression.");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await runRegressionGate(client, config, values, {
|
|
320
|
+
baseline: gate.baseline,
|
|
321
|
+
candidate: identity.buildId,
|
|
322
|
+
metric: gate.metric,
|
|
323
|
+
thresholdPct: gate.thresholdPct,
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const HELP = `Usage: framedash run-profile-test --command "<cmd>" [options]
|
|
328
|
+
|
|
329
|
+
Run an automated profiling test end-to-end for CI: export the FRAMEDASH_*
|
|
330
|
+
session contract, launch your game/profiling command, wait for its performance
|
|
331
|
+
data to ingest, then (with --baseline) gate on a build-over-build regression.
|
|
332
|
+
|
|
333
|
+
The launched command inherits these environment variables; an SDK that calls
|
|
334
|
+
BeginAutomatedSessionFromEnvironment() picks them up automatically:
|
|
335
|
+
FRAMEDASH_BUILD_ID the candidate build_id (first-class)
|
|
336
|
+
FRAMEDASH_GIT_BRANCH ci.branch attribute
|
|
337
|
+
FRAMEDASH_GIT_COMMIT ci.commit attribute
|
|
338
|
+
FRAMEDASH_TEST_SCENARIO ci.scenario attribute
|
|
339
|
+
FRAMEDASH_PROJECT_ID / FRAMEDASH_BASE_URL are forwarded so the build targets the
|
|
340
|
+
same project the gate queries. The gate uses an analytics:read key while your
|
|
341
|
+
game needs a separate events:write ingest key: pass the gate key with
|
|
342
|
+
--api-key/--api-key-file and keep the game's ingest key in the launched command's
|
|
343
|
+
FRAMEDASH_API_KEY, so the gate key does not get inherited as the game's key.
|
|
344
|
+
|
|
345
|
+
Required:
|
|
346
|
+
--command <cmd> Game/profiling command to launch (run via the shell)
|
|
347
|
+
|
|
348
|
+
Build identity (each defaults as noted; exported to the child):
|
|
349
|
+
--build-id <id> Candidate build_id (default: --commit, else git HEAD)
|
|
350
|
+
--branch <name> default: git rev-parse --abbrev-ref HEAD
|
|
351
|
+
--commit <sha> default: git rev-parse HEAD
|
|
352
|
+
--scenario <name> Test scenario label (no default)
|
|
353
|
+
|
|
354
|
+
Note: the gate compares ALL of a build_id's samples in the window (the build_id
|
|
355
|
+
is the comparison unit, as in 'perf-diff'). If a CI job may re-run the SAME
|
|
356
|
+
commit, give each run a unique build_id (e.g. --build-id "$GIT_SHA-$RUN_ID") so
|
|
357
|
+
the comparison is not diluted by a previous run's samples.
|
|
358
|
+
|
|
359
|
+
Ingest wait (polls a cache-bypassing live read and waits for the build's
|
|
360
|
+
perf-event count to GROW past its pre-run value, so a re-run for the same build
|
|
361
|
+
id is not satisfied by old data):
|
|
362
|
+
--ingest-timeout <s> Max seconds to wait for fresh events (default: 180)
|
|
363
|
+
--poll-interval <s> Seconds between build-list polls (default: 5)
|
|
364
|
+
--skip-wait Skip the ingest wait (e.g. the SDK flushed synchronously)
|
|
365
|
+
|
|
366
|
+
Regression gate (runs only when --baseline is set):
|
|
367
|
+
--baseline <id> Known-good build_id to compare the candidate against
|
|
368
|
+
--metric <name> Gate on one metric: frame_time, memory, gpu_time (default: all)
|
|
369
|
+
--threshold <pct> Tolerate a regression up to this percent (default: 0)
|
|
370
|
+
--fail-on-regression Exit 1 on a regression beyond the threshold (needs --baseline)
|
|
371
|
+
--days <n> Comparison window in days: 7, 14, 30, 90 (default: 30)
|
|
372
|
+
--map <id> Restrict the comparison to one map
|
|
373
|
+
--platform <name> Restrict the comparison to one platform
|
|
374
|
+
|
|
375
|
+
Global:
|
|
376
|
+
--api-key <key> API key (or FRAMEDASH_API_KEY env)
|
|
377
|
+
--project-id <uuid> Project ID (or FRAMEDASH_PROJECT_ID env)
|
|
378
|
+
--base-url <url> API base URL (default: https://app.framedash.dev)
|
|
379
|
+
--format <fmt> Comparison output format: json, table, csv (default: json)
|
|
380
|
+
-h, --help Show help
|
|
381
|
+
|
|
382
|
+
Example (GitHub Actions gate):
|
|
383
|
+
framedash run-profile-test \\
|
|
384
|
+
--command "./Build/Game.exe -nullrhi -ExecCmds='Automation RunTest Perf'" \\
|
|
385
|
+
--scenario nightly --baseline "$BASE_SHA" --threshold 5 --fail-on-regression`;
|
|
386
|
+
//# sourceMappingURL=run-profile-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-profile-test.js","sourceRoot":"","sources":["../../src/commands/run-profile-test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,9 @@ Commands:
|
|
|
11
11
|
dashboard Show dashboard metrics
|
|
12
12
|
retention Show player retention cohorts
|
|
13
13
|
funnel Analyze event funnels
|
|
14
|
+
builds List builds seen for the project (for perf-diff)
|
|
15
|
+
perf-diff Compare two builds and gate CI on perf regressions
|
|
16
|
+
run-profile-test Run a profiling build, wait for ingest, gate on regression
|
|
14
17
|
query Execute a read-only ClickHouse query
|
|
15
18
|
alerts Manage alert rules (list, create, update, delete)
|
|
16
19
|
maps Manage maps (list, delete)
|
|
@@ -36,6 +39,9 @@ const COMMANDS = {
|
|
|
36
39
|
dashboard: () => import("./commands/dashboard.js").then((m) => m.dashboard),
|
|
37
40
|
retention: () => import("./commands/retention.js").then((m) => m.retention),
|
|
38
41
|
funnel: () => import("./commands/funnel.js").then((m) => m.funnel),
|
|
42
|
+
builds: () => import("./commands/builds.js").then((m) => m.builds),
|
|
43
|
+
"perf-diff": () => import("./commands/perf-diff.js").then((m) => m.perfDiff),
|
|
44
|
+
"run-profile-test": () => import("./commands/run-profile-test.js").then((m) => m.runProfileTest),
|
|
39
45
|
query: () => import("./commands/query.js").then((m) => m.query),
|
|
40
46
|
alerts: () => import("./commands/alerts.js").then((m) => m.alerts),
|
|
41
47
|
maps: () => import("./commands/maps.js").then((m) => m.maps),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;KAC3F,OAAiB,CAAC;AAEpB,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { ApiClient } from "@framedash/api-client";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Create the CLI's API client. By default a request error prints a message and
|
|
4
|
+
* exits the process (the right behavior for one-shot commands). Pass
|
|
5
|
+
* `throwOnError` for a client used inside a retry loop (e.g. the run-profile-test
|
|
6
|
+
* ingest poll), where a transient 429/5xx must be thrown and retried, not exit.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createClient(baseUrl: string, apiKey: string, projectId: string, options?: {
|
|
9
|
+
throwOnError?: boolean;
|
|
10
|
+
}): ApiClient;
|
|
3
11
|
//# sourceMappingURL=create-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AAGjE,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AAGjE;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,SAAS,CAmCX"}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { ApiClient } from "@framedash/api-client";
|
|
2
2
|
import { error } from "./logger.js";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Create the CLI's API client. By default a request error prints a message and
|
|
5
|
+
* exits the process (the right behavior for one-shot commands). Pass
|
|
6
|
+
* `throwOnError` for a client used inside a retry loop (e.g. the run-profile-test
|
|
7
|
+
* ingest poll), where a transient 429/5xx must be thrown and retried, not exit.
|
|
8
|
+
*/
|
|
9
|
+
export function createClient(baseUrl, apiKey, projectId, options) {
|
|
10
|
+
if (options?.throwOnError) {
|
|
11
|
+
return new ApiClient({
|
|
12
|
+
baseUrl,
|
|
13
|
+
apiKey,
|
|
14
|
+
projectId,
|
|
15
|
+
onError(err) {
|
|
16
|
+
throw err;
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
4
20
|
return new ApiClient({
|
|
5
21
|
baseUrl,
|
|
6
22
|
apiKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-client.js","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,MAAM,UAAU,YAAY,
|
|
1
|
+
{"version":3,"file":"create-client.js","sourceRoot":"","sources":["../../src/lib/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC3B,OAAe,EACf,MAAc,EACd,SAAiB,EACjB,OAAoC;IAEpC,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;QAC3B,OAAO,IAAI,SAAS,CAAC;YACpB,OAAO;YACP,MAAM;YACN,SAAS;YACT,OAAO,CAAC,GAAa;gBACpB,MAAM,GAAG,CAAC;YACX,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,SAAS,CAAC;QACpB,OAAO;QACP,MAAM;QACN,SAAS;QACT,OAAO,CAAC,GAAa;YACpB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACxB,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;gBAClC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;oBAC9B,KAAK,CAAC,0CAA0C,UAAU,IAAI,CAAC,CAAC;gBACjE,CAAC;qBAAM,CAAC;oBACP,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;oBACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;oBACpD,MAAM,QAAQ,GACb,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;wBACtC,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,kBAAkB,EAAE;wBAChD,CAAC,CAAC,SAAS,CAAC;oBACd,KAAK,CAAC,wCAAwC,QAAQ,GAAG,CAAC,CAAC;gBAC5D,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpB,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/lib/logger.d.ts
CHANGED
|
@@ -5,5 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function log(message: string): void;
|
|
7
7
|
export declare function success(message: string): void;
|
|
8
|
+
/** Non-fatal warning (the run still exits 0). */
|
|
9
|
+
export declare function warn(message: string): void;
|
|
8
10
|
export declare function error(message: string): void;
|
|
9
11
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/lib/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C"}
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,iDAAiD;AACjD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C"}
|
package/dist/lib/logger.js
CHANGED
|
@@ -9,6 +9,10 @@ export function log(message) {
|
|
|
9
9
|
export function success(message) {
|
|
10
10
|
process.stderr.write(` \u2713 ${message}\n`);
|
|
11
11
|
}
|
|
12
|
+
/** Non-fatal warning (the run still exits 0). */
|
|
13
|
+
export function warn(message) {
|
|
14
|
+
process.stderr.write(` ! ${message}\n`);
|
|
15
|
+
}
|
|
12
16
|
export function error(message) {
|
|
13
17
|
process.stderr.write(` \u2717 ${message}\n`);
|
|
14
18
|
}
|
package/dist/lib/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,GAAG,CAAC,OAAe;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,OAAe;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;AAC/C,CAAC"}
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,GAAG,CAAC,OAAe;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,IAAI,CAAC,OAAe;IACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,OAAO,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,OAAe;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure regression-gate logic for `framedash perf-diff`, kept free of I/O so the
|
|
3
|
+
* CI pass/fail decision is unit-testable. The shapes mirror the builds/compare
|
|
4
|
+
* API response (apps/web .../builds/compare); the CLI cannot import the web
|
|
5
|
+
* app's types, so they are restated here.
|
|
6
|
+
*/
|
|
7
|
+
/** Lower-is-better performance metrics perf-diff can gate on. */
|
|
8
|
+
export type RegressionMetric = "frame_time" | "memory" | "gpu_time";
|
|
9
|
+
export declare const REGRESSION_METRICS: readonly RegressionMetric[];
|
|
10
|
+
export declare function isRegressionMetric(value: string): value is RegressionMetric;
|
|
11
|
+
/** One metric's diff, as returned by the builds/compare API. */
|
|
12
|
+
export interface ApiMetricDiff {
|
|
13
|
+
metric: RegressionMetric;
|
|
14
|
+
baselineP50: number | null;
|
|
15
|
+
candidateP50: number | null;
|
|
16
|
+
/** Signed percent change of candidate P50 vs baseline P50; null = not comparable. */
|
|
17
|
+
diffPct: number | null;
|
|
18
|
+
isRegression: boolean;
|
|
19
|
+
baselineTail: number | null;
|
|
20
|
+
candidateTail: number | null;
|
|
21
|
+
}
|
|
22
|
+
export interface ApiBuildComparison {
|
|
23
|
+
baseline: {
|
|
24
|
+
build_id: string;
|
|
25
|
+
};
|
|
26
|
+
candidate: {
|
|
27
|
+
build_id: string;
|
|
28
|
+
};
|
|
29
|
+
diffs: ApiMetricDiff[];
|
|
30
|
+
}
|
|
31
|
+
export interface RegressionVerdict {
|
|
32
|
+
/** True when at least one evaluated metric regressed beyond the threshold. */
|
|
33
|
+
failed: boolean;
|
|
34
|
+
/** Metrics that regressed beyond the threshold. */
|
|
35
|
+
offenders: ApiMetricDiff[];
|
|
36
|
+
/** Metrics actually evaluated (matching the metric filter, with comparable data). */
|
|
37
|
+
evaluated: ApiMetricDiff[];
|
|
38
|
+
}
|
|
39
|
+
export interface EvaluateOptions {
|
|
40
|
+
/** Restrict the gate to a single metric; default checks all. */
|
|
41
|
+
metric?: RegressionMetric;
|
|
42
|
+
/**
|
|
43
|
+
* A metric fails only when its P50 worsened by MORE than this percentage,
|
|
44
|
+
* letting CI tolerate run-to-run noise. Default 0 = any worsening fails.
|
|
45
|
+
*/
|
|
46
|
+
thresholdPct?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Decide whether a build comparison should fail a CI gate. All regression
|
|
50
|
+
* metrics are lower-is-better, so a positive diffPct (candidate P50 above
|
|
51
|
+
* baseline) is a regression. A null diffPct (no baseline data, or a metric
|
|
52
|
+
* unavailable on the platform, e.g. GPU timing) is not comparable and is
|
|
53
|
+
* skipped -- neither an offender nor counted as evaluated.
|
|
54
|
+
*/
|
|
55
|
+
export declare function evaluateRegression(comparison: ApiBuildComparison, options?: EvaluateOptions): RegressionVerdict;
|
|
56
|
+
/** A one-line human summary of a single metric diff (for CI logs). */
|
|
57
|
+
export declare function formatMetricDiff(d: ApiMetricDiff): string;
|
|
58
|
+
//# sourceMappingURL=perf-diff-eval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-diff-eval.d.ts","sourceRoot":"","sources":["../../src/lib/perf-diff-eval.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEpE,eAAO,MAAM,kBAAkB,EAAE,SAAS,gBAAgB,EAAyC,CAAC;AAEpG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,qFAAqF;IACrF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,SAAS,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,KAAK,EAAE,aAAa,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IACjC,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;IAChB,mDAAmD;IACnD,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,qFAAqF;IACrF,SAAS,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC/B,gEAAgE;IAChE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,kBAAkB,EAC9B,OAAO,GAAE,eAAoB,GAC3B,iBAAiB,CAmBnB;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAQzD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure regression-gate logic for `framedash perf-diff`, kept free of I/O so the
|
|
3
|
+
* CI pass/fail decision is unit-testable. The shapes mirror the builds/compare
|
|
4
|
+
* API response (apps/web .../builds/compare); the CLI cannot import the web
|
|
5
|
+
* app's types, so they are restated here.
|
|
6
|
+
*/
|
|
7
|
+
export const REGRESSION_METRICS = ["frame_time", "memory", "gpu_time"];
|
|
8
|
+
export function isRegressionMetric(value) {
|
|
9
|
+
return REGRESSION_METRICS.includes(value);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Decide whether a build comparison should fail a CI gate. All regression
|
|
13
|
+
* metrics are lower-is-better, so a positive diffPct (candidate P50 above
|
|
14
|
+
* baseline) is a regression. A null diffPct (no baseline data, or a metric
|
|
15
|
+
* unavailable on the platform, e.g. GPU timing) is not comparable and is
|
|
16
|
+
* skipped -- neither an offender nor counted as evaluated.
|
|
17
|
+
*/
|
|
18
|
+
export function evaluateRegression(comparison, options = {}) {
|
|
19
|
+
// Clamp a non-finite or negative threshold to 0 (the strictest gate) so a bad
|
|
20
|
+
// value fails safe rather than silently disabling the gate -- `x > Infinity`
|
|
21
|
+
// and `x > NaN` are both always false, which would pass every regression. The
|
|
22
|
+
// command validates --threshold too; this keeps the exported helper robust on
|
|
23
|
+
// its own.
|
|
24
|
+
const threshold = Number.isFinite(options.thresholdPct) && options.thresholdPct >= 0
|
|
25
|
+
? options.thresholdPct
|
|
26
|
+
: 0;
|
|
27
|
+
// Defensive: a malformed response (missing diffs) yields no evaluable metrics,
|
|
28
|
+
// which the caller treats as "nothing comparable" -> fail-closed. `typeof ===
|
|
29
|
+
// "number"` (not `!== null`) also skips an undefined/missing diffPct safely.
|
|
30
|
+
const diffs = Array.isArray(comparison?.diffs) ? comparison.diffs : [];
|
|
31
|
+
const evaluated = diffs.filter((d) => (!options.metric || d.metric === options.metric) && typeof d.diffPct === "number");
|
|
32
|
+
const offenders = evaluated.filter((d) => d.diffPct > threshold);
|
|
33
|
+
return { failed: offenders.length > 0, offenders, evaluated };
|
|
34
|
+
}
|
|
35
|
+
/** A one-line human summary of a single metric diff (for CI logs). */
|
|
36
|
+
export function formatMetricDiff(d) {
|
|
37
|
+
// `typeof !== "number"` covers a null OR an undefined/missing diffPct, so the
|
|
38
|
+
// toFixed() below can never throw on a malformed row.
|
|
39
|
+
if (typeof d.diffPct !== "number")
|
|
40
|
+
return `${d.metric}: no comparable data`;
|
|
41
|
+
const sign = d.diffPct >= 0 ? "+" : "";
|
|
42
|
+
return `${d.metric}: ${sign}${d.diffPct.toFixed(1)}% (P50 ${fmtValue(d.baselineP50)} -> ${fmtValue(d.candidateP50)})`;
|
|
43
|
+
}
|
|
44
|
+
function fmtValue(value) {
|
|
45
|
+
// `typeof !== "number"` covers null AND a missing/undefined value on a
|
|
46
|
+
// malformed row, so toFixed() can never throw here.
|
|
47
|
+
return typeof value !== "number" ? "n/a" : value.toFixed(2);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=perf-diff-eval.js.map
|