Skip to content

Commit f573d54

Browse files
andyclaude
andcommitted
tools/offload: host pool (radium container, tellurium native), README, docs entry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PFPK14wBcLD1s4dYUowpP4
1 parent ba78840 commit f573d54

7 files changed

Lines changed: 167 additions & 40 deletions

File tree

‎docs/claude/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ One file per fact, YAML frontmatter (`type`, `title`, `area`, `section`,
1010
holds its overview and lists its entries.
1111

1212
- [Code registration](codereg/index.md) — 58 entries
13-
- [Services](services/index.md) — 9 entries
13+
- [Services](services/index.md) — 10 entries
1414
- [MO correlation diagram](mo-diagram/index.md) — 8 entries
1515
- [wx, viewer and C++](wx-viewer/index.md) — 23 entries

‎docs/claude/services/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ what the deployment mode needs (GETTING_STARTED, "Deployment modes"):
4646
- [A calculation can live in any local folder, not only in a data store](calculations-in-any-local-folder.md)
4747
- [Waiting for login: a lost monitor parks the job, session start catches it up](waiting-for-login.md)
4848
- [Test suites get a per-run state directory, ports and X display](test-suites-isolate-per-run.md)
49+
- [Full builds and long suites go to radium/tellurium via tools/offload](long-suites-go-to-offload-hosts.md)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
type: rule
3+
title: "Full builds and long suites go to radium/tellurium via tools/offload"
4+
area: services
5+
section: "Rules"
6+
paths: ["tools/offload/run-offload.sh", "tools/offload/remote-run.sh", "tools/offload/Containerfile"]
7+
issues: []
8+
---
9+
**Do not run full builds, `tests/teaching` (about 26 min on niobium) or other
10+
long suites on niobium; use `tools/offload/run-offload.sh <pushed ref>
11+
<build|ctest|teaching|apps|run>`.** The ref must be pushed first; the host
12+
fetches from GitHub. It prints a summary and a PASS/FAIL line, and keeps the
13+
full log on the host. radium (container, 2 slots) and tellurium (native, 3
14+
slots, work root `/mnt/games/ecce` only, off after 20:30) are picked
15+
automatically; exit status 3 means none is reachable, so run locally. Inside
16+
radium's container `$USER` must be set or the test data server creates no
17+
account. See `tools/offload/README.md`.

‎tools/offload/README.md‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Offloading long builds and test suites
2+
3+
Full builds and `tests/teaching` take long enough to overload the developer
4+
machine. `run-offload.sh` runs them on one of two other machines instead.
5+
6+
tools/offload/run-offload.sh [--host radium|tellurium|auto] <git ref> <command> [args]
7+
8+
`<git ref>` is a branch, `origin/<branch>` or a commit that is **already
9+
pushed to GitHub**: the host fetches from GitHub, not from this machine.
10+
11+
| command | does |
12+
| --- | --- |
13+
| `build` | configure (first time only) and `ninja -k 0` |
14+
| `ctest [-R re ...]` | build, then `ctest -j --output-on-failure` |
15+
| `teaching [args]` | build, then `tests/teaching/run_tests.py --jobs 4 [args]` |
16+
| `apps [args]` | build and install into the build dir, then `tests/apps/run_tests.py` |
17+
| `run <cmd...>` | build, then any command in the checkout (`$SRC`; build tree `$BLD/b`) |
18+
19+
The full log stays on the host (`<host>:.../logs/<time>-<ref>-<pid>.log`);
20+
only the last 25 lines and one `PASS:`/`FAIL:`/`SKIP:` line come back. The
21+
exit status is the command's (77 is SKIP). Examples:
22+
23+
tools/offload/run-offload.sh origin/main build
24+
tools/offload/run-offload.sh wip/foo ctest -R 'parsers|tls_'
25+
tools/offload/run-offload.sh --host radium origin/main teaching --group B
26+
27+
`run-on-radium.sh` is `run-offload.sh --host radium`.
28+
29+
## Hosts
30+
31+
| | radium | tellurium |
32+
| --- | --- | --- |
33+
| role | long test suites | builds |
34+
| system | Debian 12, 6c/12t, 15 GB; build and tests in the `ecce-trixie-build` rootless podman image (`Containerfile`, `--memory 12g`) | Debian 13 native, 4c/8t, 31 GB |
35+
| work root | `~/ecce-offload` | `/mnt/games/ecce` only (never the root disk) |
36+
| concurrency | 2 jobs, `ninja -j6` | 3 jobs, `ninja -j3` |
37+
38+
`--host auto` (the default) prefers tellurium for `build`/`ctest` and radium
39+
for `teaching`/`apps`, takes the other if the first has no free slot, and
40+
queues on the first reachable host if both are full. **tellurium is off at
41+
night (after 20:30)**: a host that does not answer within 5 s is skipped. If
42+
no host is reachable the script exits 3 and says so; run the command locally
43+
then. Slots are `flock` files in `<work root>/locks`, so waiting jobs queue
44+
instead of failing. One job per ref at a time (the build dir is per ref).
45+
46+
## Layout on a host
47+
48+
`<root>/src` is a shallow clone (`--depth 50`, fetching only `main` and the
49+
requested branch); `wt/<ref>` a detached worktree per ref; `build/<ref>` the
50+
persistent build tree (`b` normal, `bi` + `install` for `apps`), so a second
51+
run of a ref is incremental; `home/<ref>` is `$HOME` for the job (tellurium
52+
also sets `XDG_CACHE_HOME`, `TMPDIR` under the root); `logs/`.
53+
54+
## Rebuilding radium's image
55+
56+
Only when the CI package list changes: copy `Containerfile` to
57+
`~/ecce-offload/image` on radium and `podman build -t ecce-trixie-build .`
58+
there (about 10 min, ~700 MB of downloads, so not casually).
59+
Inside the container `$USER` is set explicitly; without it the data server
60+
creates no account and every teaching case fails with "no such parent".

‎tools/offload/remote-run.sh‎

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@
33
set -u
44
ref=$1; shift
55
cmd=$1; shift
6-
OFF=$HOME/ecce-offload
6+
# Set by run-offload.sh: OFF (work root), MODE (container|native), SLOTS, JOBS.
7+
OFF=${OFF:-$HOME/ecce-offload}
8+
MODE=${MODE:-container}
79
REPO_URL=https://github.com/FriendsofECCE/ECCE.git
810
IMAGE=ecce-trixie-build
9-
SLOTS=2
10-
JOBS=6
11-
mkdir -p "$OFF"/{src,wt,build,logs,locks,home}
11+
SLOTS=${SLOTS:-2}
12+
JOBS=${JOBS:-6}
13+
mkdir -p "$OFF"/{src,wt,build,logs,locks,home,cache,tmp}
1214

13-
safe=$(printf %s "$ref" | tr -c 'A-Za-z0-9._-\n' '_')
15+
safe=$(printf %s "$ref" | tr -c 'A-Za-z0-9._-' '_')
1416
stamp=$(date +%Y%m%d-%H%M%S)
15-
log=$OFF/logs/$stamp-$safe.log
17+
log=$OFF/logs/$stamp-$safe-$$.log
1618
exec 3>&1
1719
# Everything below goes to the log; the summary is printed on fd 3.
1820
exec >"$log" 2>&1
1921
say() { echo "$@" >&3; }
20-
fail() { say "FAIL: $* (log: radium:$log)"; exit 1; }
22+
fail() { say "FAIL: $* (log: $(hostname):$log)"; exit 1; }
2123

2224
# Per-ref lock: two jobs on one ref would share one build directory.
2325
exec 8>"$OFF/locks/ref-$safe"
@@ -59,41 +61,50 @@ wt=$OFF/wt/$safe
5961
bdir=$OFF/build/$safe
6062
mkdir -p "$bdir" "$OFF/home/$safe"
6163
echo "ref $ref = $sha, command: $cmd $*"
62-
say "radium: $ref = ${sha:0:10}, $cmd $* (slot $got)"
64+
say "$(hostname): $ref = ${sha:0:10}, $cmd $* (slot $got)"
6365

64-
# Commands run inside the container; /work/src is the checkout, /work/build
65-
# the persistent per-ref build directory.
66+
# $SRC is the checkout, $BLD the persistent per-ref build directory.
6667
case $cmd in
67-
build) inner='configure; ninja -C /work/build/b -j'$JOBS' -k 0' ;;
68-
ctest) inner='configure; ninja -C /work/build/b -j'$JOBS' -k 0 && cd /work/build/b && ctest -j2 --output-on-failure "$@"' ;;
69-
teaching) inner='configure; ninja -C /work/build/b -j'$JOBS' -k 0 && cd /work/src && tests/teaching/run_tests.py --build /work/build/b --jobs 4 "$@"' ;;
70-
apps) inner='install_tree; cd /work/src && ECCE_TEST_HOME=/work/build/install ECCE_TEST_WRAPPERS=/work/build/install-bin tests/apps/run_tests.py "$@"' ;;
71-
run) inner='configure; cd /work/src && "$@"' ;;
68+
build) inner='configure; ninja -C $BLD/b -j'$JOBS' -k 0' ;;
69+
ctest) inner='configure; ninja -C $BLD/b -j'$JOBS' -k 0 && cd $BLD/b && ctest -j${CTEST_JOBS:-2} --output-on-failure "$@"' ;;
70+
teaching) inner='configure; ninja -C $BLD/b -j'$JOBS' -k 0 && cd $SRC && tests/teaching/run_tests.py --build $BLD/b --jobs 4 "$@"' ;;
71+
apps) inner='install_tree; cd $SRC && ECCE_TEST_HOME=$BLD/install ECCE_TEST_WRAPPERS=$BLD/install-bin tests/apps/run_tests.py "$@"' ;;
72+
run) inner='configure; cd $SRC && "$@"' ;;
7273
*) fail "unknown command '$cmd' (build|ctest|teaching|apps|run)" ;;
7374
esac
7475
prelude='
7576
configure() {
76-
[ -f /work/build/b/build.ninja ] || cmake -G Ninja -S /work/src -B /work/build/b -DECCE_REQUIRE_LIBSSH=ON || return 1
77+
[ -f $BLD/b/build.ninja ] || cmake -G Ninja -S $SRC -B $BLD/b -DECCE_REQUIRE_LIBSSH=ON || return 1
7778
}
7879
install_tree() {
79-
[ -f /work/build/bi/build.ninja ] || cmake -G Ninja -S /work/src -B /work/build/bi \
80-
-DCMAKE_INSTALL_PREFIX=/work/build/install -DECCE_HOME_DIR=/work/build/install \
81-
-DECCE_WRAPPER_DESTINATION=/work/build/install-bin || return 1
82-
ninja -C /work/build/bi -j'$JOBS' -k 0 && ninja -C /work/build/bi install
80+
[ -f $BLD/bi/build.ninja ] || cmake -G Ninja -S $SRC -B $BLD/bi \
81+
-DCMAKE_INSTALL_PREFIX=$BLD/install -DECCE_HOME_DIR=$BLD/install \
82+
-DECCE_WRAPPER_DESTINATION=$BLD/install-bin || return 1
83+
ninja -C $BLD/bi -j'$JOBS' -k 0 && ninja -C $BLD/bi install
8384
}
8485
set -e'
8586

