diff --git a/deploy/deploy-test-server.sh b/deploy/deploy-test-server.sh index fbfb99d..ca80751 100755 --- a/deploy/deploy-test-server.sh +++ b/deploy/deploy-test-server.sh @@ -138,6 +138,34 @@ $sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-web. $sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-server@.service /usr/local/lib/systemd/system/left4me-server@.service $sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/l4d2-game.slice /usr/local/lib/systemd/system/l4d2-game.slice $sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/l4d2-build.slice /usr/local/lib/systemd/system/l4d2-build.slice + +# CPU isolation via cgroup-v2 AllowedCPUs= drop-ins. Pin everything that +# isn't a live game server to core 0; give game servers cores 1..N-1. +# See docs/superpowers/specs/2026-05-09-l4d2-cpu-isolation-design.md. +NPROC=$(nproc) +SYSTEM_CPUS=${LEFT4ME_SYSTEM_CPUS:-0} +if [ "${LEFT4ME_GAME_CPUS+x}" = x ]; then + GAME_CPUS=$LEFT4ME_GAME_CPUS +else + GAME_CPUS="1-$((NPROC - 1))" +fi +if [ "$NPROC" -lt 2 ] && [ "${LEFT4ME_SYSTEM_CPUS+x}${LEFT4ME_GAME_CPUS+x}" = "" ]; then + printf 'left4me deploy: skipping CPU isolation (nproc=%s); cpuset drop-ins not written.\n' "$NPROC" >&2 +else + for slice_drop_in in \ + /etc/systemd/system/system.slice.d/99-left4me-cpuset.conf \ + /etc/systemd/system/user.slice.d/99-left4me-cpuset.conf \ + /etc/systemd/system/l4d2-build.slice.d/99-left4me-cpuset.conf; do + $sudo_cmd mkdir -p "$(dirname "$slice_drop_in")" + printf '[Slice]\nAllowedCPUs=%s\n' "$SYSTEM_CPUS" \ + | $sudo_cmd install -m 0644 -o root -g root /dev/stdin "$slice_drop_in" + done + $sudo_cmd mkdir -p /etc/systemd/system/l4d2-game.slice.d + printf '[Slice]\nAllowedCPUs=%s\n' "$GAME_CPUS" \ + | $sudo_cmd install -m 0644 -o root -g root /dev/stdin \ + /etc/systemd/system/l4d2-game.slice.d/99-left4me-cpuset.conf +fi + $sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-systemctl /usr/local/libexec/left4me/left4me-systemctl $sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-journalctl /usr/local/libexec/left4me/left4me-journalctl $sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-overlay /usr/local/libexec/left4me/left4me-overlay diff --git a/deploy/tests/test_deploy_artifacts.py b/deploy/tests/test_deploy_artifacts.py index 5edfaee..03541ff 100644 --- a/deploy/tests/test_deploy_artifacts.py +++ b/deploy/tests/test_deploy_artifacts.py @@ -170,6 +170,39 @@ def test_deploy_script_installs_perf_artifacts(): assert "sysctl --system" in script +def test_deploy_script_writes_cpuset_drop_ins(): + script = DEPLOY_SCRIPT.read_text() + + # Reads nproc and binds defaults via ${VAR:-...}. + assert "nproc" in script + assert "LEFT4ME_SYSTEM_CPUS" in script + assert "LEFT4ME_GAME_CPUS" in script + assert "${LEFT4ME_SYSTEM_CPUS:-0}" in script + + # Default game-core upper bound is computed from nproc; accept either + # the NPROC-1 form or LEFT4ME_GAME_CPUS:-1- prefix. + assert ( + "1-$((NPROC - 1))" in script + or "1-$((NPROC-1))" in script + or "1-$((nproc-1))" in script + or "LEFT4ME_GAME_CPUS:-1-" in script + ) + + # All four drop-in paths. + for slice_name in ("system", "user", "l4d2-build", "l4d2-game"): + assert ( + f"/etc/systemd/system/{slice_name}.slice.d/99-left4me-cpuset.conf" + in script + ) + + # Drop-ins use the existing install pattern. + assert "install -m 0644 -o root -g root" in script + + # Single-core host: skip with a warning to stderr. + assert ("-lt 2" in script) or ("< 2" in script) or ("-ge 2" in script) + assert "skipping CPU isolation" in script + + def _fake_command(tmp_path, command_name): marker = tmp_path / f"{command_name}.args" command = tmp_path / command_name