@frockbot/computer-host-runtime 0.3.20 → 0.3.22
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/package.json +1 -1
- package/src/runtime.test.ts +142 -0
- package/src/runtime.ts +152 -3
package/package.json
CHANGED
package/src/runtime.test.ts
CHANGED
|
@@ -28,10 +28,15 @@ import {
|
|
|
28
28
|
BROWSER_ENSURE_ACTION,
|
|
29
29
|
BROWSER_FOCUS_ACTION,
|
|
30
30
|
BROWSER_SURVEY_ACTION,
|
|
31
|
+
browserWatchdogScript,
|
|
31
32
|
CHROME_LAUNCHER,
|
|
32
33
|
CHROME_PROFILE,
|
|
33
34
|
chromeLauncherScript,
|
|
35
|
+
CHROMIUM_DISABLED_FEATURES,
|
|
36
|
+
CHROMIUM_FLAGS,
|
|
37
|
+
CHROMIUM_MAX_OLD_SPACE_MIB,
|
|
34
38
|
CHROMIUM_PATH,
|
|
39
|
+
CHROMIUM_RENDERER_PROCESS_LIMIT,
|
|
35
40
|
COMPUTER_CDP_PORT,
|
|
36
41
|
COMPUTER_DISPLAY,
|
|
37
42
|
DESKTOP_SLOTS,
|
|
@@ -89,6 +94,10 @@ import {
|
|
|
89
94
|
SLOT_IDLE_SECONDS,
|
|
90
95
|
UPDATE_PHASES,
|
|
91
96
|
updateLaunchScript,
|
|
97
|
+
WATCHDOG_LOG,
|
|
98
|
+
WATCHDOG_MEM_AVAILABLE_FLOOR_KIB,
|
|
99
|
+
WATCHDOG_RENDERER_RSS_LIMIT_KIB,
|
|
100
|
+
WATCHDOG_SCRIPT,
|
|
92
101
|
WORKSPACES_ROOT,
|
|
93
102
|
} from "./runtime.ts";
|
|
94
103
|
|
|
@@ -720,6 +729,136 @@ describe("installed shell scripts", () => {
|
|
|
720
729
|
expect(launcher).not.toContain(`rm -rf ${CHROME_PROFILE}`);
|
|
721
730
|
});
|
|
722
731
|
|
|
732
|
+
test("bounds Chromium renderers without disabling background timer throttling", () => {
|
|
733
|
+
expect(CHROMIUM_FLAGS).toContain(
|
|
734
|
+
`--renderer-process-limit=${CHROMIUM_RENDERER_PROCESS_LIMIT}`,
|
|
735
|
+
);
|
|
736
|
+
expect(CHROMIUM_FLAGS).toContain(
|
|
737
|
+
`--js-flags=--max-old-space-size=${CHROMIUM_MAX_OLD_SPACE_MIB}`,
|
|
738
|
+
);
|
|
739
|
+
expect(CHROMIUM_FLAGS).toContain(
|
|
740
|
+
`--disable-features=${CHROMIUM_DISABLED_FEATURES.join(",")}`,
|
|
741
|
+
);
|
|
742
|
+
expect(CHROMIUM_DISABLED_FEATURES).not.toContain(
|
|
743
|
+
"IntensiveWakeUpThrottling",
|
|
744
|
+
);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test("the watchdog kills an oversized renderer and leaves a bounded one running", async () => {
|
|
748
|
+
const directory = await mkdtemp(join(tmpdir(), "frockbot-watchdog-"));
|
|
749
|
+
const oversized = Bun.spawn(["sleep", "60"], {
|
|
750
|
+
stdout: "ignore",
|
|
751
|
+
stderr: "ignore",
|
|
752
|
+
});
|
|
753
|
+
const bounded = Bun.spawn(["sleep", "60"], {
|
|
754
|
+
stdout: "ignore",
|
|
755
|
+
stderr: "ignore",
|
|
756
|
+
});
|
|
757
|
+
try {
|
|
758
|
+
const procRoot = join(directory, "proc");
|
|
759
|
+
const renderer = async (pid: number, rssKiB: number) => {
|
|
760
|
+
const root = join(procRoot, String(pid));
|
|
761
|
+
await mkdir(root, { recursive: true });
|
|
762
|
+
await writeFile(
|
|
763
|
+
join(root, "cmdline"),
|
|
764
|
+
`/home/box/bin/chromium\0--type=renderer\0--renderer-client-id=${pid}\0`,
|
|
765
|
+
);
|
|
766
|
+
await writeFile(
|
|
767
|
+
join(root, "status"),
|
|
768
|
+
`Name:\tchromium\nState:\tS (sleeping)\nVmRSS:\t${rssKiB} kB\n`,
|
|
769
|
+
);
|
|
770
|
+
};
|
|
771
|
+
await mkdir(procRoot, { recursive: true });
|
|
772
|
+
await writeFile(
|
|
773
|
+
join(procRoot, "meminfo"),
|
|
774
|
+
"MemTotal: 8388608 kB\nMemAvailable: 4194304 kB\n",
|
|
775
|
+
);
|
|
776
|
+
await renderer(oversized.pid, WATCHDOG_RENDERER_RSS_LIMIT_KIB + 524_288);
|
|
777
|
+
await renderer(bounded.pid, WATCHDOG_RENDERER_RSS_LIMIT_KIB - 1);
|
|
778
|
+
const logPath = join(directory, "watchdog.log");
|
|
779
|
+
const watchdog = Bun.spawn(["bash"], {
|
|
780
|
+
stdin: new Blob([browserWatchdogScript]),
|
|
781
|
+
stdout: "pipe",
|
|
782
|
+
stderr: "pipe",
|
|
783
|
+
env: {
|
|
784
|
+
...process.env,
|
|
785
|
+
FROCKBOT_WATCHDOG_ONCE: "1",
|
|
786
|
+
FROCKBOT_WATCHDOG_PROC_ROOT: procRoot,
|
|
787
|
+
FROCKBOT_WATCHDOG_LOG: logPath,
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const [watchdogExit, watchdogError] = await Promise.all([
|
|
792
|
+
watchdog.exited,
|
|
793
|
+
new Response(watchdog.stderr).text(),
|
|
794
|
+
]);
|
|
795
|
+
expect(watchdogError).toBe("");
|
|
796
|
+
expect(watchdogExit).toBe(0);
|
|
797
|
+
expect(await oversized.exited).not.toBe(0);
|
|
798
|
+
expect(bounded.exitCode).toBeNull();
|
|
799
|
+
const log = await readFile(logPath, "utf8");
|
|
800
|
+
expect(log).toContain(`pid=${oversized.pid}`);
|
|
801
|
+
expect(log).toContain("reason=renderer-rss");
|
|
802
|
+
expect(log).not.toContain(`pid=${bounded.pid}`);
|
|
803
|
+
} finally {
|
|
804
|
+
oversized.kill();
|
|
805
|
+
bounded.kill();
|
|
806
|
+
await rm(directory, { recursive: true, force: true });
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
test("low available memory closes only the worst renderer needed for the floor", async () => {
|
|
811
|
+
const directory = await mkdtemp(join(tmpdir(), "frockbot-low-memory-"));
|
|
812
|
+
const worst = Bun.spawn(["sleep", "60"], {
|
|
813
|
+
stdout: "ignore",
|
|
814
|
+
stderr: "ignore",
|
|
815
|
+
});
|
|
816
|
+
const smaller = Bun.spawn(["sleep", "60"], {
|
|
817
|
+
stdout: "ignore",
|
|
818
|
+
stderr: "ignore",
|
|
819
|
+
});
|
|
820
|
+
try {
|
|
821
|
+
const procRoot = join(directory, "proc");
|
|
822
|
+
await mkdir(procRoot, { recursive: true });
|
|
823
|
+
await writeFile(
|
|
824
|
+
join(procRoot, "meminfo"),
|
|
825
|
+
`MemAvailable: ${WATCHDOG_MEM_AVAILABLE_FLOOR_KIB - 262_144} kB\n`,
|
|
826
|
+
);
|
|
827
|
+
for (const [pid, rssKiB] of [
|
|
828
|
+
[worst.pid, 393_216],
|
|
829
|
+
[smaller.pid, 262_144],
|
|
830
|
+
] as const) {
|
|
831
|
+
const root = join(procRoot, String(pid));
|
|
832
|
+
await mkdir(root, { recursive: true });
|
|
833
|
+
await writeFile(join(root, "cmdline"), `chromium\0--type=renderer\0`);
|
|
834
|
+
await writeFile(join(root, "status"), `VmRSS:\t${rssKiB} kB\n`);
|
|
835
|
+
}
|
|
836
|
+
const logPath = join(directory, "watchdog.log");
|
|
837
|
+
const watchdog = Bun.spawn(["bash"], {
|
|
838
|
+
stdin: new Blob([browserWatchdogScript]),
|
|
839
|
+
stdout: "ignore",
|
|
840
|
+
stderr: "pipe",
|
|
841
|
+
env: {
|
|
842
|
+
...process.env,
|
|
843
|
+
FROCKBOT_WATCHDOG_ONCE: "1",
|
|
844
|
+
FROCKBOT_WATCHDOG_PROC_ROOT: procRoot,
|
|
845
|
+
FROCKBOT_WATCHDOG_LOG: logPath,
|
|
846
|
+
},
|
|
847
|
+
});
|
|
848
|
+
expect(await watchdog.exited).toBe(0);
|
|
849
|
+
expect(await worst.exited).not.toBe(0);
|
|
850
|
+
expect(smaller.exitCode).toBeNull();
|
|
851
|
+
const log = await readFile(logPath, "utf8");
|
|
852
|
+
expect(log).toContain(`pid=${worst.pid}`);
|
|
853
|
+
expect(log).toContain("reason=low-mem-available");
|
|
854
|
+
expect(log).not.toContain(`pid=${smaller.pid}`);
|
|
855
|
+
} finally {
|
|
856
|
+
worst.kill();
|
|
857
|
+
smaller.kill();
|
|
858
|
+
await rm(directory, { recursive: true, force: true });
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
723
862
|
test("one screen carries every slot, and each viewer is clipped to one", () => {
|
|
724
863
|
const screen = installedScript(
|
|
725
864
|
provisionScript,
|
|
@@ -793,6 +932,7 @@ describe("installed shell scripts", () => {
|
|
|
793
932
|
ENSURE_AGENT_SCRIPT,
|
|
794
933
|
CONTROL_SCRIPT,
|
|
795
934
|
BOUNDED_LOG_SCRIPT,
|
|
935
|
+
WATCHDOG_SCRIPT,
|
|
796
936
|
CHROME_LAUNCHER,
|
|
797
937
|
DOCTOR_SCRIPT,
|
|
798
938
|
`${RUNTIME_ROOT}/start-gateway.sh`,
|
|
@@ -1333,6 +1473,8 @@ describe("box-doctor", () => {
|
|
|
1333
1473
|
"scratch",
|
|
1334
1474
|
"desktop-gateway",
|
|
1335
1475
|
"sync-watcher",
|
|
1476
|
+
"watchdog",
|
|
1477
|
+
"memory-top",
|
|
1336
1478
|
"browser-process",
|
|
1337
1479
|
"browser-cdp",
|
|
1338
1480
|
"screen",
|
package/src/runtime.ts
CHANGED
|
@@ -386,6 +386,22 @@ export function shellGuiCommandV1(command: string): string | undefined {
|
|
|
386
386
|
/** The one browser profile every Bot of one User shares (ADR 0012). */
|
|
387
387
|
export const CHROME_PROFILE = `${HOME_ROOT}/chrome-profile`;
|
|
388
388
|
|
|
389
|
+
/** Maximum Chromium renderer processes on one shared 8 GiB Computer. */
|
|
390
|
+
export const CHROMIUM_RENDERER_PROCESS_LIMIT = 8;
|
|
391
|
+
/** V8 old-space ceiling in each renderer; native allocations remain possible. */
|
|
392
|
+
export const CHROMIUM_MAX_OLD_SPACE_MIB = 1024;
|
|
393
|
+
/**
|
|
394
|
+
* Features disabled for a shared unattended desktop.
|
|
395
|
+
*
|
|
396
|
+
* Native window occlusion is meaningless under Xvfb and can stop painting a
|
|
397
|
+
* window that is visible only through VNC. Background timer throttling stays
|
|
398
|
+
* enabled: an unattended preview tab must not earn more CPU merely because a
|
|
399
|
+
* Bot left it open.
|
|
400
|
+
*/
|
|
401
|
+
export const CHROMIUM_DISABLED_FEATURES = [
|
|
402
|
+
"CalculateNativeWinOcclusion",
|
|
403
|
+
] as const;
|
|
404
|
+
|
|
389
405
|
/** The browser flags the Computer runs chromium under, in one place. */
|
|
390
406
|
export const CHROMIUM_FLAGS: readonly string[] = [
|
|
391
407
|
"--no-sandbox",
|
|
@@ -401,6 +417,9 @@ export const CHROMIUM_FLAGS: readonly string[] = [
|
|
|
401
417
|
"--window-position=0,0",
|
|
402
418
|
"--no-first-run",
|
|
403
419
|
"--no-default-browser-check",
|
|
420
|
+
`--renderer-process-limit=${CHROMIUM_RENDERER_PROCESS_LIMIT}`,
|
|
421
|
+
`--js-flags=--max-old-space-size=${CHROMIUM_MAX_OLD_SPACE_MIB}`,
|
|
422
|
+
`--disable-features=${CHROMIUM_DISABLED_FEATURES.join(",")}`,
|
|
404
423
|
];
|
|
405
424
|
|
|
406
425
|
export const CHROME_LAUNCHER = `${BIN_ROOT}/frockbot-chrome`;
|
|
@@ -550,6 +569,84 @@ fi
|
|
|
550
569
|
exec ${CHROME_LAUNCHER} about:blank
|
|
551
570
|
`;
|
|
552
571
|
|
|
572
|
+
/** The renderer watchdog, supervised independently of Chromium. */
|
|
573
|
+
export const WATCHDOG_SERVICE = "frockbot-browser-watchdog";
|
|
574
|
+
/** The installed watchdog executable. */
|
|
575
|
+
export const WATCHDOG_SCRIPT = `${RUNTIME_ROOT}/browser-watchdog.sh`;
|
|
576
|
+
/** Its bounded, durable-on-the-Computer action log. */
|
|
577
|
+
export const WATCHDOG_LOG = `${RUNTIME_ROOT}/watchdog.log`;
|
|
578
|
+
/** 1.5 GiB RSS: one renderer may not consume a material fraction of the box. */
|
|
579
|
+
export const WATCHDOG_RENDERER_RSS_LIMIT_KIB = 1_572_864;
|
|
580
|
+
/** Keep 512 MiB available for the Agent transport and ordinary commands. */
|
|
581
|
+
export const WATCHDOG_MEM_AVAILABLE_FLOOR_KIB = 524_288;
|
|
582
|
+
/** How often the supervised watchdog samples `/proc`. */
|
|
583
|
+
export const WATCHDOG_INTERVAL_SECONDS = 30;
|
|
584
|
+
/** Bound the durable diagnostic log without requiring logrotate. */
|
|
585
|
+
export const WATCHDOG_LOG_MAX_LINES = 200;
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Kills only Chromium renderer processes, never the shared browser process.
|
|
589
|
+
*
|
|
590
|
+
* Every renderer over the individual ceiling is killed. Under box-wide
|
|
591
|
+
* pressure, the largest remaining renderers are killed until their reclaimed
|
|
592
|
+
* RSS would restore the available-memory floor. Chromium owns renderer crash
|
|
593
|
+
* recovery, so the browser, profile, and other Bots' windows remain resident.
|
|
594
|
+
*/
|
|
595
|
+
export const browserWatchdogScript = `#!/usr/bin/env bash
|
|
596
|
+
set -u
|
|
597
|
+
PROC_ROOT="\${FROCKBOT_WATCHDOG_PROC_ROOT:-/proc}"
|
|
598
|
+
LOG="\${FROCKBOT_WATCHDOG_LOG:-${WATCHDOG_LOG}}"
|
|
599
|
+
mkdir -p "$(dirname "$LOG")"
|
|
600
|
+
touch "$LOG"
|
|
601
|
+
|
|
602
|
+
trim_log() {
|
|
603
|
+
LINES=$(wc -l < "$LOG" 2>/dev/null || echo 0)
|
|
604
|
+
if [ "$LINES" -gt ${WATCHDOG_LOG_MAX_LINES} ]; then
|
|
605
|
+
tail -n ${WATCHDOG_LOG_MAX_LINES} "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG"
|
|
606
|
+
fi
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
while true; do
|
|
610
|
+
MEM_AVAILABLE=$(awk '/^MemAvailable:/ { print $2; exit }' "$PROC_ROOT/meminfo" 2>/dev/null || true)
|
|
611
|
+
# If the kernel did not provide a reading, keep enforcing the per-renderer
|
|
612
|
+
# bound but do not infer box-wide pressure and kill otherwise healthy tabs.
|
|
613
|
+
case "$MEM_AVAILABLE" in (''|*[!0-9]*) MEM_AVAILABLE=${WATCHDOG_MEM_AVAILABLE_FLOOR_KIB};; esac
|
|
614
|
+
CANDIDATES=$(
|
|
615
|
+
for STATUS in "$PROC_ROOT"/[0-9]*/status; do
|
|
616
|
+
[ -f "$STATUS" ] || continue
|
|
617
|
+
PID=$(basename "$(dirname "$STATUS")")
|
|
618
|
+
CMDLINE=$(tr '\\000' ' ' < "$(dirname "$STATUS")/cmdline" 2>/dev/null || true)
|
|
619
|
+
printf '%s' "$CMDLINE" | grep -q -- '--type=renderer' || continue
|
|
620
|
+
RSS=$(awk '/^VmRSS:/ { print $2; exit }' "$STATUS" 2>/dev/null || true)
|
|
621
|
+
case "$RSS" in (''|*[!0-9]*) continue;; esac
|
|
622
|
+
printf '%s %s\\n' "$RSS" "$PID"
|
|
623
|
+
done | sort -rn
|
|
624
|
+
)
|
|
625
|
+
PROJECTED_AVAILABLE=$MEM_AVAILABLE
|
|
626
|
+
while read -r RSS PID; do
|
|
627
|
+
[ -n "\${PID:-}" ] || continue
|
|
628
|
+
REASON=""
|
|
629
|
+
if [ "$RSS" -gt ${WATCHDOG_RENDERER_RSS_LIMIT_KIB} ]; then
|
|
630
|
+
REASON=renderer-rss
|
|
631
|
+
elif [ "$PROJECTED_AVAILABLE" -lt ${WATCHDOG_MEM_AVAILABLE_FLOOR_KIB} ]; then
|
|
632
|
+
REASON=low-mem-available
|
|
633
|
+
else
|
|
634
|
+
break
|
|
635
|
+
fi
|
|
636
|
+
if kill -9 "$PID" 2>/dev/null; then
|
|
637
|
+
PROJECTED_AVAILABLE=$((PROJECTED_AVAILABLE + RSS))
|
|
638
|
+
printf '%s action=closed-renderer pid=%s rssMiB=%s memAvailableMiB=%s reason=%s\\n' \
|
|
639
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$PID" "$((RSS / 1024))" "$((MEM_AVAILABLE / 1024))" "$REASON" >> "$LOG"
|
|
640
|
+
fi
|
|
641
|
+
done <<EOF
|
|
642
|
+
$CANDIDATES
|
|
643
|
+
EOF
|
|
644
|
+
trim_log
|
|
645
|
+
[ "\${FROCKBOT_WATCHDOG_ONCE:-}" = 1 ] && exit 0
|
|
646
|
+
sleep ${WATCHDOG_INTERVAL_SECONDS}
|
|
647
|
+
done
|
|
648
|
+
`;
|
|
649
|
+
|
|
553
650
|
/**
|
|
554
651
|
* One Bot's viewer: an `x11vnc` clipped to that Bot's slot of the one screen.
|
|
555
652
|
*
|
|
@@ -1023,6 +1120,35 @@ if (action.action === "focus") {
|
|
|
1023
1120
|
await done({ focused: Boolean(focusPage), ...(anchor ? { targetId: anchor.targetId } : {}) });
|
|
1024
1121
|
}
|
|
1025
1122
|
|
|
1123
|
+
// Lifecycle cleanup from the Computer Package. Origins come from a preview
|
|
1124
|
+
// process's own bounded log and the action is already scoped to this Bot, but
|
|
1125
|
+
// enforce the window boundary again here: a shared browser profile never
|
|
1126
|
+
// makes another Bot's tab ours to close. If the recorded anchor was one of the
|
|
1127
|
+
// closed pages, adopt a surviving tab in the same window; only create a blank
|
|
1128
|
+
// replacement when the window has no page left.
|
|
1129
|
+
if (action.action === "close-origins") {
|
|
1130
|
+
const origins = new Set(action.origins);
|
|
1131
|
+
const candidates = anchor ? await pagesInWindow(anchor.windowId) : [];
|
|
1132
|
+
let closed = 0;
|
|
1133
|
+
for (const candidate of candidates) {
|
|
1134
|
+
let origin = "";
|
|
1135
|
+
try {
|
|
1136
|
+
origin = new URL(candidate.url()).origin;
|
|
1137
|
+
} catch {}
|
|
1138
|
+
if (!origins.has(origin)) continue;
|
|
1139
|
+
await candidate.close();
|
|
1140
|
+
closed += 1;
|
|
1141
|
+
}
|
|
1142
|
+
const remaining = anchor ? await pagesInWindow(anchor.windowId) : [];
|
|
1143
|
+
if (remaining.length > 0) {
|
|
1144
|
+
const targetId = await targetIdOf(remaining.at(-1));
|
|
1145
|
+
writeFileSync(targetPath(botKey), \`\${targetId}\\n\`, { mode: 0o600 });
|
|
1146
|
+
} else if (anchor) {
|
|
1147
|
+
await ensureWindow(botKey);
|
|
1148
|
+
}
|
|
1149
|
+
await done({ closed, origins: [...origins], snapshot: "" });
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1026
1152
|
const own = anchor ? await pagesInWindow(anchor.windowId) : [];
|
|
1027
1153
|
const page =
|
|
1028
1154
|
own.at(-1) ??
|
|
@@ -1265,7 +1391,7 @@ export const CLOCK_FLOOR_EPOCH = 1_756_684_800;
|
|
|
1265
1391
|
* corrected. The version is compared on every adoption instead, and the whole
|
|
1266
1392
|
* set is rewritten when it moves. Bump it whenever a document below changes.
|
|
1267
1393
|
*/
|
|
1268
|
-
export const REFERENCE_DOCS_VERSION = "2026-09-
|
|
1394
|
+
export const REFERENCE_DOCS_VERSION = "2026-09-05.1";
|
|
1269
1395
|
|
|
1270
1396
|
/**
|
|
1271
1397
|
* What a Bot reads to debug its own Computer.
|
|
@@ -1398,6 +1524,16 @@ more: it holds the flag set and starts the Computer's one browser on the one
|
|
|
1398
1524
|
display and the one CDP port. The \`${BROWSER_SERVICE}\` service calls it, and
|
|
1399
1525
|
nothing else needs to know the flags exist.
|
|
1400
1526
|
|
|
1527
|
+
The launcher limits Chromium to ${CHROMIUM_RENDERER_PROCESS_LIMIT} renderer
|
|
1528
|
+
processes and caps each renderer's V8 old space at
|
|
1529
|
+
${CHROMIUM_MAX_OLD_SPACE_MIB} MiB. It disables only
|
|
1530
|
+
\`${CHROMIUM_DISABLED_FEATURES.join(",")}\`; background-tab timer throttling
|
|
1531
|
+
remains enabled. Native allocations can still exceed V8's heap ceiling, so
|
|
1532
|
+
the separately supervised \`${WATCHDOG_SERVICE}\` samples memory every
|
|
1533
|
+
${WATCHDOG_INTERVAL_SECONDS} seconds. It closes a renderer above 1.5 GiB RSS,
|
|
1534
|
+
or the largest renderer when the Computer has less than 512 MiB available,
|
|
1535
|
+
and records the action in \`${WATCHDOG_LOG}\`.
|
|
1536
|
+
|
|
1401
1537
|
## What is never run from the shell
|
|
1402
1538
|
|
|
1403
1539
|
${COMPUTER_GUI_SHELL_COMMANDS.map((name) => `\`${name}\``).join(", ")}.
|
|
@@ -1420,7 +1556,8 @@ tools do.
|
|
|
1420
1556
|
\`computer_doctor\` runs \`${DOCTOR_SCRIPT}\` and hands back a report: disk on
|
|
1421
1557
|
\`/\` and \`${HOME_ROOT}\`, the size of \`${SCRATCH_ROOT}\`, the viewer
|
|
1422
1558
|
gateway, the durable-root watcher, the shared screen, the one browser process
|
|
1423
|
-
and its CDP port,
|
|
1559
|
+
and its CDP port, the renderer watchdog's recent actions, the top memory
|
|
1560
|
+
consumers, every Bot's window and whether it sits over that Bot's own
|
|
1424
1561
|
slot, the browser build and its profile, the sync signal and any conflicting
|
|
1425
1562
|
generations, this reference
|
|
1426
1563
|
set's version, the launcher and its shims, the clock, DNS, and whether a
|
|
@@ -1448,6 +1585,9 @@ Computer rather than of one run.
|
|
|
1448
1585
|
- **browser-process** — none, or more than one. One is the whole design: the
|
|
1449
1586
|
profile's lock admits exactly one browser, and a second one is a Bot with a
|
|
1450
1587
|
black screen.
|
|
1588
|
+
- **watchdog** — the renderer memory guard is not running. Its last actions
|
|
1589
|
+
remain in \`${WATCHDOG_LOG}\`; opening the Computer repairs its supervised
|
|
1590
|
+
service.
|
|
1451
1591
|
- **reference-docs** — this set is stale and refreshes when the Computer is
|
|
1452
1592
|
next opened. Nothing you can do on the box fixes it.
|
|
1453
1593
|
- **browser** — the browser build is missing. It is installed by provisioning,
|
|
@@ -1469,7 +1609,7 @@ is dropped.
|
|
|
1469
1609
|
|
|
1470
1610
|
## Logs on the box
|
|
1471
1611
|
|
|
1472
|
-
\`${DOCTOR_LOG}\`, and per-Bot under \`${BOTS_ROOT}/<botKey>\`:
|
|
1612
|
+
\`${DOCTOR_LOG}\`, \`${WATCHDOG_LOG}\`, and per-Bot under \`${BOTS_ROOT}/<botKey>\`:
|
|
1473
1613
|
\`chromium.log\`, \`fluxbox.log\`, \`x11vnc.log\`, and \`processes/<id>/log.*\`.
|
|
1474
1614
|
Provisioning's own log is \`${RUNTIME_ROOT}/provision/provision.log\`.
|
|
1475
1615
|
`,
|
|
@@ -1781,6 +1921,14 @@ if pgrep -f watch-workspace.sh >/dev/null 2>&1; then
|
|
|
1781
1921
|
else
|
|
1782
1922
|
record sync-watcher fail "no durable-root watcher is running; on-Computer writes will not signal a sync"
|
|
1783
1923
|
fi
|
|
1924
|
+
WATCHDOG_ACTIONS=$(tail -n 5 ${WATCHDOG_LOG} 2>/dev/null | tr '\n' ';' || true)
|
|
1925
|
+
if pgrep -f -- ${WATCHDOG_SCRIPT} >/dev/null 2>&1; then
|
|
1926
|
+
record watchdog pass "the renderer watchdog is running; recent actions: \${WATCHDOG_ACTIONS:-none}"
|
|
1927
|
+
else
|
|
1928
|
+
record watchdog fail "the renderer watchdog is not running; recent actions: \${WATCHDOG_ACTIONS:-none}"
|
|
1929
|
+
fi
|
|
1930
|
+
TOP_MEMORY=$(ps -eo pid=,rss=,comm= --sort=-rss 2>/dev/null | head -n 5 | tr '\n' ';' || true)
|
|
1931
|
+
record memory-top pass "top resident-memory consumers (pid rssKiB command): \${TOP_MEMORY:-unavailable}"
|
|
1784
1932
|
SLOT=""
|
|
1785
1933
|
if [ -n "$KEY" ] && [ -s ${BOTS_ROOT}/"$KEY"/slot ]; then SLOT=$(cat ${BOTS_ROOT}/"$KEY"/slot); fi
|
|
1786
1934
|
# One browser, one CDP port (ADR 0031). A second main process would mean a
|
|
@@ -1966,6 +2114,7 @@ export const COMPUTER_RUNTIME_FILES: readonly {
|
|
|
1966
2114
|
content: startBrowserScript,
|
|
1967
2115
|
mode: 0o700,
|
|
1968
2116
|
},
|
|
2117
|
+
{ path: WATCHDOG_SCRIPT, content: browserWatchdogScript, mode: 0o700 },
|
|
1969
2118
|
{
|
|
1970
2119
|
path: `${RUNTIME_ROOT}/start-view.sh`,
|
|
1971
2120
|
content: startViewScript,
|