8687
start=$(date +%s)
87-
podman run --rm --memory 12g --memory-swap 12g --userns=keep-id --shm-size 1g \
88-
-e HOME=/work/home -e TERM=dumb -e CTEST_OUTPUT_ON_FAILURE=1 \
89-
-v "$wt":/work/src -v "$bdir":/work/build -v "$OFF/home/$safe":/work/home \
90-
-w /work/src "$IMAGE" bash -c "$prelude
88+
# Inside the container the checkout, build dir and home are /work/*; native
89+
# hosts use the real paths. Only the work root is ever written to.
90+
if [ "$MODE" = container ]; then
91+
podman run --rm --memory 12g --memory-swap 12g --userns=keep-id --shm-size 1g \
92+
-e HOME=/work/home -e USER="$(id -un)" -e LOGNAME="$(id -un)" -e SRC=/work/src -e BLD=/work/build -e TERM=dumb \
93+
-e CTEST_OUTPUT_ON_FAILURE=1 \
94+
-v "$wt":/work/src -v "$bdir":/work/build -v "$OFF/home/$safe":/work/home \
95+
-w /work/src "$IMAGE" bash -c "$prelude
9196
$inner" bash "$@" </dev/null
97+
else
98+
PATH=$PATH:/usr/sbin:/sbin HOME=$OFF/home/$safe SRC=$wt BLD=$bdir XDG_CACHE_HOME=$OFF/cache TMPDIR=$OFF/tmp \
99+
TERM=dumb CTEST_OUTPUT_ON_FAILURE=1 \
100+
bash -c "$prelude
101+
$inner" bash "$@" </dev/null
102+
fi
92103
rc=$?
93104
secs=$(( $(date +%s) - start ))
94105

95106
say "---- last lines of the log ----"
96107
tail -n 25 "$log" >&3
97108
if [ $rc -eq 0 ]; then verdict=PASS; elif [ $rc -eq 77 ]; then verdict=SKIP; else verdict=FAIL; fi
98-
say "$verdict: $cmd $* on $ref (${sha:0:10}) exit $rc, ${secs}s (log: radium:$log)"
109+
say "$verdict: $cmd $* on $ref (${sha:0:10}) exit $rc, ${secs}s (log: $(hostname):$log)"
99110
exit $rc

‎tools/offload/run-offload.sh‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/bash
2+
# Run a long ECCE build or test suite on an offload machine.
3+
# run-offload.sh [--host radium|tellurium|auto] <git ref> <build|ctest [-R re]|teaching|apps|run ...> [args]
4+
# The ref must already be pushed to GitHub. See tools/offload/README.md.
5+
set -eu
6+
host=auto
7+
if [ "${1:-}" = --host ]; then host=$2; shift 2; fi
8+
if [ $# -lt 2 ]; then
9+
sed -n '2,4p' "$0" | sed 's/^# \{0,1\}//' >&2
10+
exit 2
11+
fi
12+
here=$(cd "$(dirname "$0")" && pwd)
13+
14+
# Per-host settings: work root, container or native, parallel jobs, ninja -j.
15+
conf() {
16+
case $1 in
17+
radium) echo 'OFF=$HOME/ecce-offload MODE=container SLOTS=2 JOBS=6' ;;
18+
tellurium) echo 'OFF=/mnt/games/ecce MODE=native SLOTS=3 JOBS=3 CTEST_JOBS=3' ;;
19+
*) echo "unknown host $1" >&2; return 1 ;;
20+
esac
21+
}
22+
# Free slots on a host (-1 if unreachable, within 5 s).
23+
free() {
24+
ssh -o BatchMode=yes -o ConnectTimeout=5 "$1" "$(conf "$1"); n=0; mkdir -p \$OFF/locks;
25+
for i in \$(seq 1 \$SLOTS); do ( exec 9>\$OFF/locks/slot\$i; flock -n 9 ) && n=\$((n+1)); done; echo \$n" 2>/dev/null || echo -1
26+
}
27+
if [ "$host" = auto ]; then
28+
# Builds prefer tellurium, test suites radium. A host that does not
29+
# answer (tellurium is off at night) is skipped; if every reachable host
30+
# is full the job queues on the first reachable one.
31+
case $2 in build|ctest) order="tellurium radium" ;; *) order="radium tellurium" ;; esac
32+
host= queue=
33+
for h in $order; do
34+
n=$(free "$h")
35+
[ "$n" -ge 0 ] || { echo "offload: $h unreachable, skipped" >&2; continue; }
36+
[ -n "$queue" ] || queue=$h
37+
if [ "$n" -gt 0 ]; then host=$h; break; fi
38+
done
39+
host=${host:-$queue}
40+
if [ -z "$host" ]; then
41+
echo "offload: no host reachable; run the command locally (niobium)" >&2
42+
exit 3
43+
fi
44+
echo "offload host: $host" >&2
45+
fi
46+
envs=$(conf "$host")
47+
args=$(printf '%q ' "$@")
48+
# remote-run.sh is sent on stdin, so the host needs no checkout to start from.
49+
exec ssh -o BatchMode=yes -o ConnectTimeout=5 -o ServerAliveInterval=30 "$host" "env $envs bash -s -- $args" < "$here/remote-run.sh"

‎tools/offload/run-on-radium.sh‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
#!/bin/sh
2-
# Run a long ECCE build or test suite on radium instead of this machine.
3-
# run-on-radium.sh <git ref> <build|ctest [-R re]|teaching|apps|run ...> [args]
4-
# The ref must already be pushed to GitHub. See tools/offload/README.md.
5-
set -eu
6-
if [ $# -lt 2 ]; then
7-
sed -n '2,4p' "$0" | sed 's/^# \{0,1\}//' >&2
8-
exit 2
9-
fi
10-
here=$(cd "$(dirname "$0")" && pwd)
11-
host=${RADIUM_HOST:-radium}
12-
args=$(printf '%q ' "$@")
13-
# remote-run.sh is sent on stdin, so radium needs no checkout to start from.
14-
exec ssh -o BatchMode=yes -o ServerAliveInterval=30 "$host" "bash -s -- $args" < "$here/remote-run.sh"
1+
#!/bin/bash
2+
# Thin wrapper kept for existing callers: run-offload.sh pinned to radium.
3+
exec "$(dirname "$0")/run-offload.sh" --host radium "$@"

0 commit comments

Comments
 (0)