@keepsnappy/agent 0.1.0-alpha.19 → 0.1.0-alpha.20
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 +452 -217
- 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
|
|
@@ -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
|
}
|
|
@@ -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";
|
|
@@ -5384,12 +5409,18 @@ var screenLeadText = (tier, subject, measure, main, pr) => `${STATE_WORDS[TIER_S
|
|
|
5384
5409
|
var FOR_INFORMATION = "For information";
|
|
5385
5410
|
var informationLeadText = (subject, measure, main, pr) => `${FOR_INFORMATION}: ${leadBody(subject, measure, main, pr)}`;
|
|
5386
5411
|
function placeShownName(p, o = {}) {
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5412
|
+
return namePartsText(placeShownNameParts(p, o));
|
|
5413
|
+
}
|
|
5414
|
+
function placeShownNameParts(p, o = {}) {
|
|
5415
|
+
const target = p.target ? componentNameParts(p.target.name, p.target.file) : plainParts(libraryViewName(p.kind, p.host ?? ""));
|
|
5416
|
+
const named = p.target && p.parent ? [...target, textPart(" in "), ...componentNameParts(p.parent.name, p.parent.file), textPart(p.k ? `, ${ordinal(p.k)}` : "")] : target;
|
|
5417
|
+
const screen = typeof o.onScreen === "string" ? plainParts(o.onScreen) : o.onScreen;
|
|
5418
|
+
return nameParts(screen?.length && namePartsText(screen) ? [...named, textPart(" on "), ...screen] : named);
|
|
5390
5419
|
}
|
|
5391
5420
|
var libraryViewName = (kind, hostType) => `${kind === "drag" ? "Scroll" : "Tap"} outside app components (${hostType})`;
|
|
5392
|
-
|
|
5421
|
+
var componentShownName = (name, file) => componentFileName(name, file);
|
|
5422
|
+
var componentNameParts = (name, file) => [componentPart(componentFileName(name, file))];
|
|
5423
|
+
function componentFileName(name, file) {
|
|
5393
5424
|
if (name !== "default" || !file)
|
|
5394
5425
|
return name;
|
|
5395
5426
|
const path = file.replace(/\.[^.\/]+$/, "");
|
|
@@ -5456,21 +5487,36 @@ function foldTwins(rows, placeOf, also = () => "") {
|
|
|
5456
5487
|
return { rows: out, folded: rows.length - out.length };
|
|
5457
5488
|
}
|
|
5458
5489
|
var foldedTwinsText = (n) => `${n} “${PLACE_KIND_WORDS.after3s}” ${n === 1 ? "twin" : "twins"} folded into ${n === 1 ? "its place" : "their places"}`;
|
|
5459
|
-
function
|
|
5490
|
+
function placeIdShownNameParts(id, o = {}) {
|
|
5460
5491
|
const p = placeParts(id);
|
|
5461
5492
|
if (!p)
|
|
5462
5493
|
return null;
|
|
5463
5494
|
const component = (part) => ({ name: part.name, file: part.file ?? "" });
|
|
5464
|
-
const screen = p.screen.name === "(no screen)" ?
|
|
5495
|
+
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
5496
|
if (p.kind === "screen" || p.kind === "launch")
|
|
5466
|
-
return screen
|
|
5467
|
-
return
|
|
5497
|
+
return nameParts([...screen, textPart(p.kind === "screen" && typeof p.visit === "number" && p.visit > 1 ? ` (${ordinal(p.visit)} visit)` : "")]);
|
|
5498
|
+
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
5499
|
}
|
|
5469
5500
|
var isLibraryPlace = (id) => placeParts(id)?.outsideHost != null;
|
|
5470
|
-
function
|
|
5471
|
-
const
|
|
5501
|
+
function dropTail(parts, n) {
|
|
5502
|
+
const out = [];
|
|
5503
|
+
for (const p of [...parts].reverse()) {
|
|
5504
|
+
if (n >= p.text.length) {
|
|
5505
|
+
n -= p.text.length;
|
|
5506
|
+
continue;
|
|
5507
|
+
}
|
|
5508
|
+
out.unshift({ ...p, text: p.text.slice(0, p.text.length - n) });
|
|
5509
|
+
n = 0;
|
|
5510
|
+
}
|
|
5511
|
+
return out;
|
|
5512
|
+
}
|
|
5513
|
+
function stemNameParts(stem, kind) {
|
|
5514
|
+
const shown = placeIdShownNameParts(stem);
|
|
5472
5515
|
if (shown !== null)
|
|
5473
|
-
return isLibraryPlace(stem) ? shown : `${KIND_WORD[kind]}:
|
|
5516
|
+
return nameParts(isLibraryPlace(stem) ? shown : [textPart(`${KIND_WORD[kind]}: `), ...shown]);
|
|
5517
|
+
return plainParts(stemText(stem, kind));
|
|
5518
|
+
}
|
|
5519
|
+
function stemText(stem, kind) {
|
|
5474
5520
|
if (kind === "journey")
|
|
5475
5521
|
return PLACE_KIND_WORDS.journey;
|
|
5476
5522
|
if (kind === "other")
|
|
@@ -5486,12 +5532,13 @@ function stemName(stem, kind) {
|
|
|
5486
5532
|
const visit = visitMark ? Number(visitMark[2]) : 1;
|
|
5487
5533
|
return `${PLACE_KIND_WORDS.screen}: ${screenName(visitMark ? visitMark[1] : rest)}${visit > 1 ? ` (${ordinal(visit)} visit)` : ""}`;
|
|
5488
5534
|
}
|
|
5489
|
-
|
|
5535
|
+
var windowName = (id) => namePartsText(windowNameParts(id));
|
|
5536
|
+
function windowNameParts(id) {
|
|
5490
5537
|
if (id === "flow")
|
|
5491
|
-
return PLACE_KIND_WORDS.journey;
|
|
5538
|
+
return plainParts(PLACE_KIND_WORDS.journey);
|
|
5492
5539
|
const ref = placeRef(id);
|
|
5493
|
-
const name =
|
|
5494
|
-
return ref.segment === "after3s" ?
|
|
5540
|
+
const name = stemNameParts(ref.stem, ref.kind);
|
|
5541
|
+
return ref.segment === "after3s" ? nameParts([...name, textPart(` · ${PLACE_KIND_WORDS.after3s}`)]) : name;
|
|
5495
5542
|
}
|
|
5496
5543
|
function placeKindWord(id) {
|
|
5497
5544
|
if (id === "flow")
|
|
@@ -5499,31 +5546,33 @@ function placeKindWord(id) {
|
|
|
5499
5546
|
const { kind } = placeRef(id);
|
|
5500
5547
|
return kind === "other" ? "Window" : KIND_WORD[kind];
|
|
5501
5548
|
}
|
|
5502
|
-
|
|
5549
|
+
var windowQualified = (id) => namePartsText(windowQualifiedParts(id));
|
|
5550
|
+
function windowQualifiedParts(id) {
|
|
5503
5551
|
const word = placeOpenWord(id);
|
|
5504
|
-
const label =
|
|
5552
|
+
const label = placeLabelParts(id);
|
|
5505
5553
|
if (!word)
|
|
5506
5554
|
return label;
|
|
5507
5555
|
const after = placeRef(id).segment === "after3s" ? ` · ${PLACE_KIND_WORDS.after3s}` : "";
|
|
5508
|
-
return
|
|
5556
|
+
return nameParts([...after ? dropTail(label, after.length) : label, textPart(` (${word})${after}`)]);
|
|
5509
5557
|
}
|
|
5510
5558
|
var ELEMENT_SEPARATORS = /(?<!\d)\.|\.(?!\d)|[_-]+/g;
|
|
5511
5559
|
var elementWords = (testId) => testId.replace(ELEMENT_SEPARATORS, " ").replace(/\s+/g, " ").trim();
|
|
5512
|
-
|
|
5560
|
+
var placeLabel = (id, screen) => namePartsText(placeLabelParts(id, screen));
|
|
5561
|
+
function placeLabelParts(id, screen) {
|
|
5513
5562
|
if (id === "flow")
|
|
5514
|
-
return PLACE_KIND_WORDS.journey;
|
|
5563
|
+
return plainParts(PLACE_KIND_WORDS.journey);
|
|
5515
5564
|
const ref = placeRef(id);
|
|
5516
5565
|
const after = ref.segment === "after3s" ? ` · ${PLACE_KIND_WORDS.after3s}` : "";
|
|
5517
|
-
const shown =
|
|
5566
|
+
const shown = placeIdShownNameParts(ref.stem);
|
|
5518
5567
|
if (shown !== null)
|
|
5519
|
-
return isLibraryPlace(ref.stem) ?
|
|
5568
|
+
return nameParts([...isLibraryPlace(ref.stem) ? [] : [textPart(`${KIND_WORD[ref.kind]} · `)], ...shown, textPart(after)]);
|
|
5520
5569
|
if (ref.kind === "press" || ref.kind === "drag" || ref.kind === "step") {
|
|
5521
5570
|
const element = elementWords(ref.stem.slice(ref.stem.indexOf(":") + 1).replace(OPEN_MARK, ""));
|
|
5522
|
-
return `${KIND_WORD[ref.kind]} · “${element}”${screen ? ` · ${screen}` : ""}${after}
|
|
5571
|
+
return plainParts(`${KIND_WORD[ref.kind]} · “${element}”${screen ? ` · ${screen}` : ""}${after}`);
|
|
5523
5572
|
}
|
|
5524
5573
|
const name = windowName(ref.stem);
|
|
5525
5574
|
const colon = name.indexOf(": ");
|
|
5526
|
-
return `${colon > 0 && ref.kind !== "other" ? `${name.slice(0, colon)} · ${name.slice(colon + 2)}` : name}${after}
|
|
5575
|
+
return plainParts(`${colon > 0 && ref.kind !== "other" ? `${name.slice(0, colon)} · ${name.slice(colon + 2)}` : name}${after}`);
|
|
5527
5576
|
}
|
|
5528
5577
|
// packages/present/dist/src/placeNames.js
|
|
5529
5578
|
function shortestPaths(files) {
|
|
@@ -5542,42 +5591,28 @@ function shortestPaths(files) {
|
|
|
5542
5591
|
}
|
|
5543
5592
|
return out;
|
|
5544
5593
|
}
|
|
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
|
-
}
|
|
5594
|
+
// packages/present/dist/src/words.js
|
|
5595
|
+
var REASON_WORDS = {
|
|
5596
|
+
"no-baseline": "there are no main runs on this phone and journey yet",
|
|
5597
|
+
"too-few-runs": "there are too few valid runs on a side",
|
|
5598
|
+
"judged-measures-missing": "the judged measures are missing on a side",
|
|
5599
|
+
"measured-differently": "the sides were measured differently",
|
|
5600
|
+
"runs-unknown": "a change that would count has no value in some runs, so it cannot be confirmed in every run",
|
|
5601
|
+
"facts-version-mismatch": "the sides were derived with different versions",
|
|
5602
|
+
"not-judged-on-simulators": "timing is not judged on simulators",
|
|
5603
|
+
"platforms-differ": "the sides ran on different platforms (iOS and Android are never compared)",
|
|
5604
|
+
"not-enough-main": "main does not have enough sessions on this phone and journey yet"
|
|
5580
5605
|
};
|
|
5606
|
+
var waitingWords = (runs) => `${runs} main ${runs === 1 ? "run waits" : "runs wait"} for a reprocess`;
|
|
5607
|
+
var AT_MOST = "≤ ";
|
|
5608
|
+
var atMost = (text, isBound) => isBound ? `${AT_MOST}${text}` : text;
|
|
5609
|
+
var AT_LEAST = "≥ ";
|
|
5610
|
+
var atLeast = (text, isBound) => isBound ? `${AT_LEAST}${text}` : text;
|
|
5611
|
+
|
|
5612
|
+
// packages/present/dist/src/shown.js
|
|
5613
|
+
var feltGainText = (ms) => ms && ms > 0 ? `felt gain −${fieldValueText("jank_ms", ms)}` : null;
|
|
5614
|
+
var feltCostText = (ms) => ms && ms > 0 ? `felt cost +${fieldValueText("jank_ms", ms)}` : null;
|
|
5615
|
+
|
|
5581
5616
|
// packages/present/dist/src/stability.js
|
|
5582
5617
|
var UNSTABLE_WORDS = {
|
|
5583
5618
|
short: "main varies too much here",
|
|
@@ -5724,7 +5759,7 @@ var READ_USAGE = {
|
|
|
5724
5759
|
page: "--page must be a whole number from 1; use it only with places or compare.",
|
|
5725
5760
|
all: "--all is for places or compare; do not combine it with --page, --offset or --short.",
|
|
5726
5761
|
detailFlag: " --detail (places: include recorded run detail; also included by --verbose or component paging)",
|
|
5727
|
-
detail: "Include recorded per-run component and window detail.",
|
|
5762
|
+
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
5763
|
compareTop: "--top-components must be a whole number from 1 to 200 for compare.",
|
|
5729
5764
|
top: "--top-components must be a whole number from 0 to 20, or from 1 to 200 with --page or --all.",
|
|
5730
5765
|
paging: "The API did not advance the component page; stopped without claiming the list is complete.",
|
|
@@ -5734,23 +5769,119 @@ var READ_USAGE = {
|
|
|
5734
5769
|
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
5770
|
var notMeasuredText = (side) => `${READ_WORDS.notMeasured} in run ${side.toUpperCase()}`;
|
|
5736
5771
|
|
|
5737
|
-
// packages/present/dist/src/
|
|
5738
|
-
var
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5772
|
+
// packages/present/dist/src/problems.js
|
|
5773
|
+
var FAR_OVER_BUDGET_WORD = "Far over budget";
|
|
5774
|
+
var heaviestWords = (name, value) => [textPart(`${HEAVIEST_WORD}: `), componentPart(name), ...value ? [textPart(` ${value}`)] : []];
|
|
5775
|
+
var HEAVIEST_WORD = "heaviest";
|
|
5776
|
+
var PROBLEM_TAG_WORDS = { new: NEW_IN_PR_CHIP, main: "Also on main", none: NOT_COMPARED_WORD };
|
|
5777
|
+
var PROBLEM_TAG_LINE_WORDS = { new: "new in this PR", main: "also on main", none: "not compared with main" };
|
|
5778
|
+
var problems = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "problem" : "problems"}`;
|
|
5779
|
+
var places = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "place" : "places"}`;
|
|
5780
|
+
var problemsAtPlaces = (n, at) => `${problems(n)} at ${places(at)}`;
|
|
5781
|
+
function problemCountsText(c, own) {
|
|
5782
|
+
const tags = c.tags;
|
|
5783
|
+
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(", ")}` : "";
|
|
5784
|
+
return `${problemsAtPlaces(c.count, c.places)} in ${own} own runs${split}${c.far_count ? `; ${c.far_count} of them ${FAR_OVER_BUDGET_WORD.toLowerCase()}` : ""}`;
|
|
5785
|
+
}
|
|
5786
|
+
var newProblems = (n) => `${n.toLocaleString("en-US")} new ${n === 1 ? "problem" : "problems"}`;
|
|
5787
|
+
var PROBLEMS_BLOCK_WORDS = {
|
|
5788
|
+
branch: {
|
|
5789
|
+
heading: "New problems in this branch",
|
|
5790
|
+
sub: "too slow on their own, whatever main does; they don’t block the merge",
|
|
5791
|
+
seeAll: (n) => `See all ${newProblems(n)}`,
|
|
5792
|
+
alsoLine: (n, at) => `${PROBLEM_TAG_WORDS.main}: ${problemsAtPlaces(n, at)}`,
|
|
5793
|
+
notComparedLine: (n, at) => `${PROBLEM_TAG_WORDS.none}: ${problemsAtPlaces(n, at)}`,
|
|
5794
|
+
choices: { new: PROBLEM_TAG_WORDS.new, also: PROBLEM_TAG_WORDS.main, not_compared: PROBLEM_TAG_WORDS.none },
|
|
5795
|
+
panelTitle: "Problems in this branch",
|
|
5796
|
+
panelNote: "Each one is too slow in this branch’s own runs, whatever main does. Problems never block.",
|
|
5797
|
+
filter: "Which problems"
|
|
5798
|
+
},
|
|
5799
|
+
main: {
|
|
5800
|
+
heading: "New problems in this session",
|
|
5801
|
+
sub: "too slow on their own, whatever earlier sessions did",
|
|
5802
|
+
seeAll: (n) => `See all ${newProblems(n)}`,
|
|
5803
|
+
alsoLine: (n, at) => `Also in earlier sessions: ${problemsAtPlaces(n, at)}`,
|
|
5804
|
+
notComparedLine: (n, at) => `Not compared with earlier sessions: ${problemsAtPlaces(n, at)}`,
|
|
5805
|
+
choices: { new: "New in this session", also: "Also in earlier sessions", not_compared: "Not compared with earlier sessions" },
|
|
5806
|
+
panelTitle: "Problems in this session",
|
|
5807
|
+
panelNote: "Each one is too slow in this session’s own runs, whatever earlier sessions did. Problems never block.",
|
|
5808
|
+
filter: "Which problems"
|
|
5809
|
+
}
|
|
5748
5810
|
};
|
|
5749
|
-
|
|
5750
|
-
var
|
|
5751
|
-
|
|
5752
|
-
var
|
|
5753
|
-
var
|
|
5811
|
+
// packages/present/dist/src/place-tier.js
|
|
5812
|
+
var slowerCountsText = (n, sep = " · ") => [n.worse ? `${n.worse} ${STATE_WORDS.worse.toLowerCase()}` : "", n.slight ? `${n.slight} ${STATE_WORDS.warning.toLowerCase()}` : ""].filter(Boolean).join(sep);
|
|
5813
|
+
// packages/present/dist/src/verdict-section.js
|
|
5814
|
+
var places2 = (n) => `${n.toLocaleString("en-US")} ${n === 1 ? "place" : "places"}`;
|
|
5815
|
+
var of = (n, total) => `${n.toLocaleString("en-US")} of ${places2(total)}`;
|
|
5816
|
+
var runs = (n) => `${n} valid ${n === 1 ? "run" : "runs"}`;
|
|
5817
|
+
var VERDICT_SECTION_WORDS = {
|
|
5818
|
+
slower: (n, total, slight) => `${slight ? "A little slower" : "Slower"} at ${of(n, total)}`,
|
|
5819
|
+
blocksMerge: "blocks the merge",
|
|
5820
|
+
faster: (n, total) => `Faster at ${of(n, total)}, slower at none`,
|
|
5821
|
+
mixed: (slower, faster, total) => `Slower at ${of(slower, total)}, faster at ${faster.toLocaleString("en-US")}`,
|
|
5822
|
+
unpublishedSdk: "ran an unpublished SDK build",
|
|
5823
|
+
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)})`,
|
|
5824
|
+
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`,
|
|
5825
|
+
tooFewRuns: (valid, needed) => `Not compared: ${runs(valid)} of ${needed} needed`,
|
|
5826
|
+
tooFewMainRuns: (valid, needed) => `Not compared: main has ${runs(valid)} of ${needed} needed`,
|
|
5827
|
+
tooFewBefore: (valid) => `Not compared: the sessions before have only ${runs(valid)}`,
|
|
5828
|
+
noBaseline: { branch: "Nothing to compare with: no main runs on this phone yet", main: "Nothing to compare with: no earlier sessions on this phone" },
|
|
5829
|
+
noisy: "Not judged: main’s own runs vary too much at every place here",
|
|
5830
|
+
versionMismatch: "Not compared: the sides were computed with different analysis versions",
|
|
5831
|
+
unconfirmed: (n) => `Can’t confirm ${places2(n)}: a run has no value there`,
|
|
5832
|
+
notCompared: (reason) => `Not compared: ${reason}`,
|
|
5833
|
+
notJudgedYet: (why) => `Not judged yet: ${why} on this phone`,
|
|
5834
|
+
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`,
|
|
5835
|
+
doesNotBlock: "doesn’t block",
|
|
5836
|
+
fixes: {
|
|
5837
|
+
branch: {
|
|
5838
|
+
"no-baseline": "Add this journey on this phone to main’s nightly runs.",
|
|
5839
|
+
"too-few-runs": "Run this branch again on this phone.",
|
|
5840
|
+
"judged-measures-missing": "Record the judged measures, then run again.",
|
|
5841
|
+
"runs-unknown": "Run this branch again; a full set of runs decides it.",
|
|
5842
|
+
"facts-version-mismatch": "Recompute both sides in Setup; no new runs are needed.",
|
|
5843
|
+
"platforms-differ": "Add this phone’s platform to main’s nightly runs."
|
|
5844
|
+
},
|
|
5845
|
+
main: {
|
|
5846
|
+
"no-baseline": "Nothing to do: main’s next 3 nightly sessions make a result possible.",
|
|
5847
|
+
"too-few-runs": "Main’s next nightly session decides it.",
|
|
5848
|
+
"runs-unknown": "Main’s next nightly session decides it.",
|
|
5849
|
+
"judged-measures-missing": "Record the judged measures on main; its next nightly session is compared.",
|
|
5850
|
+
"facts-version-mismatch": "Recompute both sides in Setup; no new runs are needed."
|
|
5851
|
+
}
|
|
5852
|
+
},
|
|
5853
|
+
waitForMain: { branch: "Nothing to do on this branch; main’s next nightly session decides it.", main: "Main’s next nightly session decides it." },
|
|
5854
|
+
sides: {
|
|
5855
|
+
branch: { left: "Main", right: "This branch", rightShort: "Branch" },
|
|
5856
|
+
main: { left: "Sessions before", right: "Newest session", rightShort: "Newest" },
|
|
5857
|
+
runSet: { left: "Main", right: "This run set", rightShort: "Run set" }
|
|
5858
|
+
},
|
|
5859
|
+
branchSides: (name) => ({ left: "Main", right: name || "This branch", rightShort: "Branch" }),
|
|
5860
|
+
inProgress: (arrived, expected) => `${arrived} of ${expected} runs arrived; the result comes with the last one`,
|
|
5861
|
+
whatChangedLabel: "What changed",
|
|
5862
|
+
moreSlower: (n) => `+${n.toLocaleString("en-US")} more slower`,
|
|
5863
|
+
slowerPanel: (n) => `${places2(n)} slower`,
|
|
5864
|
+
forInformation: "for information",
|
|
5865
|
+
allFilter: "All",
|
|
5866
|
+
filter: {
|
|
5867
|
+
label: "Show places",
|
|
5868
|
+
choice: (tierWord) => upperFirst(tierWord),
|
|
5869
|
+
showing: (choice, n) => `Showing ${choice.toLowerCase()}: ${n}`
|
|
5870
|
+
},
|
|
5871
|
+
countsLabel: "Places by result",
|
|
5872
|
+
links: {
|
|
5873
|
+
openChart: "Open chart",
|
|
5874
|
+
seeAll: (n) => `See all ${places2(n)}`,
|
|
5875
|
+
whichPlaces: "Which places",
|
|
5876
|
+
whichChanges: "Which changes",
|
|
5877
|
+
howToFix: "How to fix",
|
|
5878
|
+
whatDiffers: "What differs",
|
|
5879
|
+
whatChanged: "What changed",
|
|
5880
|
+
openSetup: "Open setup",
|
|
5881
|
+
openScreen: "Open screen"
|
|
5882
|
+
}
|
|
5883
|
+
};
|
|
5884
|
+
|
|
5754
5885
|
// packages/present/dist/src/main-history.js
|
|
5755
5886
|
var MAIN_TIER_WORDS = {
|
|
5756
5887
|
same: "In norm",
|
|
@@ -5832,74 +5963,12 @@ function mainCutWords(v) {
|
|
|
5832
5963
|
return parts.length ? parts.join(" ") : "Measures alike; no baseline cut.";
|
|
5833
5964
|
}
|
|
5834
5965
|
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
|
-
blocksMerge: "blocks the merge",
|
|
5842
|
-
faster: (n, total) => `Faster at ${of(n, total)}, slower at none`,
|
|
5843
|
-
mixed: (slower, faster, total) => `Slower at ${of(slower, total)}, faster at ${faster.toLocaleString("en-US")}`,
|
|
5844
|
-
unpublishedSdk: "ran an unpublished SDK build",
|
|
5845
|
-
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)})`,
|
|
5846
|
-
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`,
|
|
5847
|
-
tooFewRuns: (valid, needed) => `Not compared: ${runs(valid)} of ${needed} needed`,
|
|
5848
|
-
tooFewMainRuns: (valid, needed) => `Not compared: main has ${runs(valid)} of ${needed} needed`,
|
|
5849
|
-
tooFewBefore: (valid) => `Not compared: the sessions before have only ${runs(valid)}`,
|
|
5850
|
-
noBaseline: { branch: "Nothing to compare with: no main runs on this phone yet", main: "Nothing to compare with: no earlier sessions on this phone" },
|
|
5851
|
-
noisy: "Not judged: main’s own runs vary too much at every place here",
|
|
5852
|
-
versionMismatch: "Not compared: the sides were computed with different analysis versions",
|
|
5853
|
-
unconfirmed: (n) => `Can’t confirm ${places2(n)}: a run has no value there`,
|
|
5854
|
-
notCompared: (reason) => `Not compared: ${reason}`,
|
|
5855
|
-
notJudgedYet: (why) => `Not judged yet: ${why} on this phone`,
|
|
5856
|
-
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`,
|
|
5857
|
-
doesNotBlock: "doesn’t block",
|
|
5858
|
-
fixes: {
|
|
5859
|
-
branch: {
|
|
5860
|
-
"no-baseline": "Add this journey on this phone to main’s nightly runs.",
|
|
5861
|
-
"too-few-runs": "Run this branch again on this phone.",
|
|
5862
|
-
"judged-measures-missing": "Record the judged measures, then run again.",
|
|
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
|
-
}
|
|
5966
|
+
var MEMORY_HELD_WORDS = { head: "Memory", rest: "still held after the journey", text: "Memory still held after the journey" };
|
|
5967
|
+
var MAIN_CHART_TITLES = {
|
|
5968
|
+
renders: "Places vs their normal range",
|
|
5969
|
+
rendersTip: (baseline) => `Share of the journey’s places per session, by result against main’s ${baseline} sessions before it. Dashed: not judged yet, while main collects ${baseline} sessions on the setup (after main’s start or a change).`,
|
|
5970
|
+
memory: `${MEMORY_HELD_WORDS.text} · MB`,
|
|
5971
|
+
memoryTip: "Memory kept by the journey: what it still holds after the clean-up at its end. Line: the median of a session; band: the spread inside it."
|
|
5903
5972
|
};
|
|
5904
5973
|
// packages/present/dist/src/summary-message.js
|
|
5905
5974
|
var SUMMARY_WORDS = {
|
|
@@ -5916,6 +5985,9 @@ var SUMMARY_WORDS = {
|
|
|
5916
5985
|
measuredDifferently: "measured differently",
|
|
5917
5986
|
namesChanged: (sdk, mainSessions, needed) => `Main: ${mainSessions} of ${needed} sessions on SDK ${sdkTag(sdk)}`,
|
|
5918
5987
|
morePlaces: (n) => `+${n} more ${n === 1 ? "place" : "places"} changed`,
|
|
5988
|
+
slowerPlaces: (n, split) => `${n} ${n === 1 ? "place" : "places"} slower${split && slowerCountsText(split) ? `: ${slowerCountsText(split, ", ")}` : ""}`,
|
|
5989
|
+
noPlaceSlower: "no place slower",
|
|
5990
|
+
toCheck: (n) => `${n} ${n === 1 ? "component" : "components"} to check`,
|
|
5919
5991
|
compared: {
|
|
5920
5992
|
branch: (sessions, days) => `vs main’s last ${sessions}${days}`,
|
|
5921
5993
|
main: (sessions, days) => `newest session vs main’s ${sessions} before${days}`,
|
|
@@ -5932,7 +6004,7 @@ var SUMMARY_WORDS = {
|
|
|
5932
6004
|
runSet: "Open the run set"
|
|
5933
6005
|
}
|
|
5934
6006
|
};
|
|
5935
|
-
var ORDER = [
|
|
6007
|
+
var ORDER = [...STATE_ORDER.slice(0, 3), "not-computed", ...STATE_ORDER.slice(3)];
|
|
5936
6008
|
var rank = (l) => ORDER.indexOf(l.state);
|
|
5937
6009
|
var WORD = { ...STATE_WORDS, "not-computed": SUMMARY_WORDS.notComputed };
|
|
5938
6010
|
var shownLane = (lanes) => [...lanes].sort((a, b) => rank(a) - rank(b) || b.places - a.places)[0];
|
|
@@ -5957,6 +6029,17 @@ var changeLine = (c, tier) => {
|
|
|
5957
6029
|
const values = c.main !== null && c.now !== null ? `${fromTo(c.main, c.now)}${c.change ? ` (${c.change})` : ""}` : c.change;
|
|
5958
6030
|
return [c.place ?? upper(c.measure), ...c.place ? [c.measure] : [], values, WORD[c.tier ?? tier]].filter(Boolean).join(" · ");
|
|
5959
6031
|
};
|
|
6032
|
+
function tierSplit(l) {
|
|
6033
|
+
const worse = new Set, all = new Set;
|
|
6034
|
+
for (const c of l.changes) {
|
|
6035
|
+
const key = c.key ?? c.place ?? c.measure;
|
|
6036
|
+
all.add(key);
|
|
6037
|
+
if ((c.tier ?? l.state) === "worse")
|
|
6038
|
+
worse.add(key);
|
|
6039
|
+
}
|
|
6040
|
+
const split = { worse: worse.size, slight: all.size - worse.size };
|
|
6041
|
+
return l.state === "worse" && !split.slight || l.state === "warning" && !split.worse ? undefined : split;
|
|
6042
|
+
}
|
|
5960
6043
|
var wordReason = (word) => word?.includes(": ") ? word.slice(word.indexOf(": ") + 2) : null;
|
|
5961
6044
|
var notJudgedWhy = (l) => l.state === "not-computed" && l.word?.startsWith(`${NOT_JUDGED_WORD}: `) ? upper(l.word.slice(NOT_JUDGED_WORD.length + 2)) : null;
|
|
5962
6045
|
function why(l) {
|
|
@@ -6019,11 +6102,41 @@ function summaryMessage(input) {
|
|
|
6019
6102
|
input.pr && input.prNumber ? `${input.branch} (#${input.prNumber})` : input.branch,
|
|
6020
6103
|
phones === 1 ? l.phone ?? "unknown phone" : SUMMARY_WORDS.phones(phones)
|
|
6021
6104
|
].join(" · ");
|
|
6105
|
+
const label = input.pr ? SUMMARY_WORDS.link.comparison : SUMMARY_WORDS.link.session;
|
|
6106
|
+
const link = { label, url: (single ? l.url : null) ?? input.url };
|
|
6107
|
+
if (!input.slack)
|
|
6108
|
+
return { state: l.state, newestSlower, lead, ...withLines(changeLines(l, input.pr, newestSlower)), link };
|
|
6022
6109
|
const lines = [];
|
|
6023
|
-
|
|
6110
|
+
if (l.state === "worse" || l.state === "warning") {
|
|
6111
|
+
const n = l.places || l.changes.length;
|
|
6112
|
+
lines.push(upper([...n ? [SUMMARY_WORDS.slowerPlaces(n, tierSplit(l))] : [], feltCostText(l.feltCostMs)].filter(Boolean).join(" · ") || why(l)));
|
|
6113
|
+
} else if (l.state === "no-change")
|
|
6114
|
+
lines.push(upper(SUMMARY_WORDS.noPlaceSlower));
|
|
6115
|
+
else if (l.state === "cant-tell") {
|
|
6116
|
+
const fix = unblock(l, input.pr);
|
|
6117
|
+
lines.push(fix ? SUMMARY_WORDS.unblock(why(l), fix) : why(l));
|
|
6118
|
+
} else if (l.state === "better")
|
|
6119
|
+
lines.push([why(l), feltGainText(l.feltGainMs)].filter(Boolean).join(" · "));
|
|
6120
|
+
else
|
|
6121
|
+
lines.push(why(l));
|
|
6122
|
+
if (l.toCheckCount)
|
|
6123
|
+
lines.push(upper(SUMMARY_WORDS.toCheck(l.toCheckCount)));
|
|
6124
|
+
const basis = compared(l, input.pr, false);
|
|
6024
6125
|
const extra = conditions(l, input.pr).join(", ");
|
|
6126
|
+
const heat = l.heatedRuns ? SUMMARY_WORDS.heated(l.heatedRuns, l.runs) : null;
|
|
6127
|
+
const context = [basis ? `${basis}${extra ? `; ${extra}` : ""}` : extra, heat].filter(Boolean).join(" · ");
|
|
6128
|
+
if (context)
|
|
6129
|
+
lines.push(basis ? context : upper(context));
|
|
6130
|
+
return { state: l.state, newestSlower, lead, ...withLines(lines), link };
|
|
6131
|
+
}
|
|
6132
|
+
function changeLines(l, pr, newestSlower) {
|
|
6133
|
+
const lines = [];
|
|
6134
|
+
const basis = compared(l, pr, newestSlower);
|
|
6135
|
+
const extra = conditions(l, pr).join(", ");
|
|
6025
6136
|
const heat = l.heatedRuns ? upper(SUMMARY_WORDS.heated(l.heatedRuns, l.runs)) : null;
|
|
6026
|
-
const
|
|
6137
|
+
const felt = l.state === "worse" || l.state === "warning" ? feltCostText(l.feltCostMs) : l.state === "better" ? feltGainText(l.feltGainMs) : null;
|
|
6138
|
+
const sums = upper([felt, l.toCheckCount ? SUMMARY_WORDS.toCheck(l.toCheckCount) : null].filter(Boolean).join(" · "));
|
|
6139
|
+
const tail = [...sums ? [sums] : [], ...basis || extra ? [basis ? `${basis}${extra ? `; ${extra}` : ""}` : upper(extra)] : [], ...heat ? [heat] : []];
|
|
6027
6140
|
if (l.state === "worse" || l.state === "warning" || l.state === "better") {
|
|
6028
6141
|
const seen = new Set;
|
|
6029
6142
|
const places = l.changes.filter((c) => {
|
|
@@ -6039,12 +6152,11 @@ function summaryMessage(input) {
|
|
|
6039
6152
|
if (top.length && more > 0)
|
|
6040
6153
|
lines.push(SUMMARY_WORDS.morePlaces(more));
|
|
6041
6154
|
} else {
|
|
6042
|
-
const fix = l.state === "cant-tell" ? unblock(l,
|
|
6155
|
+
const fix = l.state === "cant-tell" ? unblock(l, pr) : null;
|
|
6043
6156
|
lines.push(fix ? SUMMARY_WORDS.unblock(why(l), fix) : why(l));
|
|
6044
6157
|
}
|
|
6045
6158
|
lines.push(...tail);
|
|
6046
|
-
|
|
6047
|
-
return { state: l.state, newestSlower, lead, ...withLines(lines), link: { label, url: (single ? l.url : null) ?? input.url } };
|
|
6159
|
+
return lines;
|
|
6048
6160
|
}
|
|
6049
6161
|
var withLines = (lines) => ({ lines, detail: lines.join(`
|
|
6050
6162
|
`) });
|
|
@@ -6142,13 +6254,16 @@ function laneOf(v, sc, set) {
|
|
|
6142
6254
|
namesChanged: sc.context?.namesChanged ?? null,
|
|
6143
6255
|
url: linkOf(sc, set),
|
|
6144
6256
|
main: sc.main ?? null,
|
|
6145
|
-
reason: sc.headline?.reason ?? sc.headline?.reasons?.[0] ?? null
|
|
6257
|
+
reason: sc.headline?.reason ?? sc.headline?.reasons?.[0] ?? null,
|
|
6258
|
+
feltCostMs: sc.headline?.felt_cost_ms ?? null,
|
|
6259
|
+
feltGainMs: sc.headline?.felt_gain_ms ?? null,
|
|
6260
|
+
toCheckCount: sc.headline?.to_check_count ?? null
|
|
6146
6261
|
};
|
|
6147
6262
|
}
|
|
6148
6263
|
var isPending = (s) => lanesOf(s).every((l) => l.state === "not-computed" && !l.word?.startsWith(`${NOT_JUDGED_WORD}: `));
|
|
6149
6264
|
var lanesOf = (s) => s.verdicts ? s.verdicts.scenarios.map((sc) => laneOf(s.verdicts, sc, s)) : [];
|
|
6150
6265
|
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 });
|
|
6266
|
+
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
6267
|
var MESSAGE_COLORS = { error: "#D6334A", warn: "#C47A00", better: "#238359", none: "#7C8492" };
|
|
6153
6268
|
function messageTone(s) {
|
|
6154
6269
|
const lanes = s ? lanesOf(s) : [];
|
|
@@ -6188,9 +6303,9 @@ function resultSlackPayload(s, o = {}) {
|
|
|
6188
6303
|
const moved = lanes.some((l) => l.state === "worse" || l.state === "warning");
|
|
6189
6304
|
const heat = lanes.some((l) => l.heatedRuns > 0);
|
|
6190
6305
|
const improved = lanes.some((l) => l.state === "better") || s.verdicts.state === "better";
|
|
6191
|
-
if (isPr(s)
|
|
6306
|
+
if (!isPr(s) && !moved && !improved && !heat && !o.always)
|
|
6192
6307
|
return null;
|
|
6193
|
-
return slackPayload(runSetMessage(s), messageTone(s));
|
|
6308
|
+
return slackPayload(runSetMessage(s, { slack: true }), messageTone(s));
|
|
6194
6309
|
}
|
|
6195
6310
|
var noResultSlackPayload = (branch, reason, url) => slackPayload(noResultMessage(branch, reason, url), { mark: "⚪", color: MESSAGE_COLORS.none });
|
|
6196
6311
|
var markdown = (m, mark) => `### ${mark} ${m.lead}
|
|
@@ -10589,7 +10704,7 @@ async function aa(deps, outDir, opts = {}) {
|
|
|
10589
10704
|
}
|
|
10590
10705
|
|
|
10591
10706
|
// apps/agent/src/farm.ts
|
|
10592
|
-
import { dirname as
|
|
10707
|
+
import { dirname as dirname4, join as join13, resolve as resolve6 } from "node:path";
|
|
10593
10708
|
|
|
10594
10709
|
// packages/appium-driver/dist/src/links.js
|
|
10595
10710
|
var ALERT_POLL_MS = 250;
|
|
@@ -10893,7 +11008,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
10893
11008
|
import { dirname as dirname2, join as join11, relative as relative2, resolve as resolve4 } from "node:path";
|
|
10894
11009
|
import { fileURLToPath } from "node:url";
|
|
10895
11010
|
function driverVersion() {
|
|
10896
|
-
const built = "0.1.0-alpha.
|
|
11011
|
+
const built = "0.1.0-alpha.20";
|
|
10897
11012
|
if (built)
|
|
10898
11013
|
return built;
|
|
10899
11014
|
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -10948,6 +11063,11 @@ var SDK_FLUSH_WORST_CASE_MS = 315000;
|
|
|
10948
11063
|
var FLUSH_MARGIN_MS = 30000;
|
|
10949
11064
|
var FLUSH_WAIT_MS = SDK_FLUSH_WORST_CASE_MS + FLUSH_MARGIN_MS;
|
|
10950
11065
|
var FLUSH_STATUSES = new Set(["ok", "error", "off"]);
|
|
11066
|
+
// packages/verdict-core/dist/src/version.generated.js
|
|
11067
|
+
var CORE_FINGERPRINT = "1c8a7ed82956dae0";
|
|
11068
|
+
|
|
11069
|
+
// packages/verdict-core/dist/src/version.js
|
|
11070
|
+
var CORE_VERSION = parseInt(CORE_FINGERPRINT.slice(0, 7), 16);
|
|
10951
11071
|
// packages/verdict-core/dist/src/window-count.js
|
|
10952
11072
|
var WINDOW_DEPENDENT_JOURNEY_MEASURES = ["sampler.cpu_ms", "sampler.frames.longest_gap_ms"];
|
|
10953
11073
|
var OFF = new WeakSet;
|
|
@@ -11225,7 +11345,7 @@ var DRIVER_RELEASES = [
|
|
|
11225
11345
|
{ affects: LOADED_MEMORY }
|
|
11226
11346
|
],
|
|
11227
11347
|
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
|
|
11348
|
+
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
11349
|
},
|
|
11230
11350
|
{
|
|
11231
11351
|
version: "0.1.0-alpha.18",
|
|
@@ -11243,8 +11363,21 @@ var DRIVER_RELEASES = [
|
|
|
11243
11363
|
],
|
|
11244
11364
|
uncalibrated: false,
|
|
11245
11365
|
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)."
|
|
11366
|
+
},
|
|
11367
|
+
{
|
|
11368
|
+
version: "0.1.0-alpha.20",
|
|
11369
|
+
previous: "0.1.0-alpha.19",
|
|
11370
|
+
declares: [
|
|
11371
|
+
{ affects: MEASURES_AT("drag"), places: ["drag"] },
|
|
11372
|
+
{ affects: MEASURES_AT("step"), places: ["step"] },
|
|
11373
|
+
{ affects: MEASURES_AT("journey"), places: ["journey"] }
|
|
11374
|
+
],
|
|
11375
|
+
uncalibrated: false,
|
|
11376
|
+
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.'
|
|
11246
11377
|
}
|
|
11247
11378
|
];
|
|
11379
|
+
var JOURNEY_RELEASES = {};
|
|
11380
|
+
var CAPTURE_MEASURED_WITH = { aspect: "capture", affects: LOADED_MEMORY, marks: true };
|
|
11248
11381
|
var UNSTABLE_NOISY = {
|
|
11249
11382
|
reason: "noisy",
|
|
11250
11383
|
spread: "sessions",
|
|
@@ -11258,7 +11391,6 @@ var UNSTABLE_NOISY = {
|
|
|
11258
11391
|
uncalibrated: false,
|
|
11259
11392
|
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
11393
|
};
|
|
11261
|
-
|
|
11262
11394
|
class ConfigError2 extends Error {
|
|
11263
11395
|
problems;
|
|
11264
11396
|
constructor(problems) {
|
|
@@ -11322,7 +11454,13 @@ function withClauseValues(base) {
|
|
|
11322
11454
|
const valued = (r) => {
|
|
11323
11455
|
if (r.id === "memory_retained")
|
|
11324
11456
|
return { ...r, threshold: [part(memory.value.warn)], uncalibrated: memory.uncalibrated };
|
|
11325
|
-
|
|
11457
|
+
if (r.role === "judged")
|
|
11458
|
+
return judgedByNoise(r);
|
|
11459
|
+
if (r.id in NOISE_FITS) {
|
|
11460
|
+
const { errorNoise: _error, ...shown } = judgedByNoise(r);
|
|
11461
|
+
return shown;
|
|
11462
|
+
}
|
|
11463
|
+
return evidence(r);
|
|
11326
11464
|
};
|
|
11327
11465
|
const rules = base.rules.map(valued);
|
|
11328
11466
|
const frames = (id) => ({ above: { frames: CLAUSES[id].value.frames }, uncalibrated: CLAUSES[id].uncalibrated });
|
|
@@ -11384,9 +11522,20 @@ var MEMORY_RETAINED = {
|
|
|
11384
11522
|
uncalibrated: true,
|
|
11385
11523
|
source: MEMORY_PAIR_SOURCE
|
|
11386
11524
|
};
|
|
11525
|
+
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
11526
|
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)`;
|
|
11527
|
+
var NOISY_WIDEN = {
|
|
11528
|
+
noisyFrom: 1,
|
|
11529
|
+
lineMultiplier: 1,
|
|
11530
|
+
sd: "aa-loo",
|
|
11531
|
+
excludeAbove: null,
|
|
11532
|
+
excludeReturnBelow: null,
|
|
11533
|
+
excludeAboveMs: null,
|
|
11534
|
+
uncalibrated: false,
|
|
11535
|
+
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`
|
|
11536
|
+
};
|
|
11388
11537
|
var SHAPE = {
|
|
11389
|
-
id: "simple-4.
|
|
11538
|
+
id: "simple-4.28+noise95-99",
|
|
11390
11539
|
minRuns: 2,
|
|
11391
11540
|
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
11541
|
repeats: { kind: "every-pr-beyond-every-main" },
|
|
@@ -11408,6 +11557,7 @@ var SHAPE = {
|
|
|
11408
11557
|
uncalibrated: false,
|
|
11409
11558
|
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
11559
|
driverReleases: DRIVER_RELEASES,
|
|
11560
|
+
...Object.keys(JOURNEY_RELEASES).length ? { journeyReleases: JOURNEY_RELEASES } : {},
|
|
11411
11561
|
unknownDiffers: ["journeyVersion"]
|
|
11412
11562
|
},
|
|
11413
11563
|
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 +11565,7 @@ var SHAPE = {
|
|
|
11415
11565
|
{ ...COMMIT_MAX_JUDGED, id: "jank", on: { measure: "jank_ms" }, source: JANK_SOURCE },
|
|
11416
11566
|
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
11567
|
RENDER_JUDGED,
|
|
11418
|
-
COMPONENT_JUDGED,
|
|
11568
|
+
{ ...COMPONENT_JUDGED, role: "evidence", source: FELT_SOURCE },
|
|
11419
11569
|
{ ...evidenceRule("commits", { measure: "commits" }, REACT_PLACES, [{ rel: 0.5, abs: 2 }], "render and commit counts are evidence only (ruling a)"), source: COUNT_SOURCE },
|
|
11420
11570
|
{ ...evidenceRule("renders", { measure: "renders" }, REACT_PLACES, [{ rel: 0.5, abs: 10 }], "render and commit counts are evidence only (ruling a)"), source: COUNT_SOURCE },
|
|
11421
11571
|
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 +11633,11 @@ var SHAPE = {
|
|
|
11483
11633
|
uncalibrated: true,
|
|
11484
11634
|
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
11635
|
},
|
|
11636
|
+
feltCost: {
|
|
11637
|
+
reads: ["jank", "render"],
|
|
11638
|
+
uncalibrated: false,
|
|
11639
|
+
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'
|
|
11640
|
+
},
|
|
11486
11641
|
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
11642
|
notJudgedLabels: ["launch"],
|
|
11488
11643
|
unstable: [{
|
|
@@ -11491,6 +11646,7 @@ var SHAPE = {
|
|
|
11491
11646
|
reads: [COMMIT_MAX_JUDGED, RENDER_JUDGED, COMPONENT_JUDGED, MEMORY_RETAINED],
|
|
11492
11647
|
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
11648
|
}],
|
|
11649
|
+
noisy: NOISY_WIDEN,
|
|
11494
11650
|
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
11651
|
measuredWith: [
|
|
11496
11652
|
{ aspect: "renderer", affects: REACT_MEASURES },
|
|
@@ -11499,7 +11655,8 @@ var SHAPE = {
|
|
|
11499
11655
|
{ aspect: "screenWindowModel", affects: WINDOW_MODEL_AFFECTS, places: ["screen"], marks: true },
|
|
11500
11656
|
{ aspect: "cpuSource", affects: ["sampler.cpu_ms"] },
|
|
11501
11657
|
{ aspect: "causeSplit", affects: REACT_MEASURES, places: REACT_PLACES, marks: true },
|
|
11502
|
-
{ aspect: "endGc", affects: MEMORY_MEASURES }
|
|
11658
|
+
{ aspect: "endGc", affects: MEMORY_MEASURES },
|
|
11659
|
+
CAPTURE_MEASURED_WITH
|
|
11503
11660
|
],
|
|
11504
11661
|
tiers: {
|
|
11505
11662
|
rel: TIER_REL,
|
|
@@ -11523,15 +11680,6 @@ var SHAPE = {
|
|
|
11523
11680
|
}
|
|
11524
11681
|
};
|
|
11525
11682
|
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
11683
|
var P4_EVEN = {
|
|
11536
11684
|
...CONFIG,
|
|
11537
11685
|
id: `${CONFIG.id}+p4-even`,
|
|
@@ -11542,6 +11690,51 @@ var P3_COMMIT = withClauseValues({
|
|
|
11542
11690
|
id: `${CONFIG.id}+p3-commit`,
|
|
11543
11691
|
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
11692
|
});
|
|
11693
|
+
// apps/agent/src/flow-version.ts
|
|
11694
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
11695
|
+
import { dirname as dirname3, relative as relative3, resolve as resolve5, sep as sep2 } from "node:path";
|
|
11696
|
+
var REFERENCE = /^\s*(?:-\s*)?(?:runFlow|runScript|file)\s*:\s*['"]?([^'"\s#{}$]+\.(?:ya?ml|js))['"]?\s*$/gm;
|
|
11697
|
+
var commonDir = (paths) => {
|
|
11698
|
+
let dir = dirname3(paths[0]);
|
|
11699
|
+
while (!paths.every((p) => p === dir || p.startsWith(dir.endsWith(sep2) ? dir : dir + sep2)))
|
|
11700
|
+
dir = dirname3(dir);
|
|
11701
|
+
return dir;
|
|
11702
|
+
};
|
|
11703
|
+
async function flowsVersion(files, read, skip = []) {
|
|
11704
|
+
if (files.length === 0)
|
|
11705
|
+
return null;
|
|
11706
|
+
const roots = files.map((f) => resolve5(f));
|
|
11707
|
+
const skipped = new Set(skip.map((s) => resolve5(s)));
|
|
11708
|
+
const seen = new Map;
|
|
11709
|
+
const visit = async (file, root) => {
|
|
11710
|
+
if (seen.has(file) || skipped.has(file))
|
|
11711
|
+
return true;
|
|
11712
|
+
const text = await read(file).then((t) => t.replace(/\r\n?/g, `
|
|
11713
|
+
`), () => null);
|
|
11714
|
+
if (text === null)
|
|
11715
|
+
return !root;
|
|
11716
|
+
seen.set(file, text);
|
|
11717
|
+
for (const m of text.matchAll(REFERENCE))
|
|
11718
|
+
await visit(resolve5(dirname3(file), m[1]), false);
|
|
11719
|
+
return true;
|
|
11720
|
+
};
|
|
11721
|
+
for (const r of roots)
|
|
11722
|
+
if (!await visit(r, true))
|
|
11723
|
+
return null;
|
|
11724
|
+
const base = commonDir(roots);
|
|
11725
|
+
const keyed = [...seen].map(([file, text]) => [relative3(base, file).split(sep2).join("/"), text]);
|
|
11726
|
+
const hash = createHash2("sha256");
|
|
11727
|
+
for (const [key, text] of keyed.sort((a, b) => a[0] < b[0] ? -1 : 1))
|
|
11728
|
+
hash.update(key).update("\x00").update(text).update("\x00");
|
|
11729
|
+
return hash.digest("hex").slice(0, 12);
|
|
11730
|
+
}
|
|
11731
|
+
async function maestroCliVersion(deps) {
|
|
11732
|
+
const r = await deps.spawner.run("maestro", ["--version"], { timeoutMs: 30000, killAfterMs: 5000 });
|
|
11733
|
+
if (r.code !== 0)
|
|
11734
|
+
return;
|
|
11735
|
+
return /^\s*v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\s*$/m.exec(r.stdout)?.[1];
|
|
11736
|
+
}
|
|
11737
|
+
|
|
11545
11738
|
// apps/agent/src/device-model.ts
|
|
11546
11739
|
import { tmpdir } from "node:os";
|
|
11547
11740
|
import { join as join12 } from "node:path";
|
|
@@ -11684,6 +11877,19 @@ var maestroLinkFields = (sdkArgs = "") => ({ keepsnappyLinkArgs: linkArgs(sdkArg
|
|
|
11684
11877
|
|
|
11685
11878
|
// apps/agent/src/farm.ts
|
|
11686
11879
|
var STATE_VERSION = 1;
|
|
11880
|
+
function plantOf(raw) {
|
|
11881
|
+
let v;
|
|
11882
|
+
try {
|
|
11883
|
+
v = JSON.parse(raw);
|
|
11884
|
+
} catch {
|
|
11885
|
+
return null;
|
|
11886
|
+
}
|
|
11887
|
+
const ok = (p) => {
|
|
11888
|
+
const x = p;
|
|
11889
|
+
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");
|
|
11890
|
+
};
|
|
11891
|
+
return Array.isArray(v) && v.length > 0 && v.length <= 200 && v.every(ok) ? v : null;
|
|
11892
|
+
}
|
|
11687
11893
|
var COMPLETION_MARKER = "run:complete";
|
|
11688
11894
|
var SETUP_FLAGS = [
|
|
11689
11895
|
["driver-version", "driver_version"],
|
|
@@ -11692,19 +11898,26 @@ var SETUP_FLAGS = [
|
|
|
11692
11898
|
["journey-version", "journey_version"],
|
|
11693
11899
|
["flag-set", "flag_set"]
|
|
11694
11900
|
];
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11901
|
+
var LOCAL_PROVIDERS = ["local", "usb", "customer", "simulator", "emulator"];
|
|
11902
|
+
var COMMIT_ID = /^[0-9a-f]{40}$/i;
|
|
11903
|
+
function instrumentFlags(f, maestro = {}) {
|
|
11904
|
+
const given = f["journey-version"]?.trim();
|
|
11905
|
+
const named = given && !COMMIT_ID.test(given) ? given : undefined;
|
|
11906
|
+
if (!f["journey-module"] && f.driver !== "appium") {
|
|
11907
|
+
const driver = f["driver-version"] ?? maestro.maestroVersion;
|
|
11908
|
+
const journey = maestro.flowsVersion ?? named;
|
|
11909
|
+
return { ...driver ? { "driver-version": driver } : {}, ...journey ? { "journey-version": journey } : {} };
|
|
11910
|
+
}
|
|
11698
11911
|
let hash;
|
|
11699
11912
|
if (f["journey-module"]) {
|
|
11700
11913
|
try {
|
|
11701
|
-
hash = journeyVersion(
|
|
11914
|
+
hash = journeyVersion(resolve6(f["journey-module"]));
|
|
11702
11915
|
} catch {
|
|
11703
11916
|
hash = undefined;
|
|
11704
11917
|
}
|
|
11705
11918
|
}
|
|
11706
11919
|
const driver = f["driver-version"] ?? driverVersion() ?? undefined;
|
|
11707
|
-
const journey =
|
|
11920
|
+
const journey = hash ?? named;
|
|
11708
11921
|
return { ...driver ? { "driver-version": driver } : {}, ...journey ? { "journey-version": journey } : {} };
|
|
11709
11922
|
}
|
|
11710
11923
|
var TOKENS_STUB = "// Replaced by `keepsnappy farm open` for one upload. Never commit real tokens.\noutput.keepsnappyTokens = {};\n";
|
|
@@ -11765,7 +11978,8 @@ async function farmJourneys(deps, f, rt, journeys) {
|
|
|
11765
11978
|
if (!byName.has(j))
|
|
11766
11979
|
rt.print(`journey ${j}: no flow file named ${j}.yaml in --config/--flows; sent without a title`);
|
|
11767
11980
|
}
|
|
11768
|
-
|
|
11981
|
+
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 });
|
|
11982
|
+
return { journeys: info, files: files.map((x) => x.file) };
|
|
11769
11983
|
}
|
|
11770
11984
|
async function farmOpen(deps, f, rt) {
|
|
11771
11985
|
const problems = [];
|
|
@@ -11782,7 +11996,7 @@ async function farmOpen(deps, f, rt) {
|
|
|
11782
11996
|
provider = "simulator";
|
|
11783
11997
|
model = model ? withSimulatorWord(model) : simulator;
|
|
11784
11998
|
}
|
|
11785
|
-
if (!model &&
|
|
11999
|
+
if (!model && LOCAL_PROVIDERS.includes(provider)) {
|
|
11786
12000
|
model = await detectDeviceModel(deps, { platform: f.platform, deviceId: f["device-id"]?.trim() }) ?? undefined;
|
|
11787
12001
|
if (model)
|
|
11788
12002
|
rt.print(`device model read from the device: ${model}`);
|
|
@@ -11809,18 +12023,29 @@ async function farmOpen(deps, f, rt) {
|
|
|
11809
12023
|
problems.push("--tokens-js <path> is required (the file the suite overlay puts at flows/keepsnappy/tokens.js)");
|
|
11810
12024
|
if (!f.state)
|
|
11811
12025
|
problems.push("--state <path> is required (read by keepsnappy farm close)");
|
|
12026
|
+
const plant = f.plant === undefined ? undefined : plantOf(f.plant);
|
|
12027
|
+
if (plant === null)
|
|
12028
|
+
problems.push('--plant must be a JSON list of { place, kind, factor > 0, unit: "budget" | "MB" }');
|
|
11812
12029
|
if (problems.length)
|
|
11813
12030
|
throw new ConfigError(problems);
|
|
11814
|
-
const statePath =
|
|
11815
|
-
const tokensPath =
|
|
12031
|
+
const statePath = resolve6(f.state);
|
|
12032
|
+
const tokensPath = resolve6(f["tokens-js"]);
|
|
11816
12033
|
if (await deps.fs.exists(statePath)) {
|
|
11817
12034
|
throw new ConfigError([`${statePath} already exists: close that run set first (keepsnappy farm close --state ${f.state}) or remove the file`]);
|
|
11818
12035
|
}
|
|
11819
12036
|
const api = apiTarget(url, f, rt);
|
|
11820
12037
|
const now = rt.now ?? Date.now;
|
|
11821
12038
|
const device = { model, os: f["device-os"] ?? null, provider };
|
|
11822
|
-
const journeyInfo = await farmJourneys(deps, f, rt, journeys);
|
|
11823
|
-
const
|
|
12039
|
+
const { journeys: journeyInfo, files: flowFiles } = await farmJourneys(deps, f, rt, journeys);
|
|
12040
|
+
const { "driver-version": _driver, "journey-version": given, ...rest } = f;
|
|
12041
|
+
const maestro = !f["journey-module"] && f.driver !== "appium";
|
|
12042
|
+
const flows = maestro ? await flowsVersion(flowFiles, (p) => deps.fs.readFile(p), [tokensPath]) : null;
|
|
12043
|
+
const asked = maestro && !f["driver-version"] && LOCAL_PROVIDERS.includes(provider) ? await maestroCliVersion(deps) : undefined;
|
|
12044
|
+
const instrument = instrumentFlags(f, { ...flows ? { flowsVersion: flows } : {}, ...asked ? { maestroVersion: asked } : {} });
|
|
12045
|
+
if (given?.trim() && given.trim() !== instrument["journey-version"]) {
|
|
12046
|
+
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"}`);
|
|
12047
|
+
}
|
|
12048
|
+
const withInstrument = { ...rest, ...instrument };
|
|
11824
12049
|
const setup = Object.fromEntries(SETUP_FLAGS.flatMap(([flag, key]) => withInstrument[flag]?.trim() ? [[key, withInstrument[flag].trim().slice(0, 200)]] : []));
|
|
11825
12050
|
const base = await baseCommitFor(deps, rt.env, { explicit: f["base-commit"], branch: git.branch, commit: git.commit }, rt.print);
|
|
11826
12051
|
const main = await mainMerges(deps, rt.env, { branch: git.branch, commit: git.commit });
|
|
@@ -11837,7 +12062,13 @@ async function farmOpen(deps, f, rt) {
|
|
|
11837
12062
|
journeys: journeyInfo,
|
|
11838
12063
|
repetitions: repeat,
|
|
11839
12064
|
source: "farm",
|
|
11840
|
-
...f["completion-marker"] || Object.keys(setup).length
|
|
12065
|
+
...f["completion-marker"] || Object.keys(setup).length || plant ? {
|
|
12066
|
+
meta: {
|
|
12067
|
+
...f["completion-marker"] ? { completion_marker: COMPLETION_MARKER } : {},
|
|
12068
|
+
...Object.keys(setup).length ? { setup } : {},
|
|
12069
|
+
...plant ? { plant } : {}
|
|
12070
|
+
}
|
|
12071
|
+
} : {}
|
|
11841
12072
|
}).catch(infra2(`cannot open a run set at ${api.url}`));
|
|
11842
12073
|
const prefix = `${git.commit ? git.commit.slice(0, 12) : "farm"}-${rs.run_set_id.slice(0, 8)}`;
|
|
11843
12074
|
const runs = [];
|
|
@@ -11868,7 +12099,7 @@ async function farmOpen(deps, f, rt) {
|
|
|
11868
12099
|
if (rt.env.GITHUB_ACTIONS === "true")
|
|
11869
12100
|
for (const t of Object.values(tokens).flat())
|
|
11870
12101
|
rt.print(`::add-mask::${t}`);
|
|
11871
|
-
await deps.fs.mkdir(
|
|
12102
|
+
await deps.fs.mkdir(dirname4(tokensPath));
|
|
11872
12103
|
await deps.fs.writeFile(tokensPath, tokensJs(api.url, rs.run_set_id, tokens, maestroLinkFields(f["sdk-args"] ?? "")));
|
|
11873
12104
|
const state = {
|
|
11874
12105
|
version: STATE_VERSION,
|
|
@@ -11888,7 +12119,7 @@ async function farmOpen(deps, f, rt) {
|
|
|
11888
12119
|
closed_at: null,
|
|
11889
12120
|
result: null
|
|
11890
12121
|
};
|
|
11891
|
-
await deps.fs.mkdir(
|
|
12122
|
+
await deps.fs.mkdir(dirname4(statePath));
|
|
11892
12123
|
await deps.fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}
|
|
11893
12124
|
`);
|
|
11894
12125
|
await githubOutputs(deps, rt, {
|
|
@@ -11994,7 +12225,7 @@ async function farmClose(deps, f, rt) {
|
|
|
11994
12225
|
throw new ConfigError(["--state <path> is required (written by keepsnappy farm open)"]);
|
|
11995
12226
|
if (f["fail-on-worse"] && !f.wait)
|
|
11996
12227
|
throw new ConfigError(["--fail-on-worse needs --wait"]);
|
|
11997
|
-
const statePath =
|
|
12228
|
+
const statePath = resolve6(f.state);
|
|
11998
12229
|
let state;
|
|
11999
12230
|
try {
|
|
12000
12231
|
state = JSON.parse(await deps.fs.readFile(statePath));
|
|
@@ -12021,10 +12252,10 @@ async function closeRunSet(deps, f, rt, state, statePath) {
|
|
|
12021
12252
|
const api = apiTarget(f.api ?? f["ingest-url"] ?? state.api, f, rt);
|
|
12022
12253
|
const now = rt.now ?? Date.now;
|
|
12023
12254
|
const sleep = rt.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
12024
|
-
const out =
|
|
12255
|
+
const out = resolve6(f.out ?? dirname4(statePath));
|
|
12025
12256
|
let bsText = null;
|
|
12026
12257
|
if (f["bs-build"])
|
|
12027
|
-
bsText = await deps.fs.readFile(
|
|
12258
|
+
bsText = await deps.fs.readFile(resolve6(f["bs-build"])).catch(() => null);
|
|
12028
12259
|
const session = sessionOutcome(bsText);
|
|
12029
12260
|
const where = `${state.branch}${state.commit ? ` @ ${state.commit.slice(0, 7)}` : ""}`;
|
|
12030
12261
|
rt.print(`KeepSnappy run set ${state.run_set_id} (${where}): closing`);
|
|
@@ -12109,7 +12340,7 @@ async function closeRunSet(deps, f, rt, state, statePath) {
|
|
|
12109
12340
|
// apps/agent/src/farm-run.ts
|
|
12110
12341
|
import { existsSync as existsSync2 } from "node:fs";
|
|
12111
12342
|
import { createRequire } from "node:module";
|
|
12112
|
-
import { dirname as
|
|
12343
|
+
import { dirname as dirname5, join as join16, resolve as resolve7 } from "node:path";
|
|
12113
12344
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12114
12345
|
|
|
12115
12346
|
// apps/agent/src/app-source.ts
|
|
@@ -12382,10 +12613,10 @@ ${r.stderr}`;
|
|
|
12382
12613
|
// apps/agent/src/farm-run.ts
|
|
12383
12614
|
function appiumDriverEntry(o = {}) {
|
|
12384
12615
|
const exists = o.exists ?? existsSync2;
|
|
12385
|
-
const bundled = join16(o.selfDir ??
|
|
12616
|
+
const bundled = join16(o.selfDir ?? dirname5(fileURLToPath2(import.meta.url)), "appium-driver.js");
|
|
12386
12617
|
if (exists(bundled))
|
|
12387
12618
|
return bundled;
|
|
12388
|
-
const pkgDir =
|
|
12619
|
+
const pkgDir = dirname5(o.packageJson ? o.packageJson() : createRequire(import.meta.url).resolve("@keepsnappy/appium-driver/package.json"));
|
|
12389
12620
|
const built = join16(pkgDir, "dist", "src", "cli.js");
|
|
12390
12621
|
return exists(built) ? built : join16(pkgDir, "src", "cli.ts");
|
|
12391
12622
|
}
|
|
@@ -12484,7 +12715,7 @@ function planFarmRun(f) {
|
|
|
12484
12715
|
ports,
|
|
12485
12716
|
env,
|
|
12486
12717
|
driverArgs: f["driver-arg"] ?? [],
|
|
12487
|
-
out:
|
|
12718
|
+
out: resolve7(f.out ?? "farm-run"),
|
|
12488
12719
|
timeoutMinutes,
|
|
12489
12720
|
killAfterSeconds,
|
|
12490
12721
|
pollSeconds
|
|
@@ -12533,7 +12764,7 @@ async function runDriver2(deps, rt, f, p, ports, ctx) {
|
|
|
12533
12764
|
device: p.device,
|
|
12534
12765
|
app: ctx.app,
|
|
12535
12766
|
...f["custom-id"] ? { customId: f["custom-id"] } : {},
|
|
12536
|
-
suiteDir:
|
|
12767
|
+
suiteDir: resolve7(f["suite-dir"]),
|
|
12537
12768
|
tokensPath: f["tokens-path"] ?? "flows/keepsnappy/tokens.js",
|
|
12538
12769
|
tokensJs: ctx.tokensJs,
|
|
12539
12770
|
execute: f.execute,
|
|
@@ -12559,7 +12790,7 @@ async function runDriver2(deps, rt, f, p, ports, ctx) {
|
|
|
12559
12790
|
throw new Error("no Appium driver entry: pass --driver-script");
|
|
12560
12791
|
const args = [
|
|
12561
12792
|
entry,
|
|
12562
|
-
...f["journey-module"] ? ["--journey-module",
|
|
12793
|
+
...f["journey-module"] ? ["--journey-module", resolve7(f["journey-module"])] : [],
|
|
12563
12794
|
"--platform",
|
|
12564
12795
|
p.platform,
|
|
12565
12796
|
"--device",
|
|
@@ -12636,7 +12867,8 @@ async function farmRun(deps, f, rt, ports) {
|
|
|
12636
12867
|
driver: p.driver,
|
|
12637
12868
|
"tokens-js": tokensJs,
|
|
12638
12869
|
state: statePath,
|
|
12639
|
-
...commit ? { commit } : {}
|
|
12870
|
+
...commit ? { commit } : {},
|
|
12871
|
+
...p.driver !== "appium" && !f["driver-version"] && f["maestro-version"] ? { "driver-version": f["maestro-version"] } : {}
|
|
12640
12872
|
}, rt);
|
|
12641
12873
|
if (openCode !== EXIT.ok)
|
|
12642
12874
|
return openCode;
|
|
@@ -12679,15 +12911,15 @@ async function farmRun(deps, f, rt, ports) {
|
|
|
12679
12911
|
}
|
|
12680
12912
|
|
|
12681
12913
|
// apps/agent/src/sourcemaps.ts
|
|
12682
|
-
import { createHash as
|
|
12914
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
12683
12915
|
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
|
|
12916
|
+
import { basename as basename3, join as join17, resolve as resolve8 } from "node:path";
|
|
12685
12917
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
12686
12918
|
import { gzipSync } from "node:zlib";
|
|
12687
12919
|
var MAX_COMPLETE_ROUNDS = 20;
|
|
12688
12920
|
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12689
12921
|
async function prepareComponents(projectRoot, options = {}) {
|
|
12690
|
-
const root =
|
|
12922
|
+
const root = resolve8(projectRoot);
|
|
12691
12923
|
const directory = join17(root, ".keepsnappy", "components");
|
|
12692
12924
|
const configurations = await readdir2(directory, { withFileTypes: true }).catch((error) => {
|
|
12693
12925
|
if (error.code === "ENOENT")
|
|
@@ -12728,7 +12960,7 @@ async function prepareComponents(projectRoot, options = {}) {
|
|
|
12728
12960
|
throw new ConfigError([`invalid KeepSnappy component fragment ${file}`]);
|
|
12729
12961
|
if (!hashes.has(fragment.source)) {
|
|
12730
12962
|
try {
|
|
12731
|
-
hashes.set(fragment.source,
|
|
12963
|
+
hashes.set(fragment.source, createHash3("sha256").update(await readFile4(resolve8(root, fragment.source))).digest("hex"));
|
|
12732
12964
|
} catch (error) {
|
|
12733
12965
|
if (error.code !== "ENOENT")
|
|
12734
12966
|
throw error;
|
|
@@ -12753,7 +12985,7 @@ async function prepareComponents(projectRoot, options = {}) {
|
|
|
12753
12985
|
await rm(temporary, { force: true });
|
|
12754
12986
|
}
|
|
12755
12987
|
const gzip = gzipSync(text);
|
|
12756
|
-
return { file, count: components.length, bytes: Buffer.byteLength(text), gzip, sha256:
|
|
12988
|
+
return { file, count: components.length, bytes: Buffer.byteLength(text), gzip, sha256: createHash3("sha256").update(gzip).digest("hex") };
|
|
12757
12989
|
}
|
|
12758
12990
|
function stripSourcesContent(map) {
|
|
12759
12991
|
if (!isObject(map))
|
|
@@ -12807,7 +13039,7 @@ async function uploadSourcemaps(opts) {
|
|
|
12807
13039
|
const { map, stripped } = stripSourcesContent(parsed);
|
|
12808
13040
|
const text = Buffer.from(JSON.stringify(map));
|
|
12809
13041
|
const gz = gzipSync(text);
|
|
12810
|
-
const sha256 =
|
|
13042
|
+
const sha256 = createHash3("sha256").update(gz).digest("hex");
|
|
12811
13043
|
const name = basename3(file);
|
|
12812
13044
|
const announced = await call(f, opts.url, `/v1/builds/${encodeURIComponent(opts.build)}/sourcemaps`, headers, {
|
|
12813
13045
|
platform: opts.platform,
|
|
@@ -13060,7 +13292,7 @@ function measuredLines(l, r, opts) {
|
|
|
13060
13292
|
}
|
|
13061
13293
|
const lane = (x) => {
|
|
13062
13294
|
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)}` : ""}`;
|
|
13295
|
+
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
13296
|
};
|
|
13065
13297
|
if (m.lanes.length === 1) {
|
|
13066
13298
|
r.both(`${scope}: newest measured ${f.branch ? "" : `on ${n.branch}, `}${lane(n)}; ${plural2(m.run_sets, "run set")} measured.`);
|
|
@@ -13086,6 +13318,7 @@ function measuredLines(l, r, opts) {
|
|
|
13086
13318
|
...m.lanes.length > shown.length ? [plural2(m.lanes.length - shown.length, "lane") + " beyond the eighth"] : []
|
|
13087
13319
|
];
|
|
13088
13320
|
}
|
|
13321
|
+
var feltWords = (x) => `${x.felt_cost ? ` · ${x.felt_cost}` : ""}${x.to_check ? `; ${SUMMARY_WORDS.toCheck(x.to_check)}` : ""}`;
|
|
13089
13322
|
function countsLine2(l) {
|
|
13090
13323
|
const c = l.counts;
|
|
13091
13324
|
const parts = [
|
|
@@ -13141,7 +13374,7 @@ function formatBranchStatus(s, opts = {}) {
|
|
|
13141
13374
|
for (const l of s.lanes) {
|
|
13142
13375
|
const shown = `${l.result === "blocks" ? `${l.word} (blocks)` : badgedWord(l.word, l.blocking)}${l.stale ? ` (${STALE_FLAG})` : ""}`;
|
|
13143
13376
|
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)}` : ""}`);
|
|
13377
|
+
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
13378
|
r.both(...resultContextLines(l.context, opts.verbose, opts.form === "short"));
|
|
13146
13379
|
r.both(...(l.comparison_notes ?? []).map((line) => ` ${line}`));
|
|
13147
13380
|
r.full(...(l.session_speed ?? []).map((line) => ` ${line}`));
|
|
@@ -13211,11 +13444,11 @@ function formatProblemList(l, opts = {}) {
|
|
|
13211
13444
|
r.both(`${staleWords(c.stale_lanes)}: ${c.stale_lanes === 1 ? "its" : "their"} problems are listed, each marked “${STALE_MARK}”.`);
|
|
13212
13445
|
l.problems.forEach((p, i) => {
|
|
13213
13446
|
const keep = i < SHORT_PLACES;
|
|
13214
|
-
r.add(keep, "", `${l.offset + i + 1}. ${p.
|
|
13447
|
+
r.add(keep, "", `${l.offset + i + 1}. ${p.place_name}${opts.verbose ? ` (${p.place})` : ""}: ${p.text}${p.intermittent ? ` — ${p.note}, not counted` : ""}`);
|
|
13215
13448
|
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
13449
|
const top = p.top_components[0];
|
|
13217
13450
|
const topName = componentReadNames(p.top_components)[0];
|
|
13218
|
-
const detail = [p.work, top ?
|
|
13451
|
+
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
13452
|
if (detail.length)
|
|
13220
13453
|
r.full(` ${detail.join("; ")}`);
|
|
13221
13454
|
if (p.url)
|
|
@@ -13958,6 +14191,7 @@ var USAGE = `keepsnappy <command>
|
|
|
13958
14191
|
[--config keepsnappy.yml] [--flows <files|dirs|globs,...>] (journey titles: keepsnappy.yml flows.paths, else the flow's name: / top comment)
|
|
13959
14192
|
[--completion-marker] (the flow posts step run:complete at the end of each iteration; runs without it are invalid)
|
|
13960
14193
|
[--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)
|
|
14194
|
+
[--plant JSON] (what the build plants against main, [{ place, kind, factor, unit: budget|MB }]: the run set's meta.plant)
|
|
13961
14195
|
[--sdk-args k=v&...] (over the run defaults profile=1 diag=1; tokens.js carries them to the flows' activation link)
|
|
13962
14196
|
farm close --state <file> [--wait [--fail-on-worse]] [--bs-build browserstack.ios.json] [--settle-seconds 60] [--max-wait-seconds 300] [--out DIR]
|
|
13963
14197
|
(--fail-on-worse as for run: with no verdict computed on the run set it checks nothing)
|
|
@@ -14053,6 +14287,7 @@ async function main2(argv) {
|
|
|
14053
14287
|
"harness-version": { type: "string" },
|
|
14054
14288
|
"journey-version": { type: "string" },
|
|
14055
14289
|
"flag-set": { type: "string" },
|
|
14290
|
+
plant: { type: "string" },
|
|
14056
14291
|
app: { type: "string" },
|
|
14057
14292
|
driver: { type: "string" },
|
|
14058
14293
|
"tunnel-for": { type: "string", multiple: true },
|
|
@@ -14103,9 +14338,9 @@ async function main2(argv) {
|
|
|
14103
14338
|
if (platform === "ios-device" && !o.device)
|
|
14104
14339
|
throw new ConfigError(["--device <udid> is required for ios-device"]);
|
|
14105
14340
|
const arms = parseArmEnv(o["arm-env"], parseArms(o.arms));
|
|
14106
|
-
const out =
|
|
14341
|
+
const out = resolve9(o.out ?? `keepsnappy-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
14107
14342
|
const udid = o.device ?? "booted";
|
|
14108
|
-
const source = platform === "ios-sim" ? new IosSimulatorDataSource(deps, udid) : new IosDeviceDataSource(deps, udid,
|
|
14343
|
+
const source = platform === "ios-sim" ? new IosSimulatorDataSource(deps, udid) : new IosDeviceDataSource(deps, udid, resolve9(out, ".scratch"));
|
|
14109
14344
|
const installer = platform === "ios-sim" ? new IosSimulatorInstaller(deps, udid) : new IosDeviceInstaller(deps, udid);
|
|
14110
14345
|
const bundleId = await bundleIdOf(deps, arms[0]?.target, o["bundle-id"]);
|
|
14111
14346
|
let markers;
|
|
@@ -14124,7 +14359,7 @@ async function main2(argv) {
|
|
|
14124
14359
|
outDir: out,
|
|
14125
14360
|
testCommand: positionals,
|
|
14126
14361
|
bundleId,
|
|
14127
|
-
...o["ri-capture"] ? { ri: new FileRiSource(deps,
|
|
14362
|
+
...o["ri-capture"] ? { ri: new FileRiSource(deps, resolve9(o["ri-capture"])) } : {},
|
|
14128
14363
|
...o["mem-window"] ? { memWindow: o["mem-window"] } : {},
|
|
14129
14364
|
...o.timeout ? { testTimeoutMs: Number(o.timeout) * 1000 } : {}
|
|
14130
14365
|
});
|
|
@@ -14136,9 +14371,9 @@ async function main2(argv) {
|
|
|
14136
14371
|
const dir = positionals[0];
|
|
14137
14372
|
if (!dir)
|
|
14138
14373
|
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 -> ${
|
|
14374
|
+
const ri = o["ri-capture"] ? new FileRiSource(deps, resolve9(o["ri-capture"])) : undefined;
|
|
14375
|
+
const rows = await extract(deps, resolve9(dir), ri, o["mem-window"], o["clock-offset-ms"] ? Number(o["clock-offset-ms"]) : undefined);
|
|
14376
|
+
console.log(`${rows.length} rows -> ${resolve9(dir, "metrics.csv")}`);
|
|
14142
14377
|
return 0;
|
|
14143
14378
|
}
|
|
14144
14379
|
if (cmd === "aa") {
|
|
@@ -14146,7 +14381,7 @@ async function main2(argv) {
|
|
|
14146
14381
|
if (!dir)
|
|
14147
14382
|
throw new ConfigError(["aa needs <out-dir>"]);
|
|
14148
14383
|
const config = { ...o.reps ? { reps: Number(o.reps) } : {}, ...o.boot ? { nBoot: Number(o.boot) } : {} };
|
|
14149
|
-
console.log((await aa(deps,
|
|
14384
|
+
console.log((await aa(deps, resolve9(dir), { config })).table);
|
|
14150
14385
|
return 0;
|
|
14151
14386
|
}
|
|
14152
14387
|
console.error(USAGE);
|