@a11ign/screenreader-fleet 0.0.0-reserved.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +94 -2
- package/dist/capture-client.d.mts +49 -0
- package/dist/capture-client.d.mts.map +1 -0
- package/dist/capture-client.mjs +352 -0
- package/dist/capture-client.mjs.map +1 -0
- package/dist/check-worker-code.d.mts +34 -0
- package/dist/check-worker-code.d.mts.map +1 -0
- package/dist/check-worker-code.mjs +142 -0
- package/dist/check-worker-code.mjs.map +1 -0
- package/dist/cli-flags.d.mts +71 -0
- package/dist/cli-flags.d.mts.map +1 -0
- package/dist/cli-flags.mjs +207 -0
- package/dist/cli-flags.mjs.map +1 -0
- package/dist/code-drift.d.mts +140 -0
- package/dist/code-drift.d.mts.map +1 -0
- package/dist/code-drift.mjs +284 -0
- package/dist/code-drift.mjs.map +1 -0
- package/dist/command-line-census.d.mts +33 -0
- package/dist/command-line-census.d.mts.map +1 -0
- package/dist/command-line-census.mjs +96 -0
- package/dist/command-line-census.mjs.map +1 -0
- package/dist/compare-workers.d.mts +3 -0
- package/dist/compare-workers.d.mts.map +1 -0
- package/dist/compare-workers.mjs +332 -0
- package/dist/compare-workers.mjs.map +1 -0
- package/dist/control-plane-isolation.d.mts +45 -0
- package/dist/control-plane-isolation.d.mts.map +1 -0
- package/dist/control-plane-isolation.mjs +67 -0
- package/dist/control-plane-isolation.mjs.map +1 -0
- package/dist/deploy-worker.d.mts +3 -0
- package/dist/deploy-worker.d.mts.map +1 -0
- package/dist/deploy-worker.mjs +333 -0
- package/dist/deploy-worker.mjs.map +1 -0
- package/dist/doctor.d.mts +216 -0
- package/dist/doctor.d.mts.map +1 -0
- package/dist/doctor.mjs +962 -0
- package/dist/doctor.mjs.map +1 -0
- package/dist/fleet-consistency.d.mts +235 -0
- package/dist/fleet-consistency.d.mts.map +1 -0
- package/dist/fleet-consistency.mjs +436 -0
- package/dist/fleet-consistency.mjs.map +1 -0
- package/dist/fleet-env.d.mts +228 -0
- package/dist/fleet-env.d.mts.map +1 -0
- package/dist/fleet-env.mjs +509 -0
- package/dist/fleet-env.mjs.map +1 -0
- package/dist/fleet-scripts.d.mts +11 -0
- package/dist/fleet-scripts.d.mts.map +1 -0
- package/dist/fleet-scripts.mjs +41 -0
- package/dist/fleet-scripts.mjs.map +1 -0
- package/dist/git-safe-env.d.mts +10 -0
- package/dist/git-safe-env.d.mts.map +1 -0
- package/dist/git-safe-env.mjs +44 -0
- package/dist/git-safe-env.mjs.map +1 -0
- package/dist/guest-run.d.mts +26 -0
- package/dist/guest-run.d.mts.map +1 -0
- package/dist/guest-run.mjs +164 -0
- package/dist/guest-run.mjs.map +1 -0
- package/dist/host-address.d.mts +33 -0
- package/dist/host-address.d.mts.map +1 -0
- package/dist/host-address.mjs +105 -0
- package/dist/host-address.mjs.map +1 -0
- package/dist/host-capacity.d.mts +64 -0
- package/dist/host-capacity.d.mts.map +1 -0
- package/dist/host-capacity.mjs +152 -0
- package/dist/host-capacity.mjs.map +1 -0
- package/dist/host-metrics.d.mts +116 -0
- package/dist/host-metrics.d.mts.map +1 -0
- package/dist/host-metrics.mjs +201 -0
- package/dist/host-metrics.mjs.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/local-vm.d.ts +125 -0
- package/dist/local-vm.d.ts.map +1 -0
- package/dist/local-vm.js +360 -0
- package/dist/local-vm.js.map +1 -0
- package/dist/measure-guard.d.mts +34 -0
- package/dist/measure-guard.d.mts.map +1 -0
- package/dist/measure-guard.mjs +73 -0
- package/dist/measure-guard.mjs.map +1 -0
- package/dist/normalise-fleet.d.mts +2 -0
- package/dist/normalise-fleet.d.mts.map +1 -0
- package/dist/normalise-fleet.mjs +76 -0
- package/dist/normalise-fleet.mjs.map +1 -0
- package/dist/npm-cli-executable.d.mts +42 -0
- package/dist/npm-cli-executable.d.mts.map +1 -0
- package/dist/npm-cli-executable.mjs +159 -0
- package/dist/npm-cli-executable.mjs.map +1 -0
- package/dist/probe-outcome.d.mts +89 -0
- package/dist/probe-outcome.d.mts.map +1 -0
- package/dist/probe-outcome.mjs +104 -0
- package/dist/probe-outcome.mjs.map +1 -0
- package/dist/protocol-guard.d.mts +34 -0
- package/dist/protocol-guard.d.mts.map +1 -0
- package/dist/protocol-guard.mjs +121 -0
- package/dist/protocol-guard.mjs.map +1 -0
- package/dist/source-walk.d.mts +12 -0
- package/dist/source-walk.d.mts.map +1 -0
- package/dist/source-walk.mjs +56 -0
- package/dist/source-walk.mjs.map +1 -0
- package/dist/transient-fault.d.mts +6 -0
- package/dist/transient-fault.d.mts.map +1 -0
- package/dist/transient-fault.mjs +86 -0
- package/dist/transient-fault.mjs.map +1 -0
- package/dist/utm-deprecated.d.mts +6 -0
- package/dist/utm-deprecated.d.mts.map +1 -0
- package/dist/utm-deprecated.mjs +23 -0
- package/dist/utm-deprecated.mjs.map +1 -0
- package/dist/worker-code-check.d.mts +29 -0
- package/dist/worker-code-check.d.mts.map +1 -0
- package/dist/worker-code-check.mjs +85 -0
- package/dist/worker-code-check.mjs.map +1 -0
- package/dist/worker-health.d.mts +56 -0
- package/dist/worker-health.d.mts.map +1 -0
- package/dist/worker-health.mjs +73 -0
- package/dist/worker-health.mjs.map +1 -0
- package/dist/worker-http.d.mts +103 -0
- package/dist/worker-http.d.mts.map +1 -0
- package/dist/worker-http.mjs +277 -0
- package/dist/worker-http.mjs.map +1 -0
- package/dist/worker-stats.d.mts +66 -0
- package/dist/worker-stats.d.mts.map +1 -0
- package/dist/worker-stats.mjs +143 -0
- package/dist/worker-stats.mjs.map +1 -0
- package/package.json +96 -4
- package/src/local-worker/autounattend.xml +280 -0
- package/src/local-worker/build-vm.sh +218 -0
- package/src/local-worker/clone-worker.sh +141 -0
- package/src/local-worker/create-utm-vm.sh +202 -0
- package/src/local-worker/fetch-windows-iso.sh +238 -0
- package/src/local-worker/first-boot.cmd +58 -0
- package/src/local-worker/worker-ctl.sh +442 -0
- package/src/provisioning/README.md +28 -0
- package/src/provisioning/apply-foreground-lock-timeout.ps1 +71 -0
- package/src/provisioning/bare-metal/README.md +213 -0
- package/src/provisioning/bare-metal/a11y-bootstrap.service +58 -0
- package/src/provisioning/bare-metal/autounattend.xml +428 -0
- package/src/provisioning/bare-metal/serve-bootstrap.sh +86 -0
- package/src/provisioning/bootstrap-control-plane.sh +463 -0
- package/src/provisioning/bootstrap-windows-worker.ps1 +649 -0
- package/src/provisioning/build-lean-worker-image.ps1 +275 -0
- package/src/provisioning/diagnose-nvda-worker.ps1 +174 -0
- package/src/provisioning/provision-nvda-worker.ps1 +827 -0
- package/src/provisioning/set-display-mode.ps1 +411 -0
- package/src/provisioning/stamp-provision-revision.ps1 +184 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `vm_stat`. Pure — the arithmetic is where this goes wrong, not the shelling out.
|
|
3
|
+
*
|
|
4
|
+
* The page size is read from the output rather than assumed: it is 16 KB on Apple Silicon and 4 KB on
|
|
5
|
+
* Intel, and hardcoding 4096 would understate every figure on this machine by a factor of four.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} output
|
|
8
|
+
*/
|
|
9
|
+
export function parseVmStat(output: string): {
|
|
10
|
+
freeMb: number;
|
|
11
|
+
activeMb: number;
|
|
12
|
+
inactiveMb: number;
|
|
13
|
+
wiredMb: number;
|
|
14
|
+
compressorMb: number;
|
|
15
|
+
pageins: number;
|
|
16
|
+
pageouts: number;
|
|
17
|
+
} | null;
|
|
18
|
+
/**
|
|
19
|
+
* Parse `iostat -d -c 2` — the disk columns for each device.
|
|
20
|
+
*
|
|
21
|
+
* The FIRST sample from iostat is an average since boot and is meaningless for a measurement window;
|
|
22
|
+
* callers must use the second. Getting that wrong reports a busy disk as idle and vice versa.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} output
|
|
25
|
+
* @returns {Array<{device: string, kbPerTransfer: number, transfersPerSecond: number, mbPerSecond: number}>}
|
|
26
|
+
*/
|
|
27
|
+
export function parseIostat(output: string): Array<{
|
|
28
|
+
device: string;
|
|
29
|
+
kbPerTransfer: number;
|
|
30
|
+
transfersPerSecond: number;
|
|
31
|
+
mbPerSecond: number;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Load averages, or null.
|
|
35
|
+
* @param {string} output
|
|
36
|
+
*/
|
|
37
|
+
export function parseLoadAverage(output: string): {
|
|
38
|
+
one: number;
|
|
39
|
+
five: number;
|
|
40
|
+
fifteen: number;
|
|
41
|
+
} | null;
|
|
42
|
+
/**
|
|
43
|
+
* RESIDENT size per process. Not footprint — `ps` has no such column; see this module's header for why
|
|
44
|
+
* that is a deliberate limit rather than an oversight, and what to read beside it.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} psOutput output of `ps -o pid=,rss=,comm=`
|
|
47
|
+
* @param {string} match substring of the command to keep
|
|
48
|
+
*/
|
|
49
|
+
export function parseProcessMemory(psOutput: string, match: string): {
|
|
50
|
+
pid: number;
|
|
51
|
+
residentMb: number;
|
|
52
|
+
command: string;
|
|
53
|
+
}[];
|
|
54
|
+
/**
|
|
55
|
+
* One snapshot of the foundations.
|
|
56
|
+
*
|
|
57
|
+
* `iostat -c 2 -w 1` deliberately takes two samples a second apart and uses the second, because the
|
|
58
|
+
* first is a since-boot average. That makes this call cost ~1 s, which is why it is a snapshot taken
|
|
59
|
+
* around a measurement rather than something polled continuously.
|
|
60
|
+
*
|
|
61
|
+
* @param {{ processMatch?: string }} options
|
|
62
|
+
*/
|
|
63
|
+
export function sampleHost({ processMatch }?: {
|
|
64
|
+
processMatch?: string;
|
|
65
|
+
}): {
|
|
66
|
+
at: string;
|
|
67
|
+
memory: {
|
|
68
|
+
freeMb: number;
|
|
69
|
+
activeMb: number;
|
|
70
|
+
inactiveMb: number;
|
|
71
|
+
wiredMb: number;
|
|
72
|
+
compressorMb: number;
|
|
73
|
+
pageins: number;
|
|
74
|
+
pageouts: number;
|
|
75
|
+
} | null;
|
|
76
|
+
disk: {
|
|
77
|
+
device: string;
|
|
78
|
+
kbPerTransfer: number;
|
|
79
|
+
transfersPerSecond: number;
|
|
80
|
+
mbPerSecond: number;
|
|
81
|
+
}[];
|
|
82
|
+
load: {
|
|
83
|
+
one: number;
|
|
84
|
+
five: number;
|
|
85
|
+
fifteen: number;
|
|
86
|
+
} | null;
|
|
87
|
+
processes: {
|
|
88
|
+
pid: number;
|
|
89
|
+
residentMb: number;
|
|
90
|
+
command: string;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* @param {HostSnapshot} before
|
|
95
|
+
* @param {HostSnapshot} after
|
|
96
|
+
*/
|
|
97
|
+
export function diffHost(before: HostSnapshot, after: HostSnapshot): {
|
|
98
|
+
pageoutsDelta: number;
|
|
99
|
+
pageinsDelta: number;
|
|
100
|
+
swappingDuringRun: boolean;
|
|
101
|
+
compressorMb: number | null;
|
|
102
|
+
freeMb: number | null;
|
|
103
|
+
residentMbTotal: number;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Named because the first attempt at annotating this called it `Record<string, number>` -- flat numbers,
|
|
107
|
+
* which is what the CALL SITES read out of it and not what it is. A shape guessed from its uses is the
|
|
108
|
+
* same defect as a type inferred from its defaults, one file over.
|
|
109
|
+
*/
|
|
110
|
+
export type HostSnapshot = {
|
|
111
|
+
memory?: Record<string, number>;
|
|
112
|
+
processes?: {
|
|
113
|
+
residentMb: number;
|
|
114
|
+
}[];
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=host-metrics.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-metrics.d.mts","sourceRoot":"","sources":["../src/host-metrics.mjs"],"names":[],"mappings":"AAmDA;;;;;;;GAOG;AACH,oCAFW,MAAM;;;;;;;;SAkBhB;AAED;;;;;;;;GAQG;AACH,oCAHW,MAAM,GACJ,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC,CAiB3G;AAED;;;GAGG;AACH,yCAFW,MAAM;;;;SAKhB;AAED;;;;;;GAMG;AACH,6CAHW,MAAM,SACN,MAAM;;;;IAWhB;AAUD;;;;;;;;GAQG;AACH,8CAFW;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;gBA7DR,MAAM;uBAAiB,MAAM;4BAAsB,MAAM;qBAAe,MAAM;;;;;;;;;;;;EAwEzG;AA2BD;;;GAGG;AACH,iCAHW,YAAY,SACZ,YAAY;;;;;;;EAmBtB;;;;;;2BApCY;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* What the HOST was doing while a measurement ran: CPU, disk, memory.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* Every performance conclusion in this project has been drawn from wall-clock time and phase marks,
|
|
8
|
+
* and the causes were misattributed three times in one day — first to memory, then to the guests
|
|
9
|
+
* themselves, then to contention in the abstract. The actual bottleneck was **disk I/O from Chromium
|
|
10
|
+
* cold-starting on three guests at once**, and it was invisible because nothing ever measured it.
|
|
11
|
+
*
|
|
12
|
+
* A timing number without the foundations underneath it does not identify a cause; it only says
|
|
13
|
+
* something got slower. These are the four things that were being guessed at:
|
|
14
|
+
*
|
|
15
|
+
* CPU load average and the guests' own share. Ruled contention in or out.
|
|
16
|
+
* DISK transfers/s and MB/s. The one that mattered, and the one never sampled.
|
|
17
|
+
* MEMORY RESIDENT bytes, not `phys_footprint`.
|
|
18
|
+
* PAGING pageouts and compressor size — the difference between "tight" and "thrashing".
|
|
19
|
+
*
|
|
20
|
+
* ## phys_footprint is a charge, not occupancy — this is the mistake it caused
|
|
21
|
+
*
|
|
22
|
+
* `MEMORY_PER_WORKER_MB` was set three times (7,600 then 8,100 then 5,600) from `top`'s memory column,
|
|
23
|
+
* which on macOS is `phys_footprint`: it counts compressed and swapped-out pages as though they were
|
|
24
|
+
* resident. Three guests showing 5.8 GB each — 17.4 GB "used" — held **3.8 GB of actual RAM** between
|
|
25
|
+
* them, with 7.8 GB sitting in the compressor. The capacity model reserved roughly four times what a
|
|
26
|
+
* worker occupies and concluded the Mac could only run two. Resident size is reported here for exactly
|
|
27
|
+
* that reason.
|
|
28
|
+
*
|
|
29
|
+
* **FOOTPRINT IS NOT REPORTED, and this header claimed it was.** The sentence used to end "footprint is
|
|
30
|
+
* reported alongside it so the gap between them stays visible", and `parseProcessMemory`'s own docstring
|
|
31
|
+
* promised "Resident size AND footprint per process". Neither is true: the source is
|
|
32
|
+
* `ps -o pid=,rss=,comm=`, which has no footprint column, and the returned records carry only `residentMb`.
|
|
33
|
+
* A documented remedy that was never implemented, vouched for by two comments.
|
|
34
|
+
*
|
|
35
|
+
* That matters because RSS is the reading that lies in the one state worth detecting: a starved guest
|
|
36
|
+
* showed `rss=0.4 GB` while its `phys_footprint` was 8.1 GB, its pages being in swap. So `residentMbTotal`
|
|
37
|
+
* FALLS as a host gets sicker — the same self-defeating shape as reading `vm_stat` for available memory.
|
|
38
|
+
*
|
|
39
|
+
* Do not read it alone. `compressorMb` and the pageout DELTA come from `vm_stat` in the same snapshot and
|
|
40
|
+
* are not distorted that way: low resident WITH a large compressor is a host absorbing pressure, and a
|
|
41
|
+
* non-zero pageout delta is one that is swapping. `compare-workers.mjs` prints all three together and
|
|
42
|
+
* labels the RSS column "<- RSS, not phys_footprint", which is why the missing field never misled anyone.
|
|
43
|
+
*
|
|
44
|
+
* Adding footprint means `top -l 1 -o mem -stats mem`, which costs a second and a fragile parse; the
|
|
45
|
+
* pairing above answers the same question from a snapshot already being taken. Recorded rather than done,
|
|
46
|
+
* so the next reader is not told a field exists that does not.
|
|
47
|
+
*/
|
|
48
|
+
import { execFileSync } from "node:child_process";
|
|
49
|
+
const BYTES_PER_MB = 1024 * 1024;
|
|
50
|
+
/**
|
|
51
|
+
* Parse `vm_stat`. Pure — the arithmetic is where this goes wrong, not the shelling out.
|
|
52
|
+
*
|
|
53
|
+
* The page size is read from the output rather than assumed: it is 16 KB on Apple Silicon and 4 KB on
|
|
54
|
+
* Intel, and hardcoding 4096 would understate every figure on this machine by a factor of four.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} output
|
|
57
|
+
*/
|
|
58
|
+
export function parseVmStat(output) {
|
|
59
|
+
const pageSize = Number(/page size of (\d+) bytes/.exec(output)?.[1]);
|
|
60
|
+
if (!Number.isFinite(pageSize))
|
|
61
|
+
return null;
|
|
62
|
+
const pages = (/** @type {string} */ label) => Number(new RegExp(`${label}:\\s+(\\d+)`).exec(output)?.[1] ?? 0);
|
|
63
|
+
const mb = (/** @type {string} */ label) => Math.round((pages(label) * pageSize) / BYTES_PER_MB);
|
|
64
|
+
return {
|
|
65
|
+
freeMb: mb("Pages free"),
|
|
66
|
+
activeMb: mb("Pages active"),
|
|
67
|
+
inactiveMb: mb("Pages inactive"),
|
|
68
|
+
wiredMb: mb("Pages wired down"),
|
|
69
|
+
// Compressed pages live in RAM. A large compressor with low `free` is a host absorbing pressure,
|
|
70
|
+
// which is a different state from one that is swapping, and they need different responses.
|
|
71
|
+
compressorMb: mb("Pages occupied by compressor"),
|
|
72
|
+
pageins: pages("Pageins"),
|
|
73
|
+
pageouts: pages("Pageouts"),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse `iostat -d -c 2` — the disk columns for each device.
|
|
78
|
+
*
|
|
79
|
+
* The FIRST sample from iostat is an average since boot and is meaningless for a measurement window;
|
|
80
|
+
* callers must use the second. Getting that wrong reports a busy disk as idle and vice versa.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} output
|
|
83
|
+
* @returns {Array<{device: string, kbPerTransfer: number, transfersPerSecond: number, mbPerSecond: number}>}
|
|
84
|
+
*/
|
|
85
|
+
export function parseIostat(output) {
|
|
86
|
+
const lines = String(output).trim().split(/\r?\n/);
|
|
87
|
+
const header = lines.findIndex((l) => /^\s*disk/.test(l));
|
|
88
|
+
if (header === -1)
|
|
89
|
+
return [];
|
|
90
|
+
const devices = lines[header].trim().split(/\s+/);
|
|
91
|
+
const samples = lines.slice(header + 2).filter((l) => /\d/.test(l));
|
|
92
|
+
const last = samples.at(-1);
|
|
93
|
+
if (!last)
|
|
94
|
+
return [];
|
|
95
|
+
const numbers = last.trim().split(/\s+/).map(Number);
|
|
96
|
+
return devices.map((device, i) => ({
|
|
97
|
+
device,
|
|
98
|
+
kbPerTransfer: numbers[i * 3] ?? 0,
|
|
99
|
+
transfersPerSecond: numbers[i * 3 + 1] ?? 0,
|
|
100
|
+
mbPerSecond: numbers[i * 3 + 2] ?? 0,
|
|
101
|
+
})).filter((d) => Number.isFinite(d.mbPerSecond));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Load averages, or null.
|
|
105
|
+
* @param {string} output
|
|
106
|
+
*/
|
|
107
|
+
export function parseLoadAverage(output) {
|
|
108
|
+
const m = /([\d.]+)\s+([\d.]+)\s+([\d.]+)/.exec(String(output));
|
|
109
|
+
return m ? { one: Number(m[1]), five: Number(m[2]), fifteen: Number(m[3]) } : null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* RESIDENT size per process. Not footprint — `ps` has no such column; see this module's header for why
|
|
113
|
+
* that is a deliberate limit rather than an oversight, and what to read beside it.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} psOutput output of `ps -o pid=,rss=,comm=`
|
|
116
|
+
* @param {string} match substring of the command to keep
|
|
117
|
+
*/
|
|
118
|
+
export function parseProcessMemory(psOutput, match) {
|
|
119
|
+
// `flatMap` rather than map -> filter -> map. The old shape checked `m &&` and was correct at runtime,
|
|
120
|
+
// but a filter cannot narrow a type for the two lines after it, so every field access read as a
|
|
121
|
+
// possible null. Doing the match and the decision in one place needs no narrowing to explain.
|
|
122
|
+
return String(psOutput).trim().split(/\r?\n/).flatMap((line) => {
|
|
123
|
+
const fields = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
124
|
+
if (!fields || !fields[3].includes(match))
|
|
125
|
+
return [];
|
|
126
|
+
return [{ pid: Number(fields[1]), residentMb: Math.round(Number(fields[2]) / 1024), command: fields[3] }];
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const run = (/** @type {string} */ cmd, /** @type {string[]} */ args) => {
|
|
130
|
+
try {
|
|
131
|
+
return execFileSync(cmd, args, { encoding: "utf8", timeout: 15_000 });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return ""; // a missing tool must never break a measurement; the field just reads null
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* One snapshot of the foundations.
|
|
139
|
+
*
|
|
140
|
+
* `iostat -c 2 -w 1` deliberately takes two samples a second apart and uses the second, because the
|
|
141
|
+
* first is a since-boot average. That makes this call cost ~1 s, which is why it is a snapshot taken
|
|
142
|
+
* around a measurement rather than something polled continuously.
|
|
143
|
+
*
|
|
144
|
+
* @param {{ processMatch?: string }} options
|
|
145
|
+
*/
|
|
146
|
+
export function sampleHost({ processMatch = "QEMU" } = {}) {
|
|
147
|
+
const memory = parseVmStat(run("vm_stat", []));
|
|
148
|
+
return {
|
|
149
|
+
at: new Date().toISOString(),
|
|
150
|
+
memory,
|
|
151
|
+
disk: parseIostat(run("iostat", ["-d", "-c", "2", "-w", "1"])),
|
|
152
|
+
load: parseLoadAverage(run("sysctl", ["-n", "vm.loadavg"])),
|
|
153
|
+
processes: parseProcessMemory(run("ps", ["-Ao", "pid=,rss=,comm="]), processMatch),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* What changed between two snapshots.
|
|
158
|
+
*
|
|
159
|
+
* Paging is reported as a DELTA because the absolute counters are since-boot and therefore enormous
|
|
160
|
+
* and useless: 6.6 GB of swap left over from an incident hours ago looks identical to a host swapping
|
|
161
|
+
* right now. Only the delta distinguishes them, and that distinction was worth an afternoon.
|
|
162
|
+
*/
|
|
163
|
+
// Every `?.` and `??` is a branch, so six inlined reads of a possibly-absent snapshot pushed diffHost
|
|
164
|
+
// to a complexity of 21 against a limit of 15 -- without being any clearer than naming the read once.
|
|
165
|
+
/**
|
|
166
|
+
* @typedef {{ memory?: Record<string, number>, processes?: { residentMb: number }[] }} HostSnapshot
|
|
167
|
+
*
|
|
168
|
+
* Named because the first attempt at annotating this called it `Record<string, number>` -- flat numbers,
|
|
169
|
+
* which is what the CALL SITES read out of it and not what it is. A shape guessed from its uses is the
|
|
170
|
+
* same defect as a type inferred from its defaults, one file over.
|
|
171
|
+
*/
|
|
172
|
+
/**
|
|
173
|
+
* @param {HostSnapshot} snapshot
|
|
174
|
+
* @param {string} field
|
|
175
|
+
* @param {number | null} fallback
|
|
176
|
+
* @returns {number | null}
|
|
177
|
+
*/
|
|
178
|
+
const reading = (snapshot, field, fallback) => snapshot?.memory?.[field] ?? fallback;
|
|
179
|
+
/**
|
|
180
|
+
* @param {HostSnapshot} before
|
|
181
|
+
* @param {HostSnapshot} after
|
|
182
|
+
*/
|
|
183
|
+
export function diffHost(before, after) {
|
|
184
|
+
// `?? 0` at the call, so these two stay plain numbers: a delta of two absent readings is 0, which is
|
|
185
|
+
// what "the host did not swap" has always meant here. `compressorMb` and `freeMb` below keep null,
|
|
186
|
+
// because an ABSENT reading and a reading of zero are different facts about the host and this file's
|
|
187
|
+
// whole purpose is that a number carries what it was computed from.
|
|
188
|
+
const pageouts = (reading(after, "pageouts", 0) ?? 0) - (reading(before, "pageouts", 0) ?? 0);
|
|
189
|
+
const pageins = (reading(after, "pageins", 0) ?? 0) - (reading(before, "pageins", 0) ?? 0);
|
|
190
|
+
return {
|
|
191
|
+
pageoutsDelta: pageouts,
|
|
192
|
+
pageinsDelta: pageins,
|
|
193
|
+
// The honest headline. Non-zero pageouts during a measurement means the host was swapping and the
|
|
194
|
+
// timing describes a constrained machine, not the software under test.
|
|
195
|
+
swappingDuringRun: pageouts > 0,
|
|
196
|
+
compressorMb: reading(after, "compressorMb", null),
|
|
197
|
+
freeMb: reading(after, "freeMb", null),
|
|
198
|
+
residentMbTotal: (after?.processes ?? []).reduce((sum, p) => sum + p.residentMb, 0),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=host-metrics.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-metrics.mjs","sourceRoot":"","sources":["../src/host-metrics.mjs"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side worker fleet: lease a Windows capture worker, judge its health, and know how many the host can
|
|
3
|
+
* actually afford to run.
|
|
4
|
+
*
|
|
5
|
+
* None of this touches guidepup or NVDA — it runs on the machine that *drives* the workers, which is why it is
|
|
6
|
+
* a separate package from `@a11ign/screenreader-worker` (ADR 0004). The split is not cosmetic: the worker is
|
|
7
|
+
* Windows-only and the fleet is not.
|
|
8
|
+
*
|
|
9
|
+
* The measurement internals — `host-metrics`, `worker-stats`, `fleet-consistency` — are deliberately NOT
|
|
10
|
+
* exported. Their shapes change every time something new gets measured, and this project's own history is a
|
|
11
|
+
* record of that happening.
|
|
12
|
+
*/
|
|
13
|
+
export { DEFAULT_WORKER, isAfterRun, leaseWorker, leaseWorkerPool, hostAddressForWorker, guestReachableUrl, } from "./local-vm.js";
|
|
14
|
+
export type { AfterRun, WorkerLease, PoolLease } from "./local-vm.js";
|
|
15
|
+
/**
|
|
16
|
+
* The provisioning and lifecycle scripts, as absolute paths.
|
|
17
|
+
*
|
|
18
|
+
* They are shell and PowerShell, not JavaScript, so a consumer has to spawn them — and they can only be found
|
|
19
|
+
* relative to the module. Delegated to `fleet-scripts.mjs` so this package has exactly ONE definition of where
|
|
20
|
+
* they live: it had four, and one of them was wrong, which broke `leaseWorker` silently.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fleetScriptPaths(): Record<string, string>;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,oBAAoB,EAAE,iBAAiB,GAClG,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAItE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEzD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side worker fleet: lease a Windows capture worker, judge its health, and know how many the host can
|
|
3
|
+
* actually afford to run.
|
|
4
|
+
*
|
|
5
|
+
* None of this touches guidepup or NVDA — it runs on the machine that *drives* the workers, which is why it is
|
|
6
|
+
* a separate package from `@a11ign/screenreader-worker` (ADR 0004). The split is not cosmetic: the worker is
|
|
7
|
+
* Windows-only and the fleet is not.
|
|
8
|
+
*
|
|
9
|
+
* The measurement internals — `host-metrics`, `worker-stats`, `fleet-consistency` — are deliberately NOT
|
|
10
|
+
* exported. Their shapes change every time something new gets measured, and this project's own history is a
|
|
11
|
+
* record of that happening.
|
|
12
|
+
*/
|
|
13
|
+
export { DEFAULT_WORKER, isAfterRun, leaseWorker, leaseWorkerPool, hostAddressForWorker, guestReachableUrl, } from "./local-vm.js";
|
|
14
|
+
import { fleetScriptPaths as scriptPaths } from "./fleet-scripts.mjs";
|
|
15
|
+
/**
|
|
16
|
+
* The provisioning and lifecycle scripts, as absolute paths.
|
|
17
|
+
*
|
|
18
|
+
* They are shell and PowerShell, not JavaScript, so a consumer has to spawn them — and they can only be found
|
|
19
|
+
* relative to the module. Delegated to `fleet-scripts.mjs` so this package has exactly ONE definition of where
|
|
20
|
+
* they live: it had four, and one of them was wrong, which broke `leaseWorker` silently.
|
|
21
|
+
*/
|
|
22
|
+
export function fleetScriptPaths() {
|
|
23
|
+
return scriptPaths();
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,oBAAoB,EAAE,iBAAiB,GAClG,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,gBAAgB,IAAI,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,WAAW,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** Where a hand-started worker on this machine has always lived. */
|
|
2
|
+
export declare const DEFAULT_WORKER = "http://localhost:8765";
|
|
3
|
+
/** What to do with the VM once the run finishes. */
|
|
4
|
+
export type AfterRun = "restore" | "stop" | "pause" | "leave";
|
|
5
|
+
export declare function isAfterRun(v: string): v is AfterRun;
|
|
6
|
+
interface VmStatus {
|
|
7
|
+
uuid: string;
|
|
8
|
+
name: string;
|
|
9
|
+
/** utmctl's own vocabulary: "started" | "paused" | "stopped" | "unknown". */
|
|
10
|
+
state: string;
|
|
11
|
+
ip: string;
|
|
12
|
+
port: number;
|
|
13
|
+
healthy: boolean;
|
|
14
|
+
/** True while a capture is in flight -- the worker's own flag. */
|
|
15
|
+
busy: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** A worker to capture against, and the cleanup that matches how it was obtained. */
|
|
18
|
+
export interface WorkerLease {
|
|
19
|
+
worker: string;
|
|
20
|
+
/** How `worker` was chosen, so callers can tailor their own error messages. */
|
|
21
|
+
source: "explicit" | "inventory.yml" | "local-vm" | "default";
|
|
22
|
+
/**
|
|
23
|
+
* The address the GUEST can use to reach THIS host, when capturing via a local VM.
|
|
24
|
+
* Undefined otherwise. Needed because anything the host serves on `localhost` is
|
|
25
|
+
* unreachable from the guest -- `localhost` there means the guest itself.
|
|
26
|
+
*/
|
|
27
|
+
hostAddress?: string;
|
|
28
|
+
/** Never throws: a cleanup failure must not mask the run's own result. */
|
|
29
|
+
release: () => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The registered local VM, or null when there is nothing to manage: not macOS, no UTM, no
|
|
33
|
+
* VM registered under that name, or duplicate registrations (worker-ctl refuses to guess
|
|
34
|
+
* between those rather than risk acting on the wrong one).
|
|
35
|
+
*/
|
|
36
|
+
export declare function findLocalVm(): Promise<VmStatus | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Start (or resume) the local VM, returning its worker URL and the matching cleanup.
|
|
39
|
+
* Throws if the VM never becomes healthy -- there is no point judging a capture that could
|
|
40
|
+
* not happen.
|
|
41
|
+
*/
|
|
42
|
+
export declare function acquireLocalWorker(vm: VmStatus, after: AfterRun): Promise<WorkerLease>;
|
|
43
|
+
interface LeaseRequest {
|
|
44
|
+
/** A worker the caller was given explicitly (--worker / A11Y_WORKER), or null. */
|
|
45
|
+
worker: string | null;
|
|
46
|
+
after: AfterRun;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Decide what to capture against, in priority order:
|
|
50
|
+
*
|
|
51
|
+
* 1. A worker the caller named is used as-is. Naming one is a statement that you are
|
|
52
|
+
* managing it yourself, so the VM lifecycle is never touched.
|
|
53
|
+
* 2. Otherwise, `inventory.yml`'s bare-metal fleet — always-on boxes, so there is no VM
|
|
54
|
+
* lifecycle to run: the first declared worker is leased with a no-op release.
|
|
55
|
+
* 3. Otherwise, a registered local UTM VM is started on demand and released afterwards.
|
|
56
|
+
* 4. Otherwise, the historical default, so a hand-run worker on this machine still works.
|
|
57
|
+
*
|
|
58
|
+
* FOUND 2026-09-06 (`docs/backlog.md` §8): this used to go straight from (1) to (3), never
|
|
59
|
+
* reading the inventory at all -- so a checkout WITH a bare-metal fleet declared and a UTM guest
|
|
60
|
+
* still registered (the ordinary state of a Mac that used to run the deprecated pool) leased the
|
|
61
|
+
* VM by default, exactly the wrong turn CLAUDE.md already records costing a capture-path change:
|
|
62
|
+
* "a deprecated path that is still the first one documented is not deprecated". `resolveWorkerPool`
|
|
63
|
+
* (`fleet-env.mjs`) already had the corrected order for the POOL case; this brings the single-worker
|
|
64
|
+
* case into line, and `worker-precedence.test.ts` is what is supposed to keep them there.
|
|
65
|
+
*
|
|
66
|
+
* Step 2 costs nothing and throws nothing for anyone without an `inventory.yml` -- a public
|
|
67
|
+
* consumer who installs this package -- because `inventoryWorkerUrls()` already treats an absent
|
|
68
|
+
* or unparsable file as "no fleet declared here" and returns `[]`. So the fall-through to (3) and
|
|
69
|
+
* (4) is byte-for-byte what it was before this change for that consumer; only a checkout that
|
|
70
|
+
* DECLARES a fleet sees new behaviour, and it sees the behaviour `doctor` and `worker:code`
|
|
71
|
+
* already assumed it had.
|
|
72
|
+
*
|
|
73
|
+
* A11Y_LOCAL_VM=0 skips step 3 for anyone who wants the pre-inventory local-VM behaviour back --
|
|
74
|
+
* unchanged in what it does, moved because inventory now sits ahead of it.
|
|
75
|
+
*
|
|
76
|
+
* Shared by every entry point that needs a worker, so the priority order cannot drift
|
|
77
|
+
* between them.
|
|
78
|
+
*
|
|
79
|
+
* `deps` is the injection seam: real callers get the real inventory reader and the real (shells
|
|
80
|
+
* out to `utmctl`) VM lookup, and a test supplies fakes for both, so the precedence can be proven
|
|
81
|
+
* without a filesystem `inventory.yml` or a UTM install. Called as `deps.inventory?.() ?? inventoryWorkerUrls()`
|
|
82
|
+
* rather than resolved into a local first — deliberately, so `inventoryWorkerUrls(` and `findLocalVm(`
|
|
83
|
+
* both appear as real CALLS at the exact decision point, which is what lets `worker-precedence.test.ts`
|
|
84
|
+
* read this function's actual order rather than only the shape of its signature.
|
|
85
|
+
*/
|
|
86
|
+
export declare function leaseWorker({ worker, after }: LeaseRequest, deps?: {
|
|
87
|
+
inventory?: () => string[];
|
|
88
|
+
findLocalVm?: () => Promise<VmStatus | null>;
|
|
89
|
+
}): Promise<WorkerLease>;
|
|
90
|
+
/**
|
|
91
|
+
* Rewrite a base URL the GUEST has to fetch so it points at this host rather than at
|
|
92
|
+
* `localhost`, which from inside the guest means the guest.
|
|
93
|
+
*
|
|
94
|
+
* This is the trap in dataset capture: the pages are served by a plain HTTP server on the
|
|
95
|
+
* Mac, and the default base URL is `http://localhost:5050`. Left alone, every capture
|
|
96
|
+
* loads the guest's own empty port and the transcripts come back describing nothing --
|
|
97
|
+
* with no error, because a connection refused inside Edge is not a worker failure.
|
|
98
|
+
*/
|
|
99
|
+
/**
|
|
100
|
+
* This host's address as seen from a worker at `workerUrl`, when the two share a subnet.
|
|
101
|
+
* Returns undefined for a hostname, or for a worker somewhere we have no interface onto --
|
|
102
|
+
* in which case the caller must be told where to reach us rather than have it guessed.
|
|
103
|
+
*/
|
|
104
|
+
export declare function hostAddressForWorker(workerUrl: string): string | undefined;
|
|
105
|
+
export declare function guestReachableUrl(baseUrl: string, lease: WorkerLease): string;
|
|
106
|
+
/** Several workers, and the cleanup that puts every one of them back as it was found. */
|
|
107
|
+
export interface PoolLease {
|
|
108
|
+
workers: string[];
|
|
109
|
+
hostAddress?: string;
|
|
110
|
+
release: () => Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Lease every local worker VM: start what is not running, and put each one back afterwards.
|
|
114
|
+
*
|
|
115
|
+
* This exists because the pool path used to hand back a no-op release, so a pooled run left
|
|
116
|
+
* every VM running indefinitely — which is precisely the cost the single-worker lease was
|
|
117
|
+
* written to avoid, reintroduced the moment pooling became the normal way to run.
|
|
118
|
+
*
|
|
119
|
+
* Per-VM restore, not a blanket stop: a VM you had already started stays started, exactly as
|
|
120
|
+
* in the single-worker case. A long dataset run must not shut down a worker somebody else is
|
|
121
|
+
* using, and with a pool that is likelier, not less.
|
|
122
|
+
*/
|
|
123
|
+
export declare function leaseWorkerPool(after: AfterRun): Promise<PoolLease | null>;
|
|
124
|
+
export {};
|
|
125
|
+
//# sourceMappingURL=local-vm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-vm.d.ts","sourceRoot":"","sources":["../src/local-vm.ts"],"names":[],"mappings":"AA0BA,oEAAoE;AACpE,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAWtD,oDAAoD;AACpD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAI9D,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,QAAQ,CAEnD;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,IAAI,EAAE,OAAO,CAAC;CACf;AAED,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,MAAM,EAAE,UAAU,GAAG,eAAe,GAAG,UAAU,GAAG,SAAS,CAAC;IAC9D;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAuCD;;;;GAIG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAY5D;AAuBD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CA2B5F;AAED,UAAU,YAAY;IACpB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,WAAW,CAC/B,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,YAAY,EAC/B,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;CAAO,GACtF,OAAO,CAAC,WAAW,CAAC,CAqBtB;AAED;;;;;;;;GAQG;AACH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO1E;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAc7E;AAED,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAuCD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CA8DhF"}
|