@keepsnappy/agent 0.1.0-alpha.19 → 0.1.0-alpha.21
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 +1 -1
- package/dist/appium-driver.js +334 -19
- package/dist/keepsnappy.js +577 -237
- package/package.json +1 -1
package/dist/keepsnappy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// apps/agent/src/cli.ts
|
|
4
|
-
import { resolve as
|
|
4
|
+
import { resolve as resolve9 } from "node:path";
|
|
5
5
|
import { parseArgs as parseArgs4 } from "node:util";
|
|
6
6
|
|
|
7
7
|
// apps/agent/src/config.ts
|
|
@@ -2334,7 +2334,7 @@ function withLaunchNames(windows) {
|
|
|
2334
2334
|
}
|
|
2335
2335
|
|
|
2336
2336
|
// packages/derive/dist/src/model.js
|
|
2337
|
-
var DERIVATION_VERSION =
|
|
2337
|
+
var DERIVATION_VERSION = 29;
|
|
2338
2338
|
// packages/stats/dist/src/core/rng.js
|
|
2339
2339
|
function splitmix32(seed) {
|
|
2340
2340
|
let s = seed >>> 0;
|
|
@@ -3608,6 +3608,7 @@ function occurrence(windows, budgetMs, idleNeutral = false, frameMs, upper = fal
|
|
|
3608
3608
|
...upper ? { timerCarriedMs: carried, renderMsUpper: renderMs === undefined || carried === undefined ? undefined : renderMs + carried } : {}
|
|
3609
3609
|
};
|
|
3610
3610
|
}
|
|
3611
|
+
var SAME_COMMIT_MS = 0.01;
|
|
3611
3612
|
var UPDATERS_KEPT = 5;
|
|
3612
3613
|
var CPU_FUNCTIONS_KEPT = 10;
|
|
3613
3614
|
var UNTIMED_COMPONENTS_KEPT = 10;
|
|
@@ -3746,7 +3747,9 @@ function profileMetrics(windows, steps, options = {}) {
|
|
|
3746
3747
|
const selected = middleBy((active.length ? active : occ).filter((o) => o.window), (o) => o.commitMaxMs, (o) => num2(o.window.raw.id) ?? 0, (o) => num2(o.window.raw.t0) ?? o.window.startWall);
|
|
3747
3748
|
const identity = options.commitIdentity && metrics.has(`${key}.commit_max_ms`) && COMMIT_IDENTITY_KINDS.includes(kind) && selected?.slowestCommit;
|
|
3748
3749
|
const openedBy = selected?.window ? options.openedBy?.get(selected.window) : undefined;
|
|
3749
|
-
|
|
3750
|
+
const streamed = identity ? options.commitTops?.get(identity.seq) : undefined;
|
|
3751
|
+
const tops = identity && streamed && Math.abs(streamed.ms - identity.ms) <= SAME_COMMIT_MS ? streamed : undefined;
|
|
3752
|
+
profiles.push({ ...profile, ...identity ? { slowestCommit: { ...identity, ...tops?.top ? { top: tops.top } : {}, ...tops?.updater ? { updater: tops.updater } : {} } } : {}, ...openedBy ? { openedBy } : {} });
|
|
3750
3753
|
}
|
|
3751
3754
|
return { metrics, profiles };
|
|
3752
3755
|
}
|
|
@@ -5003,7 +5006,7 @@ function keyFirstOpens(windows) {
|
|
|
5003
5006
|
}
|
|
5004
5007
|
|
|
5005
5008
|
// packages/derive/dist/src/derive.js
|
|
5006
|
-
var DERIVATION_VERSIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28];
|
|
5009
|
+
var DERIVATION_VERSIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29];
|
|
5007
5010
|
var windowsForVersion = (windows, version = DERIVATION_VERSION) => version >= 22 ? keyFirstOpens(windows) : windows;
|
|
5008
5011
|
function stepWindowsFor(windows, version = DERIVATION_VERSION) {
|
|
5009
5012
|
return windowsForVersion(version >= 16 ? withLaunchNames(windows).filter((w) => !isBackgroundWindow(w)) : windows, version);
|
|
@@ -5022,7 +5025,7 @@ function samplerFrameBudgetMs(samples) {
|
|
|
5022
5025
|
}
|
|
5023
5026
|
return hz > 1 ? frameBudgetMs(Math.round(hz)) : undefined;
|
|
5024
5027
|
}
|
|
5025
|
-
function profileOptions(windows, version, processWindows = windows) {
|
|
5028
|
+
function profileOptions(windows, version, processWindows = windows, commitStreams = []) {
|
|
5026
5029
|
if (version < 26)
|
|
5027
5030
|
return {};
|
|
5028
5031
|
const processes = new Set(processWindows.map((w) => w.raw.react?.causes?.process).filter((p) => typeof p === "string"));
|
|
@@ -5036,7 +5039,9 @@ function profileOptions(windows, version, processWindows = windows) {
|
|
|
5036
5039
|
if (opener)
|
|
5037
5040
|
openedBy.set(screen, opener.name);
|
|
5038
5041
|
}
|
|
5039
|
-
|
|
5042
|
+
const [only] = processes.size === 1 ? processes : [];
|
|
5043
|
+
const commitTops = new Map(commitStreams.filter((s) => s.process === only).flatMap((s) => s.commits.map((c) => [c.seq, c])));
|
|
5044
|
+
return { fullRowMs: FULL_ROW_MS, commitIdentity: processes.size <= 1, ...commitTops.size ? { commitTops } : {}, openedBy };
|
|
5040
5045
|
}
|
|
5041
5046
|
function deriveRun(input) {
|
|
5042
5047
|
const { run, markers = [], ri, clockOffsetMs, clockErrorMs, samples = [], memWindow } = input;
|
|
@@ -5059,7 +5064,7 @@ function deriveRun(input) {
|
|
|
5059
5064
|
return { rows, steps, run: final, profiles: [], derivationVersion: version };
|
|
5060
5065
|
const budget = input.frameBudgetMs ?? (version >= 6 ? samplerFrameBudgetMs(samplesInRun(samples, final.t0, final.t1)) : undefined);
|
|
5061
5066
|
const compareMs = version >= 20 ? LIMIT_FRAME_MS : budget ?? frameBudgetMs();
|
|
5062
|
-
const allComponents = profileOptions(windows, version, input.windows);
|
|
5067
|
+
const allComponents = profileOptions(windows, version, input.windows, input.commitStreams);
|
|
5063
5068
|
const prof = profileMetrics(windows, steps, {
|
|
5064
5069
|
...allComponents,
|
|
5065
5070
|
frameBudgetMs: version >= 11 ? compareMs : Number(compareMs.toFixed(1)),
|
|
@@ -5215,6 +5220,25 @@ var markerEnv = (url, runId) => ({
|
|
|
5215
5220
|
var upperFirst = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
|
|
5216
5221
|
var lowerFirst = (s) => s ? s.charAt(0).toLowerCase() + s.slice(1) : s;
|
|
5217
5222
|
|
|
5223
|
+
// packages/present/dist/src/names.js
|
|
5224
|
+
var textPart = (text) => ({ text });
|
|
5225
|
+
var componentPart = (text) => ({ text, component: true });
|
|
5226
|
+
var namePartsText = (parts) => parts.map((p) => p.text).join("");
|
|
5227
|
+
var plainParts = (text) => [textPart(text)];
|
|
5228
|
+
function nameParts(parts) {
|
|
5229
|
+
const out = [];
|
|
5230
|
+
for (const p of parts) {
|
|
5231
|
+
if (!p.text)
|
|
5232
|
+
continue;
|
|
5233
|
+
const last = out.at(-1);
|
|
5234
|
+
if (last && !last.component && !p.component)
|
|
5235
|
+
out[out.length - 1] = textPart(last.text + p.text);
|
|
5236
|
+
else
|
|
5237
|
+
out.push(p);
|
|
5238
|
+
}
|
|
5239
|
+
return out;
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5218
5242
|
// packages/present/dist/src/placeWords.js
|
|
5219
5243
|
var GENERIC_GROUPS = new Set(["app", "tabs", "root"]);
|
|
5220
5244
|
var ORDINAL_SUFFIXES = ["th", "st", "nd", "rd"];
|
|
@@ -5250,6 +5274,7 @@ var STATE_WORDS = {
|
|
|
5250
5274
|
"no-change": "No change",
|
|
5251
5275
|
"cant-tell": "Can’t tell"
|
|
5252
5276
|
};
|
|
5277
|
+
var STATE_ORDER = ["worse", "warning", "cant-tell", "better", "no-change"];
|
|
5253
5278
|
var INFO_WORDS = { heat: "Phone heated up" };
|
|
5254
5279
|
var isWorseState = (state) => state === "worse";
|
|
5255
5280
|
var NOT_BLOCKING = "not blocking";
|
|
@@ -5307,6 +5332,17 @@ function valueText(measure, v) {
|
|
|
5307
5332
|
const def = defOf(measure);
|
|
5308
5333
|
return format(def.unit, def.decimals, v);
|
|
5309
5334
|
}
|
|
5335
|
+
function rangeText(measure, lo, hi) {
|
|
5336
|
+
const a = valueText(measure, lo);
|
|
5337
|
+
const b = valueText(measure, hi);
|
|
5338
|
+
if (a === b)
|
|
5339
|
+
return a;
|
|
5340
|
+
const unitOf = (text) => text.slice(text.lastIndexOf(" ") + 1);
|
|
5341
|
+
const shared = a.includes(" ") && b.includes(" ") && unitOf(a) === unitOf(b);
|
|
5342
|
+
if (shared)
|
|
5343
|
+
return `${a.slice(0, a.lastIndexOf(" "))}–${b}`;
|
|
5344
|
+
return a.includes(" ") || b.includes(" ") ? `${a} – ${b}` : `${a}–${b}`;
|
|
5345
|
+
}
|
|
5310
5346
|
var KEY_SEPARATOR = ":";
|
|
5311
5347
|
function breakdownMeasure(field) {
|
|
5312
5348
|
const at = field.indexOf(KEY_SEPARATOR);
|
|
@@ -5384,12 +5420,18 @@ var screenLeadText = (tier, subject, measure, main, pr) => `${STATE_WORDS[TIER_S
|
|
|
5384
5420
|
var FOR_INFORMATION = "For information";
|
|
5385
5421
|
var informationLeadText = (subject, measure, main, pr) => `${FOR_INFORMATION}: ${leadBody(subject, measure, main, pr)}`;
|
|
5386
5422
|
function placeShownName(p, o = {}) {
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5423
|
+
return namePartsText(placeShownNameParts(p, o));
|
|
5424
|
+
}
|
|
5425
|
+
function placeShownNameParts(p, o = {}) {
|
|
5426
|
+
const target = p.target ? componentNameParts(p.target.name, p.target.file) : plainParts(libraryViewName(p.kind, p.host ?? ""));
|
|
5427
|
+
const named = p.target && p.parent ? [...target, textPart(" in "), ...componentNameParts(p.parent.name, p.parent.file), textPart(p.k ? `, ${ordinal(p.k)}` : "")] : target;
|
|
5428
|
+
const screen = typeof o.onScreen === "string" ? plainParts(o.onScreen) : o.onScreen;
|
|
5429
|
+
return nameParts(screen?.length && namePartsText(screen) ? [...named, textPart(" on "), ...screen] : named);
|
|
5390
5430
|
}
|
|
5391
5431
|
var libraryViewName = (kind, hostType) => `${kind === "drag" ? "Scroll" : "Tap"} outside app components (${hostType})`;
|
|
5392
|
-
|
|
5432
|
+
var componentShownName = (name, file) => componentFileName(name, file);
|
|
5433
|
+
var componentNameParts = (name, file) => [componentPart(componentFileName(name, file))];
|
|
5434
|
+
function componentFileName(name, file) {
|
|
5393
5435
|
if (name !== "default" || !file)
|
|
5394
5436
|
return name;
|
|
5395
5437
|
const path = file.replace(/\.[^.\/]+$/, "");
|
|
@@ -5456,21 +5498,36 @@ function foldTwins(rows, placeOf, also = () => "") {
|
|
|
5456
5498
|
return { rows: out, folded: rows.length - out.length };
|
|
5457
5499
|
}
|
|
5458
5500
|
var foldedTwinsText = (n) => `${n} “${PLACE_KIND_WORDS.after3s}” ${n === 1 ? "twin" : "twins"} folded into ${n === 1 ? "its place" : "their places"}`;
|
|
5459
|
-
function
|
|
5501
|
+
function placeIdShownNameParts(id, o = {}) {
|
|
5460
5502
|
const p = placeParts(id);
|
|
5461
5503
|
if (!p)
|
|
5462
5504
|
return null;
|
|
5463
5505
|
const component = (part) => ({ name: part.name, file: part.file ?? "" });
|
|
5464
|
-
const screen = p.screen.name === "(no screen)" ?
|
|
5506
|
+
const screen = p.screen.name === "(no screen)" ? [] : p.screen.name === "(outside app components)" ? [textPart(screenName(p.screen.file ?? ""))] : componentNameParts(p.screen.name, p.screen.file ?? "");
|
|
5465
5507
|
if (p.kind === "screen" || p.kind === "launch")
|
|
5466
|
-
return screen
|
|
5467
|
-
return
|
|
5508
|
+
return nameParts([...screen, textPart(p.kind === "screen" && typeof p.visit === "number" && p.visit > 1 ? ` (${ordinal(p.visit)} visit)` : "")]);
|
|
5509
|
+
return placeShownNameParts({ kind: p.kind, target: p.outsideHost === null ? component(p.target) : null, parent: p.parent ? component(p.parent) : null, k: p.k, ...p.outsideHost === null ? {} : { host: p.outsideHost } }, { onScreen: o.onScreen === false ? undefined : screen });
|
|
5468
5510
|
}
|
|
5469
5511
|
var isLibraryPlace = (id) => placeParts(id)?.outsideHost != null;
|
|
5470
|
-
function
|
|
5471
|
-
const
|
|
5512
|
+
function dropTail(parts, n) {
|
|
5513
|
+
const out = [];
|
|
5514
|
+
for (const p of [...parts].reverse()) {
|
|
5515
|
+
if (n >= p.text.length) {
|
|
5516
|
+
n -= p.text.length;
|
|
5517
|
+
continue;
|
|
5518
|
+
}
|
|
5519
|
+
out.unshift({ ...p, text: p.text.slice(0, p.text.length - n) });
|
|
5520
|
+
n = 0;
|
|
5521
|
+
}
|
|
5522
|
+
return out;
|
|
5523
|
+
}
|
|
5524
|
+
function stemNameParts(stem, kind) {
|
|
5525
|
+
const shown = placeIdShownNameParts(stem);
|
|
5472
5526
|
if (shown !== null)
|
|
5473
|
-
return isLibraryPlace(stem) ? shown : `${KIND_WORD[kind]}:
|
|
5527
|
+
return nameParts(isLibraryPlace(stem) ? shown : [textPart(`${KIND_WORD[kind]}: `), ...shown]);
|
|
5528
|
+
return plainParts(stemText(stem, kind));
|
|
5529
|
+
}
|
|
5530
|
+
function stemText(stem, kind) {
|
|
5474
5531
|
if (kind === "journey")
|
|
5475
5532
|
return PLACE_KIND_WORDS.journey;
|
|
5476
5533
|
if (kind === "other")
|
|
@@ -5486,12 +5543,13 @@ function stemName(stem, kind) {
|
|
|
5486
5543
|
const visit = visitMark ? Number(visitMark[2]) : 1;
|
|
5487
5544
|
return `${PLACE_KIND_WORDS.screen}: ${screenName(visitMark ? visitMark[1] : rest)}${visit > 1 ? ` (${ordinal(visit)} visit)` : ""}`;
|
|
5488
5545
|
}
|
|
5489
|
-
|
|
5546
|
+
var windowName = (id) => namePartsText(windowNameParts(id));
|
|
5547
|
+
function windowNameParts(id) {
|
|
5490
5548
|
if (id === "flow")
|
|
5491
|
-
return PLACE_KIND_WORDS.journey;
|
|
5549
|
+
return plainParts(PLACE_KIND_WORDS.journey);
|
|
5492
5550
|
const ref = placeRef(id);
|
|
5493
|
-
const name =
|
|
5494
|
-
return ref.segment === "after3s" ?
|
|
5551
|
+
const name = stemNameParts(ref.stem, ref.kind);
|
|
5552
|
+
return ref.segment === "after3s" ? nameParts([...name, textPart(` · ${PLACE_KIND_WORDS.after3s}`)]) : name;
|
|
5495
5553
|
}
|
|
5496
5554
|
function placeKindWord(id) {
|
|
5497
5555
|
if (id === "flow")
|
|
@@ -5499,31 +5557,33 @@ function placeKindWord(id) {
|
|
|
5499
5557
|
const { kind } = placeRef(id);
|
|
5500
5558
|
return kind === "other" ? "Window" : KIND_WORD[kind];
|
|
5501
5559
|
}
|
|
5502
|
-
|
|
5560
|
+
var windowQualified = (id) => namePartsText(windowQualifiedParts(id));
|
|
5561
|
+
function windowQualifiedParts(id) {
|
|
5503
5562
|
const word = placeOpenWord(id);
|
|
5504
|
-
const label =
|
|
5563
|
+
const label = placeLabelParts(id);
|
|
5505
5564
|
if (!word)
|
|
5506
5565
|
return label;
|
|
5507
5566
|
const after = placeRef(id).segment === "after3s" ? ` · ${PLACE_KIND_WORDS.after3s}` : "";
|
|
5508
|
-
return
|
|
5567
|
+
return nameParts([...after ? dropTail(label, after.length) : label, textPart(` (${word})${after}`)]);
|
|
5509
5568
|
}
|
|
5510
5569
|
var ELEMENT_SEPARATORS = /(?<!\d)\.|\.(?!\d)|[_-]+/g;
|
|
5511
5570
|
var elementWords = (testId) => testId.replace(ELEMENT_SEPARATORS, " ").replace(/\s+/g, " ").trim();
|
|
5512
|
-
|
|
5571
|
+
var placeLabel = (id, screen) => namePartsText(placeLabelParts(id, screen));
|
|
5572
|
+
function placeLabelParts(id, screen) {
|
|
5513
5573
|
if (id === "flow")
|
|
5514
|
-
return PLACE_KIND_WORDS.journey;
|
|
5574
|
+
return plainParts(PLACE_KIND_WORDS.journey);
|
|
5515
5575
|
const ref = placeRef(id);
|
|
5516
5576
|
const after = ref.segment === "after3s" ? ` · ${PLACE_KIND_WORDS.after3s}` : "";
|
|
5517
|
-
const shown =
|
|
5577
|
+
const shown = placeIdShownNameParts(ref.stem);
|
|
5518
5578
|
if (shown !== null)
|
|
5519
|
-
return isLibraryPlace(ref.stem) ?
|
|
5579
|
+
return nameParts([...isLibraryPlace(ref.stem) ? [] : [textPart(`${KIND_WORD[ref.kind]} · `)], ...shown, textPart(after)]);
|
|
5520
5580
|
if (ref.kind === "press" || ref.kind === "drag" || ref.kind === "step") {
|
|
5521
5581
|
const element = elementWords(ref.stem.slice(ref.stem.indexOf(":") + 1).replace(OPEN_MARK, ""));
|
|
5522
|
-
return `${KIND_WORD[ref.kind]} · “${element}”${screen ? ` · ${screen}` : ""}${after}
|
|
5582
|
+
return plainParts(`${KIND_WORD[ref.kind]} · “${element}”${screen ? ` · ${screen}` : ""}${after}`);
|
|
5523
5583
|
}
|
|
5524
5584
|
const name = windowName(ref.stem);
|
|
5525
5585
|
const colon = name.indexOf(": ");
|
|
5526
|
-
return `${colon > 0 && ref.kind !== "other" ? `${name.slice(0, colon)} · ${name.slice(colon + 2)}` : name}${after}
|
|
5586
|
+
return plainParts(`${colon > 0 && ref.kind !== "other" ? `${name.slice(0, colon)} · ${name.slice(colon + 2)}` : name}${after}`);
|
|
5527
5587
|
}
|
|
5528
5588
|
// packages/present/dist/src/placeNames.js
|
|
5529
5589
|
function shortestPaths(files) {
|
|
@@ -5542,42 +5602,34 @@ function shortestPaths(files) {
|
|
|
5542
5602
|
}
|
|
5543
5603
|
return out;
|
|
5544
5604
|
}
|
|
5545
|
-
// packages/present/dist/src/
|
|
5546
|
-
var
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
var newProblems = (n) => `${n.toLocaleString("en-US")} new ${n === 1 ? "problem" : "problems"}`;
|
|
5557
|
-
var PROBLEMS_BLOCK_WORDS = {
|
|
5558
|
-
branch: {
|
|
5559
|
-
heading: "New problems in this branch",
|
|
5560
|
-
sub: "too slow on their own, whatever main does; they don’t block the merge",
|
|
5561
|
-
seeAll: (n) => `See all ${newProblems(n)}`,
|
|
5562
|
-
alsoLine: (n) => `${problems(n)} also on main`,
|
|
5563
|
-
notComparedLine: (n) => `${problems(n)} not compared with main`,
|
|
5564
|
-
choices: { new: PROBLEM_TAG_WORDS.new, also: PROBLEM_TAG_WORDS.main, not_compared: PROBLEM_TAG_WORDS.none },
|
|
5565
|
-
panelTitle: "Problems in this branch",
|
|
5566
|
-
panelNote: "Each one is too slow in this branch’s own runs, whatever main does. Problems never block.",
|
|
5567
|
-
filter: "Which problems"
|
|
5568
|
-
},
|
|
5569
|
-
main: {
|
|
5570
|
-
heading: "New problems in this session",
|
|
5571
|
-
sub: "too slow on their own, whatever earlier sessions did",
|
|
5572
|
-
seeAll: (n) => `See all ${newProblems(n)}`,
|
|
5573
|
-
alsoLine: (n) => `${problems(n)} also in earlier sessions`,
|
|
5574
|
-
notComparedLine: (n) => `${problems(n)} not compared with earlier sessions`,
|
|
5575
|
-
choices: { new: "New in this session", also: "Also in earlier sessions", not_compared: "Not compared with earlier sessions" },
|
|
5576
|
-
panelTitle: "Problems in this session",
|
|
5577
|
-
panelNote: "Each one is too slow in this session’s own runs, whatever earlier sessions did. Problems never block.",
|
|
5578
|
-
filter: "Which problems"
|
|
5579
|
-
}
|
|
5605
|
+
// packages/present/dist/src/words.js
|
|
5606
|
+
var REASON_WORDS = {
|
|
5607
|
+
"no-baseline": "there are no main runs on this phone and journey yet",
|
|
5608
|
+
"too-few-runs": "there are too few valid runs on a side",
|
|
5609
|
+
"judged-measures-missing": "the judged measures are missing on a side",
|
|
5610
|
+
"measured-differently": "the sides were measured differently",
|
|
5611
|
+
"runs-unknown": "a change that would count has no value in some runs, so it cannot be confirmed in every run",
|
|
5612
|
+
"facts-version-mismatch": "the sides were derived with different versions",
|
|
5613
|
+
"not-judged-on-simulators": "timing is not judged on simulators",
|
|
5614
|
+
"platforms-differ": "the sides ran on different platforms (iOS and Android are never compared)",
|
|
5615
|
+
"not-enough-main": "main does not have enough sessions on this phone and journey yet"
|
|
5580
5616
|
};
|
|
5617
|
+
var waitingWords = (runs) => `${runs} main ${runs === 1 ? "run waits" : "runs wait"} for a reprocess`;
|
|
5618
|
+
var AT_MOST = "≤ ";
|
|
5619
|
+
var atMost = (text, isBound) => isBound ? `${AT_MOST}${text}` : text;
|
|
5620
|
+
var AT_LEAST = "≥ ";
|
|
5621
|
+
var atLeast = (text, isBound) => isBound ? `${AT_LEAST}${text}` : text;
|
|
5622
|
+
|
|
5623
|
+
// packages/present/dist/src/place-tier.js
|
|
5624
|
+
var slowerCountsText = (n, sep = " · ") => [n.worse ? `${n.worse} ${STATE_WORDS.worse.toLowerCase()}` : "", n.slight ? `${n.slight} ${STATE_WORDS.warning.toLowerCase()}` : ""].filter(Boolean).join(sep);
|
|
5625
|
+
|
|
5626
|
+
// packages/present/dist/src/shown.js
|
|
5627
|
+
function feltRangeText(ms, byMs, direction) {
|
|
5628
|
+
if (!ms || !(ms > 0))
|
|
5629
|
+
return null;
|
|
5630
|
+
return `${rangeText("jank_ms", ms, byMs !== null && byMs !== undefined && byMs - ms >= 1 ? byMs : ms)} ${direction}`;
|
|
5631
|
+
}
|
|
5632
|
+
|
|
5581
5633
|
// packages/present/dist/src/stability.js
|
|
5582
5634
|
var UNSTABLE_WORDS = {
|
|
5583
5635
|
short: "main varies too much here",
|
|
@@ -5724,7 +5776,7 @@ var READ_USAGE = {
|
|
|
5724
5776
|
page: "--page must be a whole number from 1; use it only with places or compare.",
|
|
5725
5777
|
all: "--all is for places or compare; do not combine it with --page, --offset or --short.",
|
|
5726
5778
|
detailFlag: " --detail (places: include recorded run detail; also included by --verbose or component paging)",
|
|
5727
|
-
detail: "Include recorded per-run component and window detail.",
|
|
5779
|
+
detail: "Include recorded per-run component and window detail: an opt-in diagnostic that reads the runs' recorded profiles (slower); ranking places never needs it.",
|
|
5728
5780
|
compareTop: "--top-components must be a whole number from 1 to 200 for compare.",
|
|
5729
5781
|
top: "--top-components must be a whole number from 0 to 20, or from 1 to 200 with --page or --all.",
|
|
5730
5782
|
paging: "The API did not advance the component page; stopped without claiming the list is complete.",
|
|
@@ -5734,23 +5786,123 @@ var READ_USAGE = {
|
|
|
5734
5786
|
var componentListsText = (totals, shown) => `Stored component rows: ${shown.rows} of ${totals.rows} compared; ${shown.added} of ${totals.added} ${READ_WORDS.onlyB.toLowerCase()}; ${shown.removed} of ${totals.removed} ${READ_WORDS.onlyA.toLowerCase()}.`;
|
|
5735
5787
|
var notMeasuredText = (side) => `${READ_WORDS.notMeasured} in run ${side.toUpperCase()}`;
|
|
5736
5788
|
|
|
5737
|
-
// packages/present/dist/src/
|
|
5738
|
-
var
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5789
|
+
// packages/present/dist/src/problems.js
|
|
5790
|
+
var FAR_OVER_BUDGET_WORD = "Far over budget";
|
|
5791
|
+
var heaviestWords = (name, value) => [textPart(`${HEAVIEST_WORD}: `), componentPart(name), ...value ? [textPart(` ${value}`)] : []];
|
|
5792
|
+
var HEAVIEST_WORD = "heaviest";
|
|
5793
|
+
var PROBLEM_TAG_WORDS = { new: NEW_IN_PR_CHIP, main: "Also on main", none: NOT_COMPARED_WORD };
|
|
5794
|
+
var PROBLEM_TAG_LINE_WORDS = { new: "new in this PR", main: "also on main", none: "not compared with main" };
|
|
5795
|
+
var problems = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "problem" : "problems"}`;
|
|
5796
|
+
var places = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "place" : "places"}`;
|
|
5797
|
+
var problemsAtPlaces = (n, at) => `${problems(n)} at ${places(at)}`;
|
|
5798
|
+
function problemCountsText(c, own) {
|
|
5799
|
+
const tags = c.tags;
|
|
5800
|
+
const split = tags ? `: ${[`${tags.new} ${PROBLEM_TAG_LINE_WORDS.new}`, ...["main", "none"].filter((t) => tags[t]).map((t) => `${tags[t]} ${PROBLEM_TAG_LINE_WORDS[t]}`)].join(", ")}` : "";
|
|
5801
|
+
return `${problemsAtPlaces(c.count, c.places)} in ${own} own runs${split}${c.far_count ? `; ${c.far_count} of them ${FAR_OVER_BUDGET_WORD.toLowerCase()}` : ""}`;
|
|
5802
|
+
}
|
|
5803
|
+
var newProblems = (n) => `${n.toLocaleString("en-US")} new ${n === 1 ? "problem" : "problems"}`;
|
|
5804
|
+
var PROBLEMS_BLOCK_WORDS = {
|
|
5805
|
+
branch: {
|
|
5806
|
+
heading: "New problems in this branch",
|
|
5807
|
+
sub: "too slow on their own, whatever main does; they don’t block the merge",
|
|
5808
|
+
seeAll: (n) => `See all ${newProblems(n)}`,
|
|
5809
|
+
alsoLine: (n, at) => `${PROBLEM_TAG_WORDS.main}: ${problemsAtPlaces(n, at)}`,
|
|
5810
|
+
notComparedLine: (n, at) => `${PROBLEM_TAG_WORDS.none}: ${problemsAtPlaces(n, at)}`,
|
|
5811
|
+
choices: { new: PROBLEM_TAG_WORDS.new, also: PROBLEM_TAG_WORDS.main, not_compared: PROBLEM_TAG_WORDS.none },
|
|
5812
|
+
panelTitle: "Problems in this branch",
|
|
5813
|
+
panelNote: "Each one is too slow in this branch’s own runs, whatever main does. Problems never block.",
|
|
5814
|
+
filter: "Which problems"
|
|
5815
|
+
},
|
|
5816
|
+
main: {
|
|
5817
|
+
heading: "New problems in this session",
|
|
5818
|
+
sub: "too slow on their own, whatever earlier sessions did",
|
|
5819
|
+
seeAll: (n) => `See all ${newProblems(n)}`,
|
|
5820
|
+
alsoLine: (n, at) => `Also in earlier sessions: ${problemsAtPlaces(n, at)}`,
|
|
5821
|
+
notComparedLine: (n, at) => `Not compared with earlier sessions: ${problemsAtPlaces(n, at)}`,
|
|
5822
|
+
choices: { new: "New in this session", also: "Also in earlier sessions", not_compared: "Not compared with earlier sessions" },
|
|
5823
|
+
panelTitle: "Problems in this session",
|
|
5824
|
+
panelNote: "Each one is too slow in this session’s own runs, whatever earlier sessions did. Problems never block.",
|
|
5825
|
+
filter: "Which problems"
|
|
5826
|
+
}
|
|
5748
5827
|
};
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5828
|
+
// packages/present/dist/src/tip.js
|
|
5829
|
+
function tip(headline, rows = [], foot) {
|
|
5830
|
+
const made = rows.flatMap((r) => r ? [Array.isArray(r) ? { label: r[0], value: r[1] } : r] : []);
|
|
5831
|
+
return foot ? { headline, rows: made, foot } : { headline, rows: made };
|
|
5832
|
+
}
|
|
5833
|
+
|
|
5834
|
+
// packages/present/dist/src/verdict-section.js
|
|
5835
|
+
var places2 = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "place" : "places"}`;
|
|
5836
|
+
var of = (n, total) => `${n.toLocaleString("en-US")} of ${places2(total)}`;
|
|
5837
|
+
var runs = (n) => `${n} valid ${n === 1 ? "run" : "runs"}`;
|
|
5838
|
+
var VERDICT_SECTION_WORDS = {
|
|
5839
|
+
slower: (n, total, slight) => `${slight ? "A little slower" : "Slower"} at ${of(n, total)}`,
|
|
5840
|
+
blocksMerge: "blocks the merge",
|
|
5841
|
+
faster: (n, total) => `Faster at ${of(n, total)}, slower at none`,
|
|
5842
|
+
mixed: (slower, faster, total) => `Slower at ${of(slower, total)}, faster at ${faster.toLocaleString("en-US")}`,
|
|
5843
|
+
unpublishedSdk: "ran an unpublished SDK build",
|
|
5844
|
+
noneMoved: (total, judged = total) => judged < total ? `No place moved out of its normal range (${judged.toLocaleString("en-US")} of ${total.toLocaleString("en-US")} judged)` : `No place moved out of its normal range (${places2(total)})`,
|
|
5845
|
+
noneMovedMeasured: (measured, same) => same ? `Nothing moved at ${of(same, same + measured)}; ${measured} measured differently` : `Nothing moved; ${measured} ${measured === 1 ? "place was" : "places were"} measured differently`,
|
|
5846
|
+
tooFewRuns: (valid, needed) => `Not compared: ${runs(valid)} of ${needed} needed`,
|
|
5847
|
+
tooFewMainRuns: (valid, needed) => `Not compared: main has ${runs(valid)} of ${needed} needed`,
|
|
5848
|
+
tooFewBefore: (valid) => `Not compared: the sessions before have only ${runs(valid)}`,
|
|
5849
|
+
noBaseline: { branch: "Nothing to compare with: no main runs on this phone yet", main: "Nothing to compare with: no earlier sessions on this phone" },
|
|
5850
|
+
noisy: "Not judged: main’s own runs vary too much at every place here",
|
|
5851
|
+
versionMismatch: "Not compared: the sides were computed with different analysis versions",
|
|
5852
|
+
unconfirmed: (n) => `Can’t confirm ${places2(n)}: a run has no value there`,
|
|
5853
|
+
notCompared: (reason) => `Not compared: ${reason}`,
|
|
5854
|
+
notJudgedYet: (why) => `Not judged yet: ${why} on this phone`,
|
|
5855
|
+
collecting: (collected, baseline, after) => after ? `Main is collecting a new baseline after ${after}: ${collected} of ${baseline} sessions` : `Main is collecting its baseline: ${collected} of ${baseline} sessions`,
|
|
5856
|
+
doesNotBlock: "doesn’t block",
|
|
5857
|
+
fixes: {
|
|
5858
|
+
branch: {
|
|
5859
|
+
"no-baseline": "Add this journey on this phone to main’s nightly runs.",
|
|
5860
|
+
"too-few-runs": "Run this branch again on this phone.",
|
|
5861
|
+
"judged-measures-missing": "Record the judged measures, then run again.",
|
|
5862
|
+
"runs-unknown": "Run this branch again; a full set of runs decides it.",
|
|
5863
|
+
"facts-version-mismatch": "Recompute both sides in Setup; no new runs are needed.",
|
|
5864
|
+
"platforms-differ": "Add this phone’s platform to main’s nightly runs."
|
|
5865
|
+
},
|
|
5866
|
+
main: {
|
|
5867
|
+
"no-baseline": "Nothing to do: main’s next 3 nightly sessions make a result possible.",
|
|
5868
|
+
"too-few-runs": "Main’s next nightly session decides it.",
|
|
5869
|
+
"runs-unknown": "Main’s next nightly session decides it.",
|
|
5870
|
+
"judged-measures-missing": "Record the judged measures on main; its next nightly session is compared.",
|
|
5871
|
+
"facts-version-mismatch": "Recompute both sides in Setup; no new runs are needed."
|
|
5872
|
+
}
|
|
5873
|
+
},
|
|
5874
|
+
waitForMain: { branch: "Nothing to do on this branch; main’s next nightly session decides it.", main: "Main’s next nightly session decides it." },
|
|
5875
|
+
sides: {
|
|
5876
|
+
branch: { left: "Main", right: "This branch", rightShort: "Branch" },
|
|
5877
|
+
main: { left: "Sessions before", right: "Newest session", rightShort: "Newest" },
|
|
5878
|
+
runSet: { left: "Main", right: "This run set", rightShort: "Run set" }
|
|
5879
|
+
},
|
|
5880
|
+
branchSides: (name) => ({ left: "Main", right: name || "This branch", rightShort: "Branch" }),
|
|
5881
|
+
inProgress: (arrived, expected) => `${arrived} of ${expected} runs arrived; the result comes with the last one`,
|
|
5882
|
+
whatChangedLabel: "What changed",
|
|
5883
|
+
moreSlower: (n) => `+${n.toLocaleString("en-US")} more slower`,
|
|
5884
|
+
slowerPanel: (n) => `${places2(n)} slower`,
|
|
5885
|
+
forInformation: "for information",
|
|
5886
|
+
allFilter: "All",
|
|
5887
|
+
filter: {
|
|
5888
|
+
label: "Show places",
|
|
5889
|
+
choice: (tierWord) => upperFirst(tierWord),
|
|
5890
|
+
showing: (choice, n) => `Showing ${choice.toLowerCase()}: ${n}`
|
|
5891
|
+
},
|
|
5892
|
+
countsLabel: "Places by result",
|
|
5893
|
+
links: {
|
|
5894
|
+
openChart: "Open chart",
|
|
5895
|
+
seeAll: (n) => `See all ${places2(n)}`,
|
|
5896
|
+
whichPlaces: "Which places",
|
|
5897
|
+
whichChanges: "Which changes",
|
|
5898
|
+
howToFix: "How to fix",
|
|
5899
|
+
whatDiffers: "What differs",
|
|
5900
|
+
whatChanged: "What changed",
|
|
5901
|
+
openSetup: "Open setup",
|
|
5902
|
+
openScreen: "Open screen"
|
|
5903
|
+
}
|
|
5904
|
+
};
|
|
5905
|
+
|
|
5754
5906
|
// packages/present/dist/src/main-history.js
|
|
5755
5907
|
var MAIN_TIER_WORDS = {
|
|
5756
5908
|
same: "In norm",
|
|
@@ -5832,75 +5984,41 @@ function mainCutWords(v) {
|
|
|
5832
5984
|
return parts.length ? parts.join(" ") : "Measures alike; no baseline cut.";
|
|
5833
5985
|
}
|
|
5834
5986
|
var BY_RANK = Object.keys(MAIN_TIER_RANK).sort((a, b) => MAIN_TIER_RANK[a] - MAIN_TIER_RANK[b]);
|
|
5835
|
-
|
|
5836
|
-
var
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
"runs-unknown": "Run this branch again; a full set of runs decides it.",
|
|
5864
|
-
"facts-version-mismatch": "Recompute both sides in Setup; no new runs are needed.",
|
|
5865
|
-
"platforms-differ": "Add this phone’s platform to main’s nightly runs."
|
|
5866
|
-
},
|
|
5867
|
-
main: {
|
|
5868
|
-
"no-baseline": "Nothing to do: main’s next 3 nightly sessions make a result possible.",
|
|
5869
|
-
"too-few-runs": "Main’s next nightly session decides it.",
|
|
5870
|
-
"runs-unknown": "Main’s next nightly session decides it.",
|
|
5871
|
-
"judged-measures-missing": "Record the judged measures on main; its next nightly session is compared.",
|
|
5872
|
-
"facts-version-mismatch": "Recompute both sides in Setup; no new runs are needed."
|
|
5873
|
-
}
|
|
5874
|
-
},
|
|
5875
|
-
waitForMain: { branch: "Nothing to do on this branch; main’s next nightly session decides it.", main: "Main’s next nightly session decides it." },
|
|
5876
|
-
sides: {
|
|
5877
|
-
branch: { left: "Main", right: "This branch", rightShort: "Branch" },
|
|
5878
|
-
main: { left: "Sessions before", right: "Newest session", rightShort: "Newest" },
|
|
5879
|
-
runSet: { left: "Main", right: "This run set", rightShort: "Run set" }
|
|
5880
|
-
},
|
|
5881
|
-
branchSides: (name) => ({ left: "Main", right: name || "This branch", rightShort: "Branch" }),
|
|
5882
|
-
inProgress: (arrived, expected) => `${arrived} of ${expected} runs arrived; the result comes with the last one`,
|
|
5883
|
-
whatChangedLabel: "What changed",
|
|
5884
|
-
forInformation: "for information",
|
|
5885
|
-
allFilter: "All",
|
|
5886
|
-
filter: {
|
|
5887
|
-
label: "Show places",
|
|
5888
|
-
choice: (tierWord) => upperFirst(tierWord),
|
|
5889
|
-
showing: (choice, n) => `Showing ${choice.toLowerCase()}: ${n}`
|
|
5890
|
-
},
|
|
5891
|
-
countsLabel: "Places by result",
|
|
5892
|
-
links: {
|
|
5893
|
-
openChart: "Open chart",
|
|
5894
|
-
seeAll: (n) => `See all ${places2(n)}`,
|
|
5895
|
-
whichPlaces: "Which places",
|
|
5896
|
-
whichChanges: "Which changes",
|
|
5897
|
-
howToFix: "How to fix",
|
|
5898
|
-
whatDiffers: "What differs",
|
|
5899
|
-
whatChanged: "What changed",
|
|
5900
|
-
openSetup: "Open setup",
|
|
5901
|
-
openScreen: "Open screen"
|
|
5902
|
-
}
|
|
5987
|
+
var MEMORY_HELD_WORDS = { head: "Memory", rest: "still held after the journey", text: "Memory still held after the journey" };
|
|
5988
|
+
var MAIN_CHART_TITLES = {
|
|
5989
|
+
renders: "Places vs their normal range",
|
|
5990
|
+
rendersTip: (baseline) => tip("Places vs their normal range", [
|
|
5991
|
+
["Bar", "The journey’s places in one session"],
|
|
5992
|
+
["Colour", `Result against main’s ${baseline} sessions before it`],
|
|
5993
|
+
["Dashed", `Not judged yet: main is collecting ${baseline} sessions on this setup`]
|
|
5994
|
+
], "Main collects them after its start or a change"),
|
|
5995
|
+
memory: `${MEMORY_HELD_WORDS.text} · MB`,
|
|
5996
|
+
memoryTip: tip(MEASURES["mem.retained_growth_mb"].label, [
|
|
5997
|
+
["Shows", "What it still holds after the clean-up at its end"],
|
|
5998
|
+
["Line", "The median of a session"],
|
|
5999
|
+
["Band", "The spread inside the session"]
|
|
6000
|
+
])
|
|
6001
|
+
};
|
|
6002
|
+
|
|
6003
|
+
// packages/present/dist/src/tips.js
|
|
6004
|
+
var LEAD_TIP = tip("A lead, not a confirmed cause", [], "Read from what the app did");
|
|
6005
|
+
// packages/present/dist/src/rules-lines.js
|
|
6006
|
+
var RULES_WORDS = {
|
|
6007
|
+
action: "Recompute",
|
|
6008
|
+
older: "Computed with older rules",
|
|
6009
|
+
marker: "new rules",
|
|
6010
|
+
blocksUnderPrevious: "blocks under the previous rules",
|
|
6011
|
+
bannerText: "New rules: pull requests wait for a recompute",
|
|
6012
|
+
bannerAction: "Recompute",
|
|
6013
|
+
engine: "engine",
|
|
6014
|
+
engineUpdate: "engine update"
|
|
5903
6015
|
};
|
|
6016
|
+
var sameRulesRef = (a, b) => a.configId === b.configId && a.coreVersion === b.coreVersion && a.derivation === b.derivation && (a.configFingerprint === undefined || b.configFingerprint === undefined || a.configFingerprint === b.configFingerprint);
|
|
6017
|
+
var rulesChangeText = (computed, live) => sameRulesRef(computed, live) ? null : RULES_WORDS.older;
|
|
6018
|
+
var staleRulesText = (rules) => rules ? rulesChangeText(rules.computed, rules.live) : null;
|
|
6019
|
+
var blocksUnderPreviousRulesText = (r) => r.blocking && r.stale ? RULES_WORDS.blocksUnderPrevious : null;
|
|
6020
|
+
var waitingForRecomputeBanner = (r) => r.waiting > 0 ? `${RULES_WORDS.bannerText} · ${RULES_WORDS.bannerAction}` : null;
|
|
6021
|
+
|
|
5904
6022
|
// packages/present/dist/src/summary-message.js
|
|
5905
6023
|
var SUMMARY_WORDS = {
|
|
5906
6024
|
notComputed: "Not judged yet",
|
|
@@ -5916,6 +6034,9 @@ var SUMMARY_WORDS = {
|
|
|
5916
6034
|
measuredDifferently: "measured differently",
|
|
5917
6035
|
namesChanged: (sdk, mainSessions, needed) => `Main: ${mainSessions} of ${needed} sessions on SDK ${sdkTag(sdk)}`,
|
|
5918
6036
|
morePlaces: (n) => `+${n} more ${n === 1 ? "place" : "places"} changed`,
|
|
6037
|
+
slowerPlaces: (n, split) => `${n} ${n === 1 ? "place" : "places"} slower${split && slowerCountsText(split) ? `: ${slowerCountsText(split, ", ")}` : ""}`,
|
|
6038
|
+
noPlaceSlower: "no place slower",
|
|
6039
|
+
toCheck: (n) => `${n} ${n === 1 ? "component" : "components"} to check`,
|
|
5919
6040
|
compared: {
|
|
5920
6041
|
branch: (sessions, days) => `vs main’s last ${sessions}${days}`,
|
|
5921
6042
|
main: (sessions, days) => `newest session vs main’s ${sessions} before${days}`,
|
|
@@ -5928,11 +6049,13 @@ var SUMMARY_WORDS = {
|
|
|
5928
6049
|
link: {
|
|
5929
6050
|
comparison: "See the comparison",
|
|
5930
6051
|
session: "See the session",
|
|
6052
|
+
mainOverTime: "See main over time",
|
|
5931
6053
|
branch: (branch) => `Open ${branch}`,
|
|
5932
6054
|
runSet: "Open the run set"
|
|
5933
6055
|
}
|
|
5934
6056
|
};
|
|
5935
|
-
var
|
|
6057
|
+
var feltOf = (l) => l.state === "worse" || l.state === "warning" ? feltRangeText(l.feltCostMs, l.slowerByMs, "slower") : l.state === "better" ? feltRangeText(l.feltGainMs, l.fasterByMs, "faster") : null;
|
|
6058
|
+
var ORDER = [...STATE_ORDER.slice(0, 3), "not-computed", ...STATE_ORDER.slice(3)];
|
|
5936
6059
|
var rank = (l) => ORDER.indexOf(l.state);
|
|
5937
6060
|
var WORD = { ...STATE_WORDS, "not-computed": SUMMARY_WORDS.notComputed };
|
|
5938
6061
|
var shownLane = (lanes) => [...lanes].sort((a, b) => rank(a) - rank(b) || b.places - a.places)[0];
|
|
@@ -5957,6 +6080,17 @@ var changeLine = (c, tier) => {
|
|
|
5957
6080
|
const values = c.main !== null && c.now !== null ? `${fromTo(c.main, c.now)}${c.change ? ` (${c.change})` : ""}` : c.change;
|
|
5958
6081
|
return [c.place ?? upper(c.measure), ...c.place ? [c.measure] : [], values, WORD[c.tier ?? tier]].filter(Boolean).join(" · ");
|
|
5959
6082
|
};
|
|
6083
|
+
function tierSplit(l) {
|
|
6084
|
+
const worse = new Set, all = new Set;
|
|
6085
|
+
for (const c of l.changes) {
|
|
6086
|
+
const key = c.key ?? c.place ?? c.measure;
|
|
6087
|
+
all.add(key);
|
|
6088
|
+
if ((c.tier ?? l.state) === "worse")
|
|
6089
|
+
worse.add(key);
|
|
6090
|
+
}
|
|
6091
|
+
const split = { worse: worse.size, slight: all.size - worse.size };
|
|
6092
|
+
return l.state === "worse" && !split.slight || l.state === "warning" && !split.worse ? undefined : split;
|
|
6093
|
+
}
|
|
5960
6094
|
var wordReason = (word) => word?.includes(": ") ? word.slice(word.indexOf(": ") + 2) : null;
|
|
5961
6095
|
var notJudgedWhy = (l) => l.state === "not-computed" && l.word?.startsWith(`${NOT_JUDGED_WORD}: `) ? upper(l.word.slice(NOT_JUDGED_WORD.length + 2)) : null;
|
|
5962
6096
|
function why(l) {
|
|
@@ -5984,18 +6118,21 @@ var noneMoved = VERDICT_SECTION_WORDS.noneMoved(1).replace(/ \(.*\)$/, "");
|
|
|
5984
6118
|
function conditions(l, pr) {
|
|
5985
6119
|
const moved = l.state === "worse" || l.state === "warning";
|
|
5986
6120
|
return [
|
|
5987
|
-
pr && l.state === "worse" ? l.blocking ? VERDICT_SECTION_WORDS.blocksMerge : NOT_BLOCKING : "",
|
|
6121
|
+
pr && l.state === "worse" ? l.blocking ? blocksUnderPreviousRulesText({ blocking: true, stale: !!l.rules }) ?? VERDICT_SECTION_WORDS.blocksMerge : NOT_BLOCKING : "",
|
|
5988
6122
|
moved && l.measuredDifferently ? SUMMARY_WORDS.measuredDifferently : "",
|
|
5989
6123
|
(moved || l.state === "better") && l.fewRuns ? SUMMARY_WORDS.fewRuns : ""
|
|
5990
6124
|
].filter(Boolean);
|
|
5991
6125
|
}
|
|
6126
|
+
var rulesLines = (l) => [staleRulesText(l.rules), waitingForRecomputeBanner({ waiting: l.waiting ?? 0 })].filter((x) => x !== null);
|
|
6127
|
+
function comparedText(main, kind) {
|
|
6128
|
+
const args = [SUMMARY_WORDS.sessions(main.sessions), SUMMARY_WORDS.days(days(main.labels))];
|
|
6129
|
+
return `${SUMMARY_WORDS.compared[kind](...args)} · ${SUMMARY_WORDS.runs(main.runs, main.each)}`;
|
|
6130
|
+
}
|
|
5992
6131
|
function compared(l, pr, slower) {
|
|
5993
6132
|
const judged = l.state === "worse" || l.state === "warning" || l.state === "better" || l.state === "no-change";
|
|
5994
6133
|
if (!judged || !l.main?.sessions)
|
|
5995
6134
|
return null;
|
|
5996
|
-
|
|
5997
|
-
const what = pr ? SUMMARY_WORDS.compared.branch(...args) : slower ? SUMMARY_WORDS.compared.mainSlower(...args) : SUMMARY_WORDS.compared.main(...args);
|
|
5998
|
-
return `${what} · ${SUMMARY_WORDS.runs(l.main.runs, l.main.each)}`;
|
|
6135
|
+
return comparedText(l.main, pr ? "branch" : slower ? "mainSlower" : "main");
|
|
5999
6136
|
}
|
|
6000
6137
|
function summaryMessage(input) {
|
|
6001
6138
|
const l = shownLane(input.lanes);
|
|
@@ -6019,11 +6156,42 @@ function summaryMessage(input) {
|
|
|
6019
6156
|
input.pr && input.prNumber ? `${input.branch} (#${input.prNumber})` : input.branch,
|
|
6020
6157
|
phones === 1 ? l.phone ?? "unknown phone" : SUMMARY_WORDS.phones(phones)
|
|
6021
6158
|
].join(" · ");
|
|
6159
|
+
const label = input.pr ? SUMMARY_WORDS.link.comparison : SUMMARY_WORDS.link.session;
|
|
6160
|
+
const link = { label, url: (single ? l.url : null) ?? input.url };
|
|
6161
|
+
if (!input.slack)
|
|
6162
|
+
return { state: l.state, newestSlower, lead, ...withLines(changeLines(l, input.pr, newestSlower)), link };
|
|
6022
6163
|
const lines = [];
|
|
6023
|
-
|
|
6164
|
+
if (l.state === "worse" || l.state === "warning") {
|
|
6165
|
+
const n = l.places || l.changes.length;
|
|
6166
|
+
lines.push(upper([...n ? [SUMMARY_WORDS.slowerPlaces(n, tierSplit(l))] : [], feltOf(l)].filter(Boolean).join(" · ") || why(l)));
|
|
6167
|
+
} else if (l.state === "no-change")
|
|
6168
|
+
lines.push(upper(SUMMARY_WORDS.noPlaceSlower));
|
|
6169
|
+
else if (l.state === "cant-tell") {
|
|
6170
|
+
const fix = unblock(l, input.pr);
|
|
6171
|
+
lines.push(fix ? SUMMARY_WORDS.unblock(why(l), fix) : why(l));
|
|
6172
|
+
} else if (l.state === "better")
|
|
6173
|
+
lines.push([why(l), feltOf(l)].filter(Boolean).join(" · "));
|
|
6174
|
+
else
|
|
6175
|
+
lines.push(why(l));
|
|
6176
|
+
if (l.toCheckCount)
|
|
6177
|
+
lines.push(upper(SUMMARY_WORDS.toCheck(l.toCheckCount)));
|
|
6178
|
+
const basis = compared(l, input.pr, false);
|
|
6024
6179
|
const extra = conditions(l, input.pr).join(", ");
|
|
6180
|
+
const heat = l.heatedRuns ? SUMMARY_WORDS.heated(l.heatedRuns, l.runs) : null;
|
|
6181
|
+
const context = [basis ? `${basis}${extra ? `; ${extra}` : ""}` : extra, heat].filter(Boolean).join(" · ");
|
|
6182
|
+
if (context)
|
|
6183
|
+
lines.push(basis ? context : upper(context));
|
|
6184
|
+
lines.push(...rulesLines(l));
|
|
6185
|
+
return { state: l.state, newestSlower, lead, ...withLines(lines), link };
|
|
6186
|
+
}
|
|
6187
|
+
function changeLines(l, pr, newestSlower) {
|
|
6188
|
+
const lines = [];
|
|
6189
|
+
const basis = compared(l, pr, newestSlower);
|
|
6190
|
+
const extra = conditions(l, pr).join(", ");
|
|
6025
6191
|
const heat = l.heatedRuns ? upper(SUMMARY_WORDS.heated(l.heatedRuns, l.runs)) : null;
|
|
6026
|
-
const
|
|
6192
|
+
const felt = feltOf(l);
|
|
6193
|
+
const sums = upper([felt, l.toCheckCount ? SUMMARY_WORDS.toCheck(l.toCheckCount) : null].filter(Boolean).join(" · "));
|
|
6194
|
+
const tail = [...sums ? [sums] : [], ...basis || extra ? [basis ? `${basis}${extra ? `; ${extra}` : ""}` : upper(extra)] : [], ...heat ? [heat] : [], ...rulesLines(l)];
|
|
6027
6195
|
if (l.state === "worse" || l.state === "warning" || l.state === "better") {
|
|
6028
6196
|
const seen = new Set;
|
|
6029
6197
|
const places = l.changes.filter((c) => {
|
|
@@ -6039,12 +6207,11 @@ function summaryMessage(input) {
|
|
|
6039
6207
|
if (top.length && more > 0)
|
|
6040
6208
|
lines.push(SUMMARY_WORDS.morePlaces(more));
|
|
6041
6209
|
} else {
|
|
6042
|
-
const fix = l.state === "cant-tell" ? unblock(l,
|
|
6210
|
+
const fix = l.state === "cant-tell" ? unblock(l, pr) : null;
|
|
6043
6211
|
lines.push(fix ? SUMMARY_WORDS.unblock(why(l), fix) : why(l));
|
|
6044
6212
|
}
|
|
6045
6213
|
lines.push(...tail);
|
|
6046
|
-
|
|
6047
|
-
return { state: l.state, newestSlower, lead, ...withLines(lines), link: { label, url: (single ? l.url : null) ?? input.url } };
|
|
6214
|
+
return lines;
|
|
6048
6215
|
}
|
|
6049
6216
|
var withLines = (lines) => ({ lines, detail: lines.join(`
|
|
6050
6217
|
`) });
|
|
@@ -6054,6 +6221,7 @@ function pendingMessage(branch, url) {
|
|
|
6054
6221
|
function noResultMessage(branch, reason, url) {
|
|
6055
6222
|
return { state: "not-computed", newestSlower: false, lead: `${SUMMARY_WORDS.noResult} · ${clip(branch, NAME.name)}`, ...withLines([reason.replace(/\.$/, "")]), link: url ? { label: SUMMARY_WORDS.link.runSet, url } : null };
|
|
6056
6223
|
}
|
|
6224
|
+
|
|
6057
6225
|
// packages/present/dist/src/run-set-message.js
|
|
6058
6226
|
var midLine = (label, metric) => !metric.startsWith("component:") && /^[A-Z][a-z]*(\s|$)/.test(label) ? `${label[0].toLowerCase()}${label.slice(1)}` : label;
|
|
6059
6227
|
function movedItems(v, sc) {
|
|
@@ -6142,13 +6310,20 @@ function laneOf(v, sc, set) {
|
|
|
6142
6310
|
namesChanged: sc.context?.namesChanged ?? null,
|
|
6143
6311
|
url: linkOf(sc, set),
|
|
6144
6312
|
main: sc.main ?? null,
|
|
6145
|
-
reason: sc.headline?.reason ?? sc.headline?.reasons?.[0] ?? null
|
|
6313
|
+
reason: sc.headline?.reason ?? sc.headline?.reasons?.[0] ?? null,
|
|
6314
|
+
feltCostMs: sc.headline?.felt_cost_ms ?? null,
|
|
6315
|
+
feltGainMs: sc.headline?.felt_gain_ms ?? null,
|
|
6316
|
+
slowerByMs: sc.headline?.slower_by_ms ?? null,
|
|
6317
|
+
fasterByMs: sc.headline?.faster_by_ms ?? null,
|
|
6318
|
+
toCheckCount: sc.headline?.to_check_count ?? null,
|
|
6319
|
+
rules: sc.headline?.rules ?? null,
|
|
6320
|
+
waiting: sc.headline?.waiting_for_reprocess ?? 0
|
|
6146
6321
|
};
|
|
6147
6322
|
}
|
|
6148
6323
|
var isPending = (s) => lanesOf(s).every((l) => l.state === "not-computed" && !l.word?.startsWith(`${NOT_JUDGED_WORD}: `));
|
|
6149
6324
|
var lanesOf = (s) => s.verdicts ? s.verdicts.scenarios.map((sc) => laneOf(s.verdicts, sc, s)) : [];
|
|
6150
6325
|
var isPr = (s) => !!s.verdicts?.scenarios.some((sc) => sc.role === "comparison");
|
|
6151
|
-
var runSetMessage = (s) => summaryMessage({ branch: s.branch, pr: isPr(s), lanes: lanesOf(s), url: linkOf(s) ?? s.dashboard_url, app: s.app ?? null, prNumber: s.pull_request?.number ?? null });
|
|
6326
|
+
var runSetMessage = (s, o = {}) => summaryMessage({ branch: s.branch, pr: isPr(s), lanes: lanesOf(s), url: linkOf(s) ?? s.dashboard_url, app: s.app ?? null, prNumber: s.pull_request?.number ?? null, slack: o.slack });
|
|
6152
6327
|
var MESSAGE_COLORS = { error: "#D6334A", warn: "#C47A00", better: "#238359", none: "#7C8492" };
|
|
6153
6328
|
function messageTone(s) {
|
|
6154
6329
|
const lanes = s ? lanesOf(s) : [];
|
|
@@ -6181,16 +6356,10 @@ ${esc(m.detail)}` } },
|
|
|
6181
6356
|
}]
|
|
6182
6357
|
};
|
|
6183
6358
|
}
|
|
6184
|
-
function resultSlackPayload(s
|
|
6185
|
-
if (!s.verdicts)
|
|
6359
|
+
function resultSlackPayload(s) {
|
|
6360
|
+
if (!s.verdicts || !isPr(s))
|
|
6186
6361
|
return null;
|
|
6187
|
-
|
|
6188
|
-
const moved = lanes.some((l) => l.state === "worse" || l.state === "warning");
|
|
6189
|
-
const heat = lanes.some((l) => l.heatedRuns > 0);
|
|
6190
|
-
const improved = lanes.some((l) => l.state === "better") || s.verdicts.state === "better";
|
|
6191
|
-
if (isPr(s) ? !moved && !heat : !moved && !improved && !heat && !o.always)
|
|
6192
|
-
return null;
|
|
6193
|
-
return slackPayload(runSetMessage(s), messageTone(s));
|
|
6362
|
+
return slackPayload(runSetMessage(s, { slack: true }), messageTone(s));
|
|
6194
6363
|
}
|
|
6195
6364
|
var noResultSlackPayload = (branch, reason, url) => slackPayload(noResultMessage(branch, reason, url), { mark: "⚪", color: MESSAGE_COLORS.none });
|
|
6196
6365
|
var markdown = (m, mark) => `### ${mark} ${m.lead}
|
|
@@ -6203,6 +6372,7 @@ ${m.link ? `
|
|
|
6203
6372
|
var resultMarkdown = (s) => markdown(runSetMessage(s), messageTone(s).mark);
|
|
6204
6373
|
var noResultMarkdown = (branch, reason, url) => markdown(noResultMessage(branch, reason, url), "⚪");
|
|
6205
6374
|
var pendingMarkdown = (branch, url) => markdown(pendingMessage(branch, url), "⚪");
|
|
6375
|
+
|
|
6206
6376
|
// packages/present/dist/src/stale.js
|
|
6207
6377
|
function staleNoteText(why) {
|
|
6208
6378
|
const newer = why.includes("newer-run-set");
|
|
@@ -6220,6 +6390,15 @@ var staleFlagText = (why) => {
|
|
|
6220
6390
|
return `${note.charAt(0).toLowerCase()}${note.slice(1)}`;
|
|
6221
6391
|
};
|
|
6222
6392
|
var STALE_MARK = "stale result";
|
|
6393
|
+
|
|
6394
|
+
// packages/present/dist/src/main-session-message.js
|
|
6395
|
+
var TONE = {
|
|
6396
|
+
worse: { mark: "\uD83D\uDD34", color: MESSAGE_COLORS.error },
|
|
6397
|
+
slight: { mark: "\uD83D\uDFE1", color: MESSAGE_COLORS.warn },
|
|
6398
|
+
better: { mark: "\uD83D\uDFE2", color: MESSAGE_COLORS.better },
|
|
6399
|
+
cant: { mark: "❔", color: MESSAGE_COLORS.none },
|
|
6400
|
+
same: { mark: "⚪", color: MESSAGE_COLORS.none }
|
|
6401
|
+
};
|
|
6223
6402
|
// apps/agent/src/upload.ts
|
|
6224
6403
|
import { join as join4 } from "node:path";
|
|
6225
6404
|
var MAX_RATE_RETRIES = 5;
|
|
@@ -6379,7 +6558,8 @@ var addCounts = (xs) => xs.reduce((a, c) => ({ worse: a.worse + c.worse, warning
|
|
|
6379
6558
|
var setupLine = (h) => {
|
|
6380
6559
|
const parts = [
|
|
6381
6560
|
h?.measured_differently?.length ? `Measured differently (${h.measured_differently.join(", ")}) — ${NOT_BLOCKING}` : "",
|
|
6382
|
-
h?.waiting_for_reprocess ? waitingWords(h.waiting_for_reprocess) : ""
|
|
6561
|
+
h?.waiting_for_reprocess ? waitingWords(h.waiting_for_reprocess) : "",
|
|
6562
|
+
staleRulesText(h?.rules) ?? ""
|
|
6383
6563
|
].filter(Boolean);
|
|
6384
6564
|
return parts.length ? parts.join("; ") : null;
|
|
6385
6565
|
};
|
|
@@ -10589,7 +10769,7 @@ async function aa(deps, outDir, opts = {}) {
|
|
|
10589
10769
|
}
|
|
10590
10770
|
|
|
10591
10771
|
// apps/agent/src/farm.ts
|
|
10592
|
-
import { dirname as
|
|
10772
|
+
import { dirname as dirname4, join as join13, resolve as resolve6 } from "node:path";
|
|
10593
10773
|
|
|
10594
10774
|
// packages/appium-driver/dist/src/links.js
|
|
10595
10775
|
var ALERT_POLL_MS = 250;
|
|
@@ -10893,7 +11073,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
10893
11073
|
import { dirname as dirname2, join as join11, relative as relative2, resolve as resolve4 } from "node:path";
|
|
10894
11074
|
import { fileURLToPath } from "node:url";
|
|
10895
11075
|
function driverVersion() {
|
|
10896
|
-
const built = "0.1.0-alpha.
|
|
11076
|
+
const built = "0.1.0-alpha.21";
|
|
10897
11077
|
if (built)
|
|
10898
11078
|
return built;
|
|
10899
11079
|
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -10948,9 +11128,15 @@ var SDK_FLUSH_WORST_CASE_MS = 315000;
|
|
|
10948
11128
|
var FLUSH_MARGIN_MS = 30000;
|
|
10949
11129
|
var FLUSH_WAIT_MS = SDK_FLUSH_WORST_CASE_MS + FLUSH_MARGIN_MS;
|
|
10950
11130
|
var FLUSH_STATUSES = new Set(["ok", "error", "off"]);
|
|
11131
|
+
// packages/verdict-core/dist/src/version.generated.js
|
|
11132
|
+
var CORE_FINGERPRINT = "1aef48c7adfd5630";
|
|
11133
|
+
|
|
11134
|
+
// packages/verdict-core/dist/src/version.js
|
|
11135
|
+
var CORE_VERSION = parseInt(CORE_FINGERPRINT.slice(0, 7), 16);
|
|
10951
11136
|
// packages/verdict-core/dist/src/window-count.js
|
|
10952
11137
|
var WINDOW_DEPENDENT_JOURNEY_MEASURES = ["sampler.cpu_ms", "sampler.frames.longest_gap_ms"];
|
|
10953
11138
|
var OFF = new WeakSet;
|
|
11139
|
+
|
|
10954
11140
|
// packages/verdict-core/dist/src/clauses.js
|
|
10955
11141
|
var row = (id, what, unit, cmp, ruling, uncalibrated, value) => ({ id, what, unit, cmp, ruling, uncalibrated, value });
|
|
10956
11142
|
var NO_RULING = "no ruling: a starting value of the deciding config (L2 audit F-13)";
|
|
@@ -11004,6 +11190,12 @@ var CLAUSES = {
|
|
|
11004
11190
|
"U-NOISY": row("U-NOISY", "A place is unstable on main above this ratio of spread to allowed, and stays so down to `below`; from this many main runs in this many sessions.", "ratio", ">", "REQ-MET-29, Arek 2026-10-18 ~12:00-13:20; thresholds provisional", false, { above: 1, below: 0.75, minMainRuns: 6, minSessions: 3 })
|
|
11005
11191
|
};
|
|
11006
11192
|
var CLAUSE_LIST = Object.values(CLAUSES);
|
|
11193
|
+
|
|
11194
|
+
// packages/verdict-core/dist/src/comp-confirm.js
|
|
11195
|
+
var COMPONENT_K99 = (() => {
|
|
11196
|
+
const f = CLAUSES["T-WARN-NOISE"].value.component;
|
|
11197
|
+
return { a: 0, b: f.c, power: 0.5, lift: f.k99 };
|
|
11198
|
+
})();
|
|
11007
11199
|
// packages/verdict-core/dist/src/config.js
|
|
11008
11200
|
var WINDOWS = ["screen", "press", "drag", "step", "other"];
|
|
11009
11201
|
var STEP = ["step"];
|
|
@@ -11225,7 +11417,7 @@ var DRIVER_RELEASES = [
|
|
|
11225
11417
|
{ affects: LOADED_MEMORY }
|
|
11226
11418
|
],
|
|
11227
11419
|
uncalibrated: false,
|
|
11228
|
-
source: "Drag end points move above the platform bottom system-gesture zone (fix/driver-gesture-zone); start points unchanged; on iOS a shortened drag keeps its duration (slower); on Android it keeps its speed (ends sooner). A drag's end, and the duration of a step whose scroll steps contain such drags, can change. W1 (#306): farm sessions record no video or device logs by default (`--capture off`), so the phone carries less load and every memory measure (mem.*, memory_left_mb, sampler.memory.*, ri.memory.*) is measured differently from capture-on alpha.16 sessions (Elastyczność 2026-10-26 15:45 B1-DIFF; controller Stab 4). The declaration only separates alpha.16 from alpha.17: two alpha.17 sessions with capture on and off
|
|
11420
|
+
source: "Drag end points move above the platform bottom system-gesture zone (fix/driver-gesture-zone); start points unchanged; on iOS a shortened drag keeps its duration (slower); on Android it keeps its speed (ends sooner). A drag's end, and the duration of a step whose scroll steps contain such drags, can change. W1 (#306): farm sessions record no video or device logs by default (`--capture off`), so the phone carries less load and every memory measure (mem.*, memory_left_mb, sampler.memory.*, ri.memory.*) is measured differently from capture-on alpha.16 sessions (Elastyczność 2026-10-26 15:45 B1-DIFF; controller Stab 4). The declaration only separates alpha.16 from alpha.17: two alpha.17 sessions with capture on and off are told apart by the `capture` measured-with aspect (run setup.capture, #344, #426). Screens and taps stay compared (REQ-FARM-35)."
|
|
11229
11421
|
},
|
|
11230
11422
|
{
|
|
11231
11423
|
version: "0.1.0-alpha.18",
|
|
@@ -11243,8 +11435,32 @@ var DRIVER_RELEASES = [
|
|
|
11243
11435
|
],
|
|
11244
11436
|
uncalibrated: false,
|
|
11245
11437
|
source: "#393 (fix/keepsnappy-clamped-row-drag-travel, appium-driver CHANGELOG 2026-10-27): on iOS a row-to-top drag whose end safeBottomY clamps (chat history on every iPhone with a home indicator) starts 33 pt higher, so it travels 33 pt further in the same 400 ms (a faster release, a longer coast), with one list lookup and two frame reads right before it. Every measure of that drag window can move, React work included (more rows scroll by), and so can the step whose scroll steps contain it. Android and unclamped drags send the same requests as alpha.18 (pinned): declares nothing there (REQ-FARM-35)."
|
|
11438
|
+
},
|
|
11439
|
+
{
|
|
11440
|
+
version: "0.1.0-alpha.20",
|
|
11441
|
+
previous: "0.1.0-alpha.19",
|
|
11442
|
+
declares: [
|
|
11443
|
+
{ affects: MEASURES_AT("drag"), places: ["drag"] },
|
|
11444
|
+
{ affects: MEASURES_AT("step"), places: ["step"] },
|
|
11445
|
+
{ affects: MEASURES_AT("journey"), places: ["journey"] }
|
|
11446
|
+
],
|
|
11447
|
+
uncalibrated: false,
|
|
11448
|
+
source: '#417 (plan 84 step 2.1, D1, appium-driver CHANGELOG "release alpha.20", 2026-10-27): one gesture definition for every list and screen scroll on both platforms, one touch held 150 ms over 60 % of the list frame (at least 250 dp or pt). Android list and screen flings 12 000 / 3000 dp/s -> that contact (a shorter coast, about 1000-1600 dp in place of about 7000, so fewer momentum frames and possibly one more gesture per page pass); iOS list drags 75 % (end) / 65 % (top) -> 60 %, iOS drawn-row (inverted list) drags 400 -> 150 ms; touches reaching a system zone move whole. Every other request, body and wait is pinned unchanged against alpha.19. The CHANGELOG states "changes Android and iOS list/scroll places; compare only within alpha.20 on both platforms", so both platforms are declared at drag, step and journey places (REQ-FARM-35): the journey holds the whole-run measures the judge reads (memory kept, memory at launch, end and peak, whole-run CPU, the longest freeze), and a shorter coast and one more gesture per page pass move them too.'
|
|
11449
|
+
},
|
|
11450
|
+
{
|
|
11451
|
+
version: "0.1.0-alpha.21",
|
|
11452
|
+
previous: "0.1.0-alpha.20",
|
|
11453
|
+
declares: [
|
|
11454
|
+
{ affects: MEASURES_AT("drag"), places: ["drag"] },
|
|
11455
|
+
{ affects: MEASURES_AT("step"), places: ["step"] },
|
|
11456
|
+
{ affects: MEASURES_AT("journey"), places: ["journey"] }
|
|
11457
|
+
],
|
|
11458
|
+
uncalibrated: false,
|
|
11459
|
+
source: `#491 (appium-driver CHANGELOG "0.1.0-alpha.21", 2026-10-29): in a paged scrollToEnd pass, when a page has not landed by the end of its 3 s wait and the before row is cut by (Android: touches) the list's bottom edge, the pass spends its one extra end gesture and waits again, where alpha.20 failed the run. Such a run sends one more end gesture, one row lookup and one frame read; every other request, body and wait is unchanged. That drag window, the step whose scroll steps contain it and the whole run (one more gesture and coast) can move, on both platforms, so drag, step and journey places are declared (REQ-FARM-35).`
|
|
11246
11460
|
}
|
|
11247
11461
|
];
|
|
11462
|
+
var JOURNEY_RELEASES = {};
|
|
11463
|
+
var CAPTURE_MEASURED_WITH = { aspect: "capture", affects: LOADED_MEMORY, marks: true };
|
|
11248
11464
|
var UNSTABLE_NOISY = {
|
|
11249
11465
|
reason: "noisy",
|
|
11250
11466
|
spread: "sessions",
|
|
@@ -11258,7 +11474,6 @@ var UNSTABLE_NOISY = {
|
|
|
11258
11474
|
uncalibrated: false,
|
|
11259
11475
|
source: 'Arek 2026-10-18 ~12:00, ~12:20 (noisy on main = unstable, main only, per comparison, hysteresis), 12:50 (within-session spread only: a regression that lands on main moves whole sessions and must stay judged), 13:20 ("zrób na razie wg rekomendacji"; threshold provisional). ' + "Ratio = z99 x the predicted no-change difference SD from the pooled within-session MAD of main (Var(median) = (pi/2) sigma^2 / n) / the smallest warning step at main's median. " + "Replay (noisy/REPORT.md, derivation 22, 1 PR vs 3 main): S23 night 2 -> 1 false worse z 60 (MAD rule 0), sim 0, NI pool 95 -> 64 warnings z 840 (MAD 92); 3 vs 3 0 everywhere; flagged per split S23 17.8, NI 4.9, sim 0.7; judged per split S23 64.5 -> 46.8; flips S23 8 (MAD 21); planted main steps flagged noisy 0 (MAD 5-29 %, with the between-session part 56-99 %). " + "Curve: kept for the judged-rule lookup; the ratio uses the warning step"
|
|
11260
11476
|
};
|
|
11261
|
-
|
|
11262
11477
|
class ConfigError2 extends Error {
|
|
11263
11478
|
problems;
|
|
11264
11479
|
constructor(problems) {
|
|
@@ -11322,7 +11537,13 @@ function withClauseValues(base) {
|
|
|
11322
11537
|
const valued = (r) => {
|
|
11323
11538
|
if (r.id === "memory_retained")
|
|
11324
11539
|
return { ...r, threshold: [part(memory.value.warn)], uncalibrated: memory.uncalibrated };
|
|
11325
|
-
|
|
11540
|
+
if (r.role === "judged")
|
|
11541
|
+
return judgedByNoise(r);
|
|
11542
|
+
if (r.id in NOISE_FITS) {
|
|
11543
|
+
const { errorNoise: _error, ...shown } = judgedByNoise(r);
|
|
11544
|
+
return shown;
|
|
11545
|
+
}
|
|
11546
|
+
return evidence(r);
|
|
11326
11547
|
};
|
|
11327
11548
|
const rules = base.rules.map(valued);
|
|
11328
11549
|
const frames = (id) => ({ above: { frames: CLAUSES[id].value.frames }, uncalibrated: CLAUSES[id].uncalibrated });
|
|
@@ -11384,9 +11605,32 @@ var MEMORY_RETAINED = {
|
|
|
11384
11605
|
uncalibrated: true,
|
|
11385
11606
|
source: MEMORY_PAIR_SOURCE
|
|
11386
11607
|
};
|
|
11608
|
+
var FELT_SOURCE = 'Arek 2026-10-27 ~08:00 ("Ok", [ctl-arek-felt-cost], amends decision 0005 / Q-5): component self time explains, never decides; every component past its noise is listed "to check", also on a green verdict';
|
|
11387
11609
|
var JANK_SOURCE = `W8 option A, Arek 2026-10-26 ("Opcja A jest git", [ctl-arek-jank-time]): jank time (the window's commit times past one 60 Hz frame, added up) is judged in place of the slowest commit, so one spike no longer decides and jitter below a frame counts 0; curve T-NOISE-JANK; deciding since simple-4.26 after the pack v3 replay (A/A Slightly worse 58 -> 46 of 840 on aa-ni, doses 54 of 54)`;
|
|
11610
|
+
var NOISY_WIDEN = {
|
|
11611
|
+
noisyFrom: 1,
|
|
11612
|
+
lineMultiplier: 1,
|
|
11613
|
+
sd: "aa-loo",
|
|
11614
|
+
excludeAbove: null,
|
|
11615
|
+
excludeReturnBelow: null,
|
|
11616
|
+
excludeAboveMs: null,
|
|
11617
|
+
uncalibrated: false,
|
|
11618
|
+
source: `Arek 2026-10-27 ~19:45 [ctl-arek-noisy-widen]: noisy places judged with a widened line, tunable down to the old exclusion (excludeAbove 1.0 / 0.75 with the within-session spread); amendment ~19:40 ("Szum z 3 sesji main powinniśmy liczyć tak samo jak A/A"): one main session against the others, between-session shift included, plus the PR's run-to-run spread; ctl 19:45: ×1, k never raised to pass; start position: never left out (ctl 22:20, the Pixel 7 no longer counts). Pack v4 replay against simple-4.27 without the Pixel 7: A/A false Worse 1 / 1 of 1070 splits, doses caught Reference night 166 / 166, Reference PR 93 / 93, farm iPhone ms 4863 -> 5012 of 6580 (Worse 3107 -> 3167), places left out 9.8 % -> 0 %. Footnote Pixel 7 (21 splits): A/A Worse 0 -> 4`
|
|
11619
|
+
};
|
|
11620
|
+
var COMP_CONFIRM = {
|
|
11621
|
+
maxComponents: 3,
|
|
11622
|
+
share: 0.6,
|
|
11623
|
+
uncalibrated: false,
|
|
11624
|
+
source: `Arek 2026-10-28 ~08:00 idea, ctl 08:10 "Zleć jako pomiar", k-sweep ctl 08:50, deciding ~11:00 "tak" [ctl-arek-comp-confirm]: a place's render sums many components, the component that caused a slowdown carries far less noise; form B (the confirmed components' summed excess past their own lines is the place's excess); k 3 and share 60 % from the k-sweep (docs/stats/2026-10-28-comp-confirm.md); gate: A/A vs simple-4.28, added false Worse 0 and added false Slightly worse at most 1 per 500 splits`
|
|
11625
|
+
};
|
|
11626
|
+
var RELIABLE = {
|
|
11627
|
+
minValidRuns: 3,
|
|
11628
|
+
warmStart: { androidRawFrom: 3, iosFrom: 2 },
|
|
11629
|
+
uncalibrated: false,
|
|
11630
|
+
source: 'Arek via ctl 2026-10-28 ~11:40, corrected ~11:55 ("fair and moderate are not an overheated phone") [ctl-arek-thermal-unreliable]: a run that started at Android SEVERE (3) or above, or iOS serious (2) or above, makes its session unreliable; the SDK maps SEVERE to serious, so both scales read alike. NONE / LIGHT / MODERATE and nominal / fair are normal: listed as started warm, never unreliable. [ctl-arek-three-runs]: a session counts only with 3 valid runs, on main and on the PR (no earlier ruling on partial PR sessions: G-MINRUNS had none). Background: giga2-scratch/noise-p9p-g71/findings.md (P9P false Worse from LIGHT starts stay; the G71 1-run main session no longer counts)'
|
|
11631
|
+
};
|
|
11388
11632
|
var SHAPE = {
|
|
11389
|
-
id: "simple-4.
|
|
11633
|
+
id: "simple-4.29+noise95-99",
|
|
11390
11634
|
minRuns: 2,
|
|
11391
11635
|
baseline: { sessions: 3, ...GUESS("Q4 / REQ-VER-33: main's newest 3 sessions against one PR session (the quieter setting of the plan 58 simulation)") },
|
|
11392
11636
|
repeats: { kind: "every-pr-beyond-every-main" },
|
|
@@ -11408,6 +11652,7 @@ var SHAPE = {
|
|
|
11408
11652
|
uncalibrated: false,
|
|
11409
11653
|
source: 'Arek 2026-10-16 ("Tak, to też ustalaliśmy"): the SDK, the driver and the journey are the instrument (REQ-VER-35); a release declares the measures it affects (REQ-VER-34); a forced GC before each step is a different instrument (REQ-VER-13, docs/plans/gc-before-action.md)',
|
|
11410
11654
|
driverReleases: DRIVER_RELEASES,
|
|
11655
|
+
...Object.keys(JOURNEY_RELEASES).length ? { journeyReleases: JOURNEY_RELEASES } : {},
|
|
11411
11656
|
unknownDiffers: ["journeyVersion"]
|
|
11412
11657
|
},
|
|
11413
11658
|
calibrated: { platforms: ["ios", "android"], uncalibrated: false, source: 'REQ-FARM-26: Android moved in by Arek ("Ok", 2026-10-17) after the S23 A/A, 5 sessions × 3 runs: 0 z 25 false worse under the deciding rule (stats-android/aa/aa-report.md)' },
|
|
@@ -11415,7 +11660,7 @@ var SHAPE = {
|
|
|
11415
11660
|
{ ...COMMIT_MAX_JUDGED, id: "jank", on: { measure: "jank_ms" }, source: JANK_SOURCE },
|
|
11416
11661
|
evidenceRule("commit_max", { measure: "commit_max_ms" }, JUDGED_WINDOWS, [], "W8 option A: the slowest commit is shown, jank time is judged", { devices: ANY_DEVICE }),
|
|
11417
11662
|
RENDER_JUDGED,
|
|
11418
|
-
COMPONENT_JUDGED,
|
|
11663
|
+
{ ...COMPONENT_JUDGED, role: "evidence", source: FELT_SOURCE },
|
|
11419
11664
|
{ ...evidenceRule("commits", { measure: "commits" }, REACT_PLACES, [{ rel: 0.5, abs: 2 }], "render and commit counts are evidence only (ruling a)"), source: COUNT_SOURCE },
|
|
11420
11665
|
{ ...evidenceRule("renders", { measure: "renders" }, REACT_PLACES, [{ rel: 0.5, abs: 10 }], "render and commit counts are evidence only (ruling a)"), source: COUNT_SOURCE },
|
|
11421
11666
|
evidenceRule("render_upper", { measure: "render_ms_upper" }, WINDOWS, [{ rel: TIER_REL, abs: TIER_UPDATE_MS.warnMs }], "REQ-MEAS-32 timers, option A (Arek 2026-10-17): judged plus timer-carried render time is evidence with the render threshold; render_ms decides"),
|
|
@@ -11483,6 +11728,11 @@ var SHAPE = {
|
|
|
11483
11728
|
uncalibrated: true,
|
|
11484
11729
|
source: "ruling d: entry and in-screen mount about 100 ms; an update one frame (16.7 ms on every screen, Arek 2026-10-16)"
|
|
11485
11730
|
},
|
|
11731
|
+
feltCost: {
|
|
11732
|
+
reads: ["jank", "render"],
|
|
11733
|
+
uncalibrated: false,
|
|
11734
|
+
source: 'Arek 2026-10-27 ~08:00 ("Ok", [ctl-arek-felt-cost]) after giga2-scratch/analysis/2026-10-27-verdict-scale.md option C; B4 replay on the jank pack v3 (676d7453a39c) against simple-4.26: A/A Worse / Slightly worse over 16 pools 2 / 68 -> 1 / 34, doses 52 of 54 (in-screen-mount 1.1x and slow-commit 0.5x lose one case each), golden 10 of 10, corpus 0 moves'
|
|
11735
|
+
},
|
|
11486
11736
|
freeze: { on: "sampler_longest_gap_ms", uncalibrated: false, source: 'evidence only: the longest freeze covers the whole window, not the transition; "no dropped frames during the transition" (ruling d) needs per-place frame data that is not available' },
|
|
11487
11737
|
notJudgedLabels: ["launch"],
|
|
11488
11738
|
unstable: [{
|
|
@@ -11491,6 +11741,9 @@ var SHAPE = {
|
|
|
11491
11741
|
reads: [COMMIT_MAX_JUDGED, RENDER_JUDGED, COMPONENT_JUDGED, MEMORY_RETAINED],
|
|
11492
11742
|
source: `${UNSTABLE_NOISY.source}; Arek 2026-10-26 ("A wygląda kusząco. Oddaj do impl.", ctl-arek-met29-own-step): the step is the place's own work kind's (entry and in-screen mount 10 ms, update 5 ms), the smallest only when the kind is unknown; ctl-arek-jank-time: noisiness still reads the slowest commit, so a place is unstable exactly when it was before jank time was judged`
|
|
11493
11743
|
}],
|
|
11744
|
+
noisy: NOISY_WIDEN,
|
|
11745
|
+
compConfirm: COMP_CONFIRM,
|
|
11746
|
+
reliable: RELIABLE,
|
|
11494
11747
|
judgeFrom: { mainSessions: 3, uncalibrated: false, source: 'Arek 2026-10-18 (REQ-VER-41): a phone is judged only when main has at least 3 sessions on that phone and journey; with fewer the result is "not enough main yet", not judged, not blocking, values shown. Replay (agents/release/replay, d22): 1 PR vs 2 main on the S23 night pool gave 13 z 60 false worse+warning, 1 vs 3 gave 0' },
|
|
11495
11748
|
measuredWith: [
|
|
11496
11749
|
{ aspect: "renderer", affects: REACT_MEASURES },
|
|
@@ -11499,7 +11752,8 @@ var SHAPE = {
|
|
|
11499
11752
|
{ aspect: "screenWindowModel", affects: WINDOW_MODEL_AFFECTS, places: ["screen"], marks: true },
|
|
11500
11753
|
{ aspect: "cpuSource", affects: ["sampler.cpu_ms"] },
|
|
11501
11754
|
{ aspect: "causeSplit", affects: REACT_MEASURES, places: REACT_PLACES, marks: true },
|
|
11502
|
-
{ aspect: "endGc", affects: MEMORY_MEASURES }
|
|
11755
|
+
{ aspect: "endGc", affects: MEMORY_MEASURES },
|
|
11756
|
+
CAPTURE_MEASURED_WITH
|
|
11503
11757
|
],
|
|
11504
11758
|
tiers: {
|
|
11505
11759
|
rel: TIER_REL,
|
|
@@ -11523,15 +11777,6 @@ var SHAPE = {
|
|
|
11523
11777
|
}
|
|
11524
11778
|
};
|
|
11525
11779
|
var CONFIG = withClauseValues(SHAPE);
|
|
11526
|
-
var P2A_SLIGHT = withClauseValues({
|
|
11527
|
-
...CONFIG,
|
|
11528
|
-
id: `${CONFIG.id}+p2a-sw`,
|
|
11529
|
-
unstable: CONFIG.unstable.map((u) => ({
|
|
11530
|
-
...u,
|
|
11531
|
-
judgeAt: { line: "error", tier: "warning" },
|
|
11532
|
-
source: `${u.source}; P2a candidate (plan 81, Arek 2026-10-23 ~15:45): judged only past the error line, as Slightly worse, never blocking`
|
|
11533
|
-
}))
|
|
11534
|
-
});
|
|
11535
11780
|
var P4_EVEN = {
|
|
11536
11781
|
...CONFIG,
|
|
11537
11782
|
id: `${CONFIG.id}+p4-even`,
|
|
@@ -11542,6 +11787,51 @@ var P3_COMMIT = withClauseValues({
|
|
|
11542
11787
|
id: `${CONFIG.id}+p3-commit`,
|
|
11543
11788
|
commitGrouping: { uncalibrated: true, source: "plan 81 P3; Arek 2026-10-26 [ctl-arek-p81-p3p4]: two windows that share one proven commit (the same slowest commit in every PR run) may be one finding; never merged without it" }
|
|
11544
11789
|
});
|
|
11790
|
+
// apps/agent/src/flow-version.ts
|
|
11791
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
11792
|
+
import { dirname as dirname3, relative as relative3, resolve as resolve5, sep as sep2 } from "node:path";
|
|
11793
|
+
var REFERENCE = /^\s*(?:-\s*)?(?:runFlow|runScript|file)\s*:\s*['"]?([^'"\s#{}$]+\.(?:ya?ml|js))['"]?\s*$/gm;
|
|
11794
|
+
var commonDir = (paths) => {
|
|
11795
|
+
let dir = dirname3(paths[0]);
|
|
11796
|
+
while (!paths.every((p) => p === dir || p.startsWith(dir.endsWith(sep2) ? dir : dir + sep2)))
|
|
11797
|
+
dir = dirname3(dir);
|
|
11798
|
+
return dir;
|
|
11799
|
+
};
|
|
11800
|
+
async function flowsVersion(files, read, skip = []) {
|
|
11801
|
+
if (files.length === 0)
|
|
11802
|
+
return null;
|
|
11803
|
+
const roots = files.map((f) => resolve5(f));
|
|
11804
|
+
const skipped = new Set(skip.map((s) => resolve5(s)));
|
|
11805
|
+
const seen = new Map;
|
|
11806
|
+
const visit = async (file, root) => {
|
|
11807
|
+
if (seen.has(file) || skipped.has(file))
|
|
11808
|
+
return true;
|
|
11809
|
+
const text = await read(file).then((t) => t.replace(/\r\n?/g, `
|
|
11810
|
+
`), () => null);
|
|
11811
|
+
if (text === null)
|
|
11812
|
+
return !root;
|
|
11813
|
+
seen.set(file, text);
|
|
11814
|
+
for (const m of text.matchAll(REFERENCE))
|
|
11815
|
+
await visit(resolve5(dirname3(file), m[1]), false);
|
|
11816
|
+
return true;
|
|
11817
|
+
};
|
|
11818
|
+
for (const r of roots)
|
|
11819
|
+
if (!await visit(r, true))
|
|
11820
|
+
return null;
|
|
11821
|
+
const base = commonDir(roots);
|
|
11822
|
+
const keyed = [...seen].map(([file, text]) => [relative3(base, file).split(sep2).join("/"), text]);
|
|
11823
|
+
const hash = createHash2("sha256");
|
|
11824
|
+
for (const [key, text] of keyed.sort((a, b) => a[0] < b[0] ? -1 : 1))
|
|
11825
|
+
hash.update(key).update("\x00").update(text).update("\x00");
|
|
11826
|
+
return hash.digest("hex").slice(0, 12);
|
|
11827
|
+
}
|
|
11828
|
+
async function maestroCliVersion(deps) {
|
|
11829
|
+
const r = await deps.spawner.run("maestro", ["--version"], { timeoutMs: 30000, killAfterMs: 5000 });
|
|
11830
|
+
if (r.code !== 0)
|
|
11831
|
+
return;
|
|
11832
|
+
return /^\s*v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\s*$/m.exec(r.stdout)?.[1];
|
|
11833
|
+
}
|
|
11834
|
+
|
|
11545
11835
|
// apps/agent/src/device-model.ts
|
|
11546
11836
|
import { tmpdir } from "node:os";
|
|
11547
11837
|
import { join as join12 } from "node:path";
|
|
@@ -11623,7 +11913,7 @@ async function detectSimulator(deps, udid) {
|
|
|
11623
11913
|
function slackMessage(s, ctx) {
|
|
11624
11914
|
if (ctx.blocked)
|
|
11625
11915
|
return noResultSlackPayload(ctx.branch, ctx.blocked.reason, s ? linkOf(s) : null);
|
|
11626
|
-
return s ? resultSlackPayload(s
|
|
11916
|
+
return s ? resultSlackPayload(s) : null;
|
|
11627
11917
|
}
|
|
11628
11918
|
async function postToSlack(url, payload, fetchImpl = fetch) {
|
|
11629
11919
|
try {
|
|
@@ -11684,6 +11974,19 @@ var maestroLinkFields = (sdkArgs = "") => ({ keepsnappyLinkArgs: linkArgs(sdkArg
|
|
|
11684
11974
|
|
|
11685
11975
|
// apps/agent/src/farm.ts
|
|
11686
11976
|
var STATE_VERSION = 1;
|
|
11977
|
+
function plantOf(raw) {
|
|
11978
|
+
let v;
|
|
11979
|
+
try {
|
|
11980
|
+
v = JSON.parse(raw);
|
|
11981
|
+
} catch {
|
|
11982
|
+
return null;
|
|
11983
|
+
}
|
|
11984
|
+
const ok = (p) => {
|
|
11985
|
+
const x = p;
|
|
11986
|
+
return !!x && typeof x.place === "string" && !!x.place && typeof x.kind === "string" && !!x.kind && typeof x.factor === "number" && Number.isFinite(x.factor) && x.factor > 0 && (x.unit === "budget" || x.unit === "MB");
|
|
11987
|
+
};
|
|
11988
|
+
return Array.isArray(v) && v.length > 0 && v.length <= 200 && v.every(ok) ? v : null;
|
|
11989
|
+
}
|
|
11687
11990
|
var COMPLETION_MARKER = "run:complete";
|
|
11688
11991
|
var SETUP_FLAGS = [
|
|
11689
11992
|
["driver-version", "driver_version"],
|
|
@@ -11692,19 +11995,26 @@ var SETUP_FLAGS = [
|
|
|
11692
11995
|
["journey-version", "journey_version"],
|
|
11693
11996
|
["flag-set", "flag_set"]
|
|
11694
11997
|
];
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11998
|
+
var LOCAL_PROVIDERS = ["local", "usb", "customer", "simulator", "emulator"];
|
|
11999
|
+
var COMMIT_ID = /^[0-9a-f]{40}$/i;
|
|
12000
|
+
function instrumentFlags(f, maestro = {}) {
|
|
12001
|
+
const given = f["journey-version"]?.trim();
|
|
12002
|
+
const named = given && !COMMIT_ID.test(given) ? given : undefined;
|
|
12003
|
+
if (!f["journey-module"] && f.driver !== "appium") {
|
|
12004
|
+
const driver = f["driver-version"] ?? maestro.maestroVersion;
|
|
12005
|
+
const journey = maestro.flowsVersion ?? named;
|
|
12006
|
+
return { ...driver ? { "driver-version": driver } : {}, ...journey ? { "journey-version": journey } : {} };
|
|
12007
|
+
}
|
|
11698
12008
|
let hash;
|
|
11699
12009
|
if (f["journey-module"]) {
|
|
11700
12010
|
try {
|
|
11701
|
-
hash = journeyVersion(
|
|
12011
|
+
hash = journeyVersion(resolve6(f["journey-module"]));
|
|
11702
12012
|
} catch {
|
|
11703
12013
|
hash = undefined;
|
|
11704
12014
|
}
|
|
11705
12015
|
}
|
|
11706
12016
|
const driver = f["driver-version"] ?? driverVersion() ?? undefined;
|
|
11707
|
-
const journey =
|
|
12017
|
+
const journey = hash ?? named;
|
|
11708
12018
|
return { ...driver ? { "driver-version": driver } : {}, ...journey ? { "journey-version": journey } : {} };
|
|
11709
12019
|
}
|
|
11710
12020
|
var TOKENS_STUB = "// Replaced by `keepsnappy farm open` for one upload. Never commit real tokens.\noutput.keepsnappyTokens = {};\n";
|
|
@@ -11765,7 +12075,8 @@ async function farmJourneys(deps, f, rt, journeys) {
|
|
|
11765
12075
|
if (!byName.has(j))
|
|
11766
12076
|
rt.print(`journey ${j}: no flow file named ${j}.yaml in --config/--flows; sent without a title`);
|
|
11767
12077
|
}
|
|
11768
|
-
|
|
12078
|
+
const info = await journeysFor(journeys.map((j) => ({ name: j, ...byName.get(j) ?? {} })), { readFile: (p) => deps.fs.readFile(p), root: files.length ? await repoRoot(deps) : null });
|
|
12079
|
+
return { journeys: info, files: files.map((x) => x.file) };
|
|
11769
12080
|
}
|
|
11770
12081
|
async function farmOpen(deps, f, rt) {
|
|
11771
12082
|
const problems = [];
|
|
@@ -11782,7 +12093,7 @@ async function farmOpen(deps, f, rt) {
|
|
|
11782
12093
|
provider = "simulator";
|
|
11783
12094
|
model = model ? withSimulatorWord(model) : simulator;
|
|
11784
12095
|
}
|
|
11785
|
-
if (!model &&
|
|
12096
|
+
if (!model && LOCAL_PROVIDERS.includes(provider)) {
|
|
11786
12097
|
model = await detectDeviceModel(deps, { platform: f.platform, deviceId: f["device-id"]?.trim() }) ?? undefined;
|
|
11787
12098
|
if (model)
|
|
11788
12099
|
rt.print(`device model read from the device: ${model}`);
|
|
@@ -11809,18 +12120,29 @@ async function farmOpen(deps, f, rt) {
|
|
|
11809
12120
|
problems.push("--tokens-js <path> is required (the file the suite overlay puts at flows/keepsnappy/tokens.js)");
|
|
11810
12121
|
if (!f.state)
|
|
11811
12122
|
problems.push("--state <path> is required (read by keepsnappy farm close)");
|
|
12123
|
+
const plant = f.plant === undefined ? undefined : plantOf(f.plant);
|
|
12124
|
+
if (plant === null)
|
|
12125
|
+
problems.push('--plant must be a JSON list of { place, kind, factor > 0, unit: "budget" | "MB" }');
|
|
11812
12126
|
if (problems.length)
|
|
11813
12127
|
throw new ConfigError(problems);
|
|
11814
|
-
const statePath =
|
|
11815
|
-
const tokensPath =
|
|
12128
|
+
const statePath = resolve6(f.state);
|
|
12129
|
+
const tokensPath = resolve6(f["tokens-js"]);
|
|
11816
12130
|
if (await deps.fs.exists(statePath)) {
|
|
11817
12131
|
throw new ConfigError([`${statePath} already exists: close that run set first (keepsnappy farm close --state ${f.state}) or remove the file`]);
|
|
11818
12132
|
}
|
|
11819
12133
|
const api = apiTarget(url, f, rt);
|
|
11820
12134
|
const now = rt.now ?? Date.now;
|
|
11821
12135
|
const device = { model, os: f["device-os"] ?? null, provider };
|
|
11822
|
-
const journeyInfo = await farmJourneys(deps, f, rt, journeys);
|
|
11823
|
-
const
|
|
12136
|
+
const { journeys: journeyInfo, files: flowFiles } = await farmJourneys(deps, f, rt, journeys);
|
|
12137
|
+
const { "driver-version": _driver, "journey-version": given, ...rest } = f;
|
|
12138
|
+
const maestro = !f["journey-module"] && f.driver !== "appium";
|
|
12139
|
+
const flows = maestro ? await flowsVersion(flowFiles, (p) => deps.fs.readFile(p), [tokensPath]) : null;
|
|
12140
|
+
const asked = maestro && !f["driver-version"] && LOCAL_PROVIDERS.includes(provider) ? await maestroCliVersion(deps) : undefined;
|
|
12141
|
+
const instrument = instrumentFlags(f, { ...flows ? { flowsVersion: flows } : {}, ...asked ? { maestroVersion: asked } : {} });
|
|
12142
|
+
if (given?.trim() && given.trim() !== instrument["journey-version"]) {
|
|
12143
|
+
rt.print(`--journey-version ${given.trim()} ignored: ${instrument["journey-version"] ? `the journey version is the hash of what runs, ${instrument["journey-version"]}` : "a commit id names the checkout, not the journey; the run records no journey version"}`);
|
|
12144
|
+
}
|
|
12145
|
+
const withInstrument = { ...rest, ...instrument };
|
|
11824
12146
|
const setup = Object.fromEntries(SETUP_FLAGS.flatMap(([flag, key]) => withInstrument[flag]?.trim() ? [[key, withInstrument[flag].trim().slice(0, 200)]] : []));
|
|
11825
12147
|
const base = await baseCommitFor(deps, rt.env, { explicit: f["base-commit"], branch: git.branch, commit: git.commit }, rt.print);
|
|
11826
12148
|
const main = await mainMerges(deps, rt.env, { branch: git.branch, commit: git.commit });
|
|
@@ -11837,7 +12159,13 @@ async function farmOpen(deps, f, rt) {
|
|
|
11837
12159
|
journeys: journeyInfo,
|
|
11838
12160
|
repetitions: repeat,
|
|
11839
12161
|
source: "farm",
|
|
11840
|
-
...f["completion-marker"] || Object.keys(setup).length
|
|
12162
|
+
...f["completion-marker"] || Object.keys(setup).length || plant ? {
|
|
12163
|
+
meta: {
|
|
12164
|
+
...f["completion-marker"] ? { completion_marker: COMPLETION_MARKER } : {},
|
|
12165
|
+
...Object.keys(setup).length ? { setup } : {},
|
|
12166
|
+
...plant ? { plant } : {}
|
|
12167
|
+
}
|
|
12168
|
+
} : {}
|
|
11841
12169
|
}).catch(infra2(`cannot open a run set at ${api.url}`));
|
|
11842
12170
|
const prefix = `${git.commit ? git.commit.slice(0, 12) : "farm"}-${rs.run_set_id.slice(0, 8)}`;
|
|
11843
12171
|
const runs = [];
|
|
@@ -11868,7 +12196,7 @@ async function farmOpen(deps, f, rt) {
|
|
|
11868
12196
|
if (rt.env.GITHUB_ACTIONS === "true")
|
|
11869
12197
|
for (const t of Object.values(tokens).flat())
|
|
11870
12198
|
rt.print(`::add-mask::${t}`);
|
|
11871
|
-
await deps.fs.mkdir(
|
|
12199
|
+
await deps.fs.mkdir(dirname4(tokensPath));
|
|
11872
12200
|
await deps.fs.writeFile(tokensPath, tokensJs(api.url, rs.run_set_id, tokens, maestroLinkFields(f["sdk-args"] ?? "")));
|
|
11873
12201
|
const state = {
|
|
11874
12202
|
version: STATE_VERSION,
|
|
@@ -11888,7 +12216,7 @@ async function farmOpen(deps, f, rt) {
|
|
|
11888
12216
|
closed_at: null,
|
|
11889
12217
|
result: null
|
|
11890
12218
|
};
|
|
11891
|
-
await deps.fs.mkdir(
|
|
12219
|
+
await deps.fs.mkdir(dirname4(statePath));
|
|
11892
12220
|
await deps.fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}
|
|
11893
12221
|
`);
|
|
11894
12222
|
await githubOutputs(deps, rt, {
|
|
@@ -11994,7 +12322,7 @@ async function farmClose(deps, f, rt) {
|
|
|
11994
12322
|
throw new ConfigError(["--state <path> is required (written by keepsnappy farm open)"]);
|
|
11995
12323
|
if (f["fail-on-worse"] && !f.wait)
|
|
11996
12324
|
throw new ConfigError(["--fail-on-worse needs --wait"]);
|
|
11997
|
-
const statePath =
|
|
12325
|
+
const statePath = resolve6(f.state);
|
|
11998
12326
|
let state;
|
|
11999
12327
|
try {
|
|
12000
12328
|
state = JSON.parse(await deps.fs.readFile(statePath));
|
|
@@ -12021,10 +12349,10 @@ async function closeRunSet(deps, f, rt, state, statePath) {
|
|
|
12021
12349
|
const api = apiTarget(f.api ?? f["ingest-url"] ?? state.api, f, rt);
|
|
12022
12350
|
const now = rt.now ?? Date.now;
|
|
12023
12351
|
const sleep = rt.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
12024
|
-
const out =
|
|
12352
|
+
const out = resolve6(f.out ?? dirname4(statePath));
|
|
12025
12353
|
let bsText = null;
|
|
12026
12354
|
if (f["bs-build"])
|
|
12027
|
-
bsText = await deps.fs.readFile(
|
|
12355
|
+
bsText = await deps.fs.readFile(resolve6(f["bs-build"])).catch(() => null);
|
|
12028
12356
|
const session = sessionOutcome(bsText);
|
|
12029
12357
|
const where = `${state.branch}${state.commit ? ` @ ${state.commit.slice(0, 7)}` : ""}`;
|
|
12030
12358
|
rt.print(`KeepSnappy run set ${state.run_set_id} (${where}): closing`);
|
|
@@ -12109,7 +12437,7 @@ async function closeRunSet(deps, f, rt, state, statePath) {
|
|
|
12109
12437
|
// apps/agent/src/farm-run.ts
|
|
12110
12438
|
import { existsSync as existsSync2 } from "node:fs";
|
|
12111
12439
|
import { createRequire } from "node:module";
|
|
12112
|
-
import { dirname as
|
|
12440
|
+
import { dirname as dirname5, join as join16, resolve as resolve7 } from "node:path";
|
|
12113
12441
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12114
12442
|
|
|
12115
12443
|
// apps/agent/src/app-source.ts
|
|
@@ -12382,10 +12710,10 @@ ${r.stderr}`;
|
|
|
12382
12710
|
// apps/agent/src/farm-run.ts
|
|
12383
12711
|
function appiumDriverEntry(o = {}) {
|
|
12384
12712
|
const exists = o.exists ?? existsSync2;
|
|
12385
|
-
const bundled = join16(o.selfDir ??
|
|
12713
|
+
const bundled = join16(o.selfDir ?? dirname5(fileURLToPath2(import.meta.url)), "appium-driver.js");
|
|
12386
12714
|
if (exists(bundled))
|
|
12387
12715
|
return bundled;
|
|
12388
|
-
const pkgDir =
|
|
12716
|
+
const pkgDir = dirname5(o.packageJson ? o.packageJson() : createRequire(import.meta.url).resolve("@keepsnappy/appium-driver/package.json"));
|
|
12389
12717
|
const built = join16(pkgDir, "dist", "src", "cli.js");
|
|
12390
12718
|
return exists(built) ? built : join16(pkgDir, "src", "cli.ts");
|
|
12391
12719
|
}
|
|
@@ -12484,7 +12812,7 @@ function planFarmRun(f) {
|
|
|
12484
12812
|
ports,
|
|
12485
12813
|
env,
|
|
12486
12814
|
driverArgs: f["driver-arg"] ?? [],
|
|
12487
|
-
out:
|
|
12815
|
+
out: resolve7(f.out ?? "farm-run"),
|
|
12488
12816
|
timeoutMinutes,
|
|
12489
12817
|
killAfterSeconds,
|
|
12490
12818
|
pollSeconds
|
|
@@ -12533,7 +12861,7 @@ async function runDriver2(deps, rt, f, p, ports, ctx) {
|
|
|
12533
12861
|
device: p.device,
|
|
12534
12862
|
app: ctx.app,
|
|
12535
12863
|
...f["custom-id"] ? { customId: f["custom-id"] } : {},
|
|
12536
|
-
suiteDir:
|
|
12864
|
+
suiteDir: resolve7(f["suite-dir"]),
|
|
12537
12865
|
tokensPath: f["tokens-path"] ?? "flows/keepsnappy/tokens.js",
|
|
12538
12866
|
tokensJs: ctx.tokensJs,
|
|
12539
12867
|
execute: f.execute,
|
|
@@ -12559,7 +12887,7 @@ async function runDriver2(deps, rt, f, p, ports, ctx) {
|
|
|
12559
12887
|
throw new Error("no Appium driver entry: pass --driver-script");
|
|
12560
12888
|
const args = [
|
|
12561
12889
|
entry,
|
|
12562
|
-
...f["journey-module"] ? ["--journey-module",
|
|
12890
|
+
...f["journey-module"] ? ["--journey-module", resolve7(f["journey-module"])] : [],
|
|
12563
12891
|
"--platform",
|
|
12564
12892
|
p.platform,
|
|
12565
12893
|
"--device",
|
|
@@ -12636,7 +12964,8 @@ async function farmRun(deps, f, rt, ports) {
|
|
|
12636
12964
|
driver: p.driver,
|
|
12637
12965
|
"tokens-js": tokensJs,
|
|
12638
12966
|
state: statePath,
|
|
12639
|
-
...commit ? { commit } : {}
|
|
12967
|
+
...commit ? { commit } : {},
|
|
12968
|
+
...p.driver !== "appium" && !f["driver-version"] && f["maestro-version"] ? { "driver-version": f["maestro-version"] } : {}
|
|
12640
12969
|
}, rt);
|
|
12641
12970
|
if (openCode !== EXIT.ok)
|
|
12642
12971
|
return openCode;
|
|
@@ -12679,15 +13008,15 @@ async function farmRun(deps, f, rt, ports) {
|
|
|
12679
13008
|
}
|
|
12680
13009
|
|
|
12681
13010
|
// apps/agent/src/sourcemaps.ts
|
|
12682
|
-
import { createHash as
|
|
13011
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
12683
13012
|
import { access, readFile as readFile4, readdir as readdir2, rename, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
12684
|
-
import { basename as basename3, join as join17, resolve as
|
|
13013
|
+
import { basename as basename3, join as join17, resolve as resolve8 } from "node:path";
|
|
12685
13014
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
12686
13015
|
import { gzipSync } from "node:zlib";
|
|
12687
13016
|
var MAX_COMPLETE_ROUNDS = 20;
|
|
12688
13017
|
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12689
13018
|
async function prepareComponents(projectRoot, options = {}) {
|
|
12690
|
-
const root =
|
|
13019
|
+
const root = resolve8(projectRoot);
|
|
12691
13020
|
const directory = join17(root, ".keepsnappy", "components");
|
|
12692
13021
|
const configurations = await readdir2(directory, { withFileTypes: true }).catch((error) => {
|
|
12693
13022
|
if (error.code === "ENOENT")
|
|
@@ -12728,7 +13057,7 @@ async function prepareComponents(projectRoot, options = {}) {
|
|
|
12728
13057
|
throw new ConfigError([`invalid KeepSnappy component fragment ${file}`]);
|
|
12729
13058
|
if (!hashes.has(fragment.source)) {
|
|
12730
13059
|
try {
|
|
12731
|
-
hashes.set(fragment.source,
|
|
13060
|
+
hashes.set(fragment.source, createHash3("sha256").update(await readFile4(resolve8(root, fragment.source))).digest("hex"));
|
|
12732
13061
|
} catch (error) {
|
|
12733
13062
|
if (error.code !== "ENOENT")
|
|
12734
13063
|
throw error;
|
|
@@ -12753,7 +13082,7 @@ async function prepareComponents(projectRoot, options = {}) {
|
|
|
12753
13082
|
await rm(temporary, { force: true });
|
|
12754
13083
|
}
|
|
12755
13084
|
const gzip = gzipSync(text);
|
|
12756
|
-
return { file, count: components.length, bytes: Buffer.byteLength(text), gzip, sha256:
|
|
13085
|
+
return { file, count: components.length, bytes: Buffer.byteLength(text), gzip, sha256: createHash3("sha256").update(gzip).digest("hex") };
|
|
12757
13086
|
}
|
|
12758
13087
|
function stripSourcesContent(map) {
|
|
12759
13088
|
if (!isObject(map))
|
|
@@ -12807,7 +13136,7 @@ async function uploadSourcemaps(opts) {
|
|
|
12807
13136
|
const { map, stripped } = stripSourcesContent(parsed);
|
|
12808
13137
|
const text = Buffer.from(JSON.stringify(map));
|
|
12809
13138
|
const gz = gzipSync(text);
|
|
12810
|
-
const sha256 =
|
|
13139
|
+
const sha256 = createHash3("sha256").update(gz).digest("hex");
|
|
12811
13140
|
const name = basename3(file);
|
|
12812
13141
|
const announced = await call(f, opts.url, `/v1/builds/${encodeURIComponent(opts.build)}/sourcemaps`, headers, {
|
|
12813
13142
|
platform: opts.platform,
|
|
@@ -12957,7 +13286,8 @@ var SHORT_FORM_NOTES = {
|
|
|
12957
13286
|
window_history: `the runs older than the newest ${SHORT_POINTS}`,
|
|
12958
13287
|
pr_status: "the per-journey dashboard links and the heat note",
|
|
12959
13288
|
list_problems: `the work and component lines, the dashboard links and the problems after the first ${SHORT_PLACES}`,
|
|
12960
|
-
compare_runs: `the component lines and the measures beyond each place's ${SHORT_COMPARE_MEASURES} times that moved most
|
|
13289
|
+
compare_runs: `the component lines and the measures beyond each place's ${SHORT_COMPARE_MEASURES} times that moved most`,
|
|
13290
|
+
get_investigation: "the lines of the worse places, the components to check, the problems and the commits"
|
|
12961
13291
|
};
|
|
12962
13292
|
var andList = (xs) => xs.length < 2 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
12963
13293
|
|
|
@@ -13060,7 +13390,7 @@ function measuredLines(l, r, opts) {
|
|
|
13060
13390
|
}
|
|
13061
13391
|
const lane = (x) => {
|
|
13062
13392
|
const runs = x.runs.after !== null && x.runs.before !== null ? x.branch === l.baseline_branch ? `, ${x.runs.after} latest runs vs ${x.runs.before} earlier` : `, ${x.runs.after} branch runs vs ${x.runs.before} on ${l.baseline_branch}` : "";
|
|
13063
|
-
return `${x.commit?.sha ? `commit ${x.commit.sha.slice(0, 7)}` : "commit not recorded"} (${when(x.at)}): ${x.word}${runs}${x.waiting_for_reprocess ? `; ${waitingWords(x.waiting_for_reprocess)}` : ""}`;
|
|
13393
|
+
return `${x.commit?.sha ? `commit ${x.commit.sha.slice(0, 7)}` : "commit not recorded"} (${when(x.at)}): ${x.word}${feltWords(x)}${runs}${x.waiting_for_reprocess ? `; ${waitingWords(x.waiting_for_reprocess)}` : ""}`;
|
|
13064
13394
|
};
|
|
13065
13395
|
if (m.lanes.length === 1) {
|
|
13066
13396
|
r.both(`${scope}: newest measured ${f.branch ? "" : `on ${n.branch}, `}${lane(n)}; ${plural2(m.run_sets, "run set")} measured.`);
|
|
@@ -13086,6 +13416,7 @@ function measuredLines(l, r, opts) {
|
|
|
13086
13416
|
...m.lanes.length > shown.length ? [plural2(m.lanes.length - shown.length, "lane") + " beyond the eighth"] : []
|
|
13087
13417
|
];
|
|
13088
13418
|
}
|
|
13419
|
+
var feltWords = (x) => `${x.felt_cost ? ` · ${x.felt_cost}` : ""}${x.to_check ? `; ${SUMMARY_WORDS.toCheck(x.to_check)}` : ""}`;
|
|
13089
13420
|
function countsLine2(l) {
|
|
13090
13421
|
const c = l.counts;
|
|
13091
13422
|
const parts = [
|
|
@@ -13139,10 +13470,14 @@ function formatBranchStatus(s, opts = {}) {
|
|
|
13139
13470
|
if (s.lanes.length)
|
|
13140
13471
|
r.both("");
|
|
13141
13472
|
for (const l of s.lanes) {
|
|
13142
|
-
const
|
|
13473
|
+
const blocks = blocksUnderPreviousRulesText({ blocking: l.blocking, stale: !!l.rules }) ?? "blocks";
|
|
13474
|
+
const shown = `${l.result === "blocks" ? `${l.word} (${blocks})` : badgedWord(l.word, l.blocking)}${l.stale && !l.rules ? ` (${STALE_FLAG})` : ""}`;
|
|
13143
13475
|
const sha = l.commit?.sha?.slice(0, 7);
|
|
13144
|
-
r.both(` ${l.journey_title ?? l.journey} on ${l.phone_name}: ${shown} · ${sha ? `commit ${sha}` : "commit not recorded"} (${when(l.at)})${l.in_newest_run_set ? "" : " · older run set"}${l.waiting_for_reprocess ? `; ${waitingWords(l.waiting_for_reprocess)}` : ""}`);
|
|
13476
|
+
r.both(` ${l.journey_title ?? l.journey} on ${l.phone_name}: ${shown}${feltWords(l)} · ${sha ? `commit ${sha}` : "commit not recorded"} (${when(l.at)})${l.in_newest_run_set ? "" : " · older run set"}${l.waiting_for_reprocess ? `; ${waitingWords(l.waiting_for_reprocess)}` : ""}`);
|
|
13145
13477
|
r.both(...resultContextLines(l.context, opts.verbose, opts.form === "short"));
|
|
13478
|
+
const rulesLine = staleRulesText(l.rules);
|
|
13479
|
+
if (rulesLine)
|
|
13480
|
+
r.both(` ${rulesLine}`);
|
|
13146
13481
|
r.both(...(l.comparison_notes ?? []).map((line) => ` ${line}`));
|
|
13147
13482
|
r.full(...(l.session_speed ?? []).map((line) => ` ${line}`));
|
|
13148
13483
|
for (const f of l.worst)
|
|
@@ -13211,11 +13546,11 @@ function formatProblemList(l, opts = {}) {
|
|
|
13211
13546
|
r.both(`${staleWords(c.stale_lanes)}: ${c.stale_lanes === 1 ? "its" : "their"} problems are listed, each marked “${STALE_MARK}”.`);
|
|
13212
13547
|
l.problems.forEach((p, i) => {
|
|
13213
13548
|
const keep = i < SHORT_PLACES;
|
|
13214
|
-
r.add(keep, "", `${l.offset + i + 1}. ${p.
|
|
13549
|
+
r.add(keep, "", `${l.offset + i + 1}. ${p.place_name}${opts.verbose ? ` (${p.place})` : ""}: ${p.text}${p.intermittent ? ` — ${p.note}, not counted` : ""}`);
|
|
13215
13550
|
r.add(keep, ` ${p.journey_title ?? p.journey} on ${p.phone_name}, ${p.runs_text}${p.tag_word ? `, ${p.tag_word}` : ""}${p.stale ? `, ${STALE_MARK}` : ""}`);
|
|
13216
13551
|
const top = p.top_components[0];
|
|
13217
13552
|
const topName = componentReadNames(p.top_components)[0];
|
|
13218
|
-
const detail = [p.work, top ?
|
|
13553
|
+
const detail = [p.work, top ? `${namePartsText(heaviestWords(`${topName}${opts.verbose && top.key ? ` (${top.key})` : ""}`, top.self_ms === null ? null : top.self_text))}` : null, p.new_component ? NEW_IN_PR_CHIP : null, p.range_text ? `${p.range_text} between runs` : null].filter(Boolean);
|
|
13219
13554
|
if (detail.length)
|
|
13220
13555
|
r.full(` ${detail.join("; ")}`);
|
|
13221
13556
|
if (p.url)
|
|
@@ -13571,7 +13906,7 @@ class ApiError extends Error {
|
|
|
13571
13906
|
this.name = "ApiError";
|
|
13572
13907
|
}
|
|
13573
13908
|
}
|
|
13574
|
-
async function get(t, path, query = {}) {
|
|
13909
|
+
async function get(t, path, query = {}, send = {}) {
|
|
13575
13910
|
const u = new URL(path, t.url.endsWith("/") ? t.url : `${t.url}/`);
|
|
13576
13911
|
for (const [k, v] of Object.entries(query)) {
|
|
13577
13912
|
if (Array.isArray(v))
|
|
@@ -13580,7 +13915,10 @@ async function get(t, path, query = {}) {
|
|
|
13580
13915
|
else if (v !== undefined && v !== "")
|
|
13581
13916
|
u.searchParams.set(k, String(v));
|
|
13582
13917
|
}
|
|
13583
|
-
const
|
|
13918
|
+
const headers = { "x-keepsnappy-key": t.key, accept: "application/json" };
|
|
13919
|
+
if (send.body !== undefined)
|
|
13920
|
+
headers["content-type"] = "application/json";
|
|
13921
|
+
const r = await (t.fetch ?? fetch)(u, { method: send.method ?? "GET", headers, ...send.body !== undefined ? { body: JSON.stringify(send.body) } : {} });
|
|
13584
13922
|
const text = await r.text();
|
|
13585
13923
|
let body;
|
|
13586
13924
|
try {
|
|
@@ -13592,7 +13930,7 @@ async function get(t, path, query = {}) {
|
|
|
13592
13930
|
if (!r.ok) {
|
|
13593
13931
|
const code = typeof body.error === "string" ? body.error : String(r.status);
|
|
13594
13932
|
const message = typeof body.message === "string" ? body.message : code;
|
|
13595
|
-
const hint = r.status === 401 ? " (check KEEPSNAPPY_KEY)" : r.status === 403 ?
|
|
13933
|
+
const hint = r.status === 401 ? " (check KEEPSNAPPY_KEY)" : r.status === 403 ? ` (the key needs the ${send.scope ?? "read or run"} scope)` : "";
|
|
13596
13934
|
throw new ApiError(r.status, code, `${message}${hint}`);
|
|
13597
13935
|
}
|
|
13598
13936
|
return body;
|
|
@@ -13958,6 +14296,7 @@ var USAGE = `keepsnappy <command>
|
|
|
13958
14296
|
[--config keepsnappy.yml] [--flows <files|dirs|globs,...>] (journey titles: keepsnappy.yml flows.paths, else the flow's name: / top comment)
|
|
13959
14297
|
[--completion-marker] (the flow posts step run:complete at the end of each iteration; runs without it are invalid)
|
|
13960
14298
|
[--driver appium --journey-module FILE] [--driver-version V] [--app-build-id ID] [--harness-version V] [--journey-version V] [--flag-set LABEL] (setup fingerprint, plan 51)
|
|
14299
|
+
[--plant JSON] (what the build plants against main, [{ place, kind, factor, unit: budget|MB }]: the run set's meta.plant)
|
|
13961
14300
|
[--sdk-args k=v&...] (over the run defaults profile=1 diag=1; tokens.js carries them to the flows' activation link)
|
|
13962
14301
|
farm close --state <file> [--wait [--fail-on-worse]] [--bs-build browserstack.ios.json] [--settle-seconds 60] [--max-wait-seconds 300] [--out DIR]
|
|
13963
14302
|
(--fail-on-worse as for run: with no verdict computed on the run set it checks nothing)
|
|
@@ -14053,6 +14392,7 @@ async function main2(argv) {
|
|
|
14053
14392
|
"harness-version": { type: "string" },
|
|
14054
14393
|
"journey-version": { type: "string" },
|
|
14055
14394
|
"flag-set": { type: "string" },
|
|
14395
|
+
plant: { type: "string" },
|
|
14056
14396
|
app: { type: "string" },
|
|
14057
14397
|
driver: { type: "string" },
|
|
14058
14398
|
"tunnel-for": { type: "string", multiple: true },
|
|
@@ -14103,9 +14443,9 @@ async function main2(argv) {
|
|
|
14103
14443
|
if (platform === "ios-device" && !o.device)
|
|
14104
14444
|
throw new ConfigError(["--device <udid> is required for ios-device"]);
|
|
14105
14445
|
const arms = parseArmEnv(o["arm-env"], parseArms(o.arms));
|
|
14106
|
-
const out =
|
|
14446
|
+
const out = resolve9(o.out ?? `keepsnappy-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
14107
14447
|
const udid = o.device ?? "booted";
|
|
14108
|
-
const source = platform === "ios-sim" ? new IosSimulatorDataSource(deps, udid) : new IosDeviceDataSource(deps, udid,
|
|
14448
|
+
const source = platform === "ios-sim" ? new IosSimulatorDataSource(deps, udid) : new IosDeviceDataSource(deps, udid, resolve9(out, ".scratch"));
|
|
14109
14449
|
const installer = platform === "ios-sim" ? new IosSimulatorInstaller(deps, udid) : new IosDeviceInstaller(deps, udid);
|
|
14110
14450
|
const bundleId = await bundleIdOf(deps, arms[0]?.target, o["bundle-id"]);
|
|
14111
14451
|
let markers;
|
|
@@ -14124,7 +14464,7 @@ async function main2(argv) {
|
|
|
14124
14464
|
outDir: out,
|
|
14125
14465
|
testCommand: positionals,
|
|
14126
14466
|
bundleId,
|
|
14127
|
-
...o["ri-capture"] ? { ri: new FileRiSource(deps,
|
|
14467
|
+
...o["ri-capture"] ? { ri: new FileRiSource(deps, resolve9(o["ri-capture"])) } : {},
|
|
14128
14468
|
...o["mem-window"] ? { memWindow: o["mem-window"] } : {},
|
|
14129
14469
|
...o.timeout ? { testTimeoutMs: Number(o.timeout) * 1000 } : {}
|
|
14130
14470
|
});
|
|
@@ -14136,9 +14476,9 @@ async function main2(argv) {
|
|
|
14136
14476
|
const dir = positionals[0];
|
|
14137
14477
|
if (!dir)
|
|
14138
14478
|
throw new ConfigError(["extract needs <out-dir>"]);
|
|
14139
|
-
const ri = o["ri-capture"] ? new FileRiSource(deps,
|
|
14140
|
-
const rows = await extract(deps,
|
|
14141
|
-
console.log(`${rows.length} rows -> ${
|
|
14479
|
+
const ri = o["ri-capture"] ? new FileRiSource(deps, resolve9(o["ri-capture"])) : undefined;
|
|
14480
|
+
const rows = await extract(deps, resolve9(dir), ri, o["mem-window"], o["clock-offset-ms"] ? Number(o["clock-offset-ms"]) : undefined);
|
|
14481
|
+
console.log(`${rows.length} rows -> ${resolve9(dir, "metrics.csv")}`);
|
|
14142
14482
|
return 0;
|
|
14143
14483
|
}
|
|
14144
14484
|
if (cmd === "aa") {
|
|
@@ -14146,7 +14486,7 @@ async function main2(argv) {
|
|
|
14146
14486
|
if (!dir)
|
|
14147
14487
|
throw new ConfigError(["aa needs <out-dir>"]);
|
|
14148
14488
|
const config = { ...o.reps ? { reps: Number(o.reps) } : {}, ...o.boot ? { nBoot: Number(o.boot) } : {} };
|
|
14149
|
-
console.log((await aa(deps,
|
|
14489
|
+
console.log((await aa(deps, resolve9(dir), { config })).table);
|
|
14150
14490
|
return 0;
|
|
14151
14491
|
}
|
|
14152
14492
|
console.error(USAGE);
|