From 6c3a83ffa17c57fcbd85c9b9d41806d62f83f0c8 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 10 Jun 2026 22:22:59 +0200 Subject: [PATCH] fix(dashboard): correct container-load cgroup path (always-empty LOAD tab) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard LOAD tab always showed "no running agent containers": container_stats::gather() looked for each agent's cgroup at machine.slice/machine-h\x2d.scope, but that path never exists. nixos-container runs `systemd-nspawn --keep-unit` with `Slice = "machine.slice"` (nixpkgs virtualisation/nixos-containers.nix), so --keep-unit means no separate machined `machine-.scope` is created — the container's cgroup IS the launching service unit, `container@.service`, under machine.slice. systemd-machined still logs "New machine " (registration), which is what made the scope-path assumption look plausible, but the cgroup stays on the unit. Fix scope_dir to machine.slice/container@.service and drop the \x2d escaping (the service unit name is used verbatim in the cgroup dir; the journal shows the literal `container@h-.service`). Adds a unit test pinning the path. --- hive-c0re/src/container_stats.rs | 51 +++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/hive-c0re/src/container_stats.rs b/hive-c0re/src/container_stats.rs index bd39097e..0e95bd9d 100644 --- a/hive-c0re/src/container_stats.rs +++ b/hive-c0re/src/container_stats.rs @@ -3,7 +3,11 @@ //! machine directly — same privsep-clean posture as the turn-stats //! sqlite reads (`cpu.stat` / `memory.*` are world-readable; no //! `hive-priv` needed). Runs host-side in hive-c0re, where -//! `/sys/fs/cgroup/machine.slice/` holds the nspawn machine scopes. +//! `/sys/fs/cgroup/machine.slice/` holds the per-container cgroups. Because +//! nixos-container runs `systemd-nspawn --keep-unit` (with +//! `Slice = "machine.slice"`), each container's cgroup is its launching +//! service unit `container@.service` — not a machined +//! `machine-.scope`. See [`scope_dir`]. //! //! No network: agents share the host network namespace //! (`privateNetwork = false`), so there is no per-container net @@ -47,16 +51,22 @@ pub struct ContainerResource { pub mem_max_bytes: Option, } -/// systemd escapes the machine name in the cgroup scope dir -/// (`machine-.scope`). For our machine names — `h-`, -/// agent ∈ `[a-z0-9_-]` — the only character systemd escapes is `-`, -/// which becomes `\x2d` (verified against `systemd-escape`). -fn escape_machine(machine: &str) -> String { - machine.replace('-', "\\x2d") -} - +/// Cgroup directory for a container's machine. +/// +/// nixos-container runs `systemd-nspawn --keep-unit` with +/// `Slice = "machine.slice"` (see nixpkgs +/// `virtualisation/nixos-containers.nix`). `--keep-unit` means nspawn does +/// **not** create a separate machined `machine-.scope` — the +/// container's cgroup *is* the launching service unit, +/// `container@.service`, placed under `machine.slice`. +/// systemd-machined still logs "New machine " (registration), but the +/// cgroup stays on the service unit. So the path is +/// `machine.slice/container@.service`, and the service unit name is +/// used verbatim — no `\x2d` escaping (that only applies when a string is +/// converted *into* a scope/slice unit name, not to an already-formed +/// instance unit; the journal shows the literal `container@h-.service`). fn scope_dir(machine: &str) -> PathBuf { - PathBuf::from(MACHINE_SLICE).join(format!("machine-{}.scope", escape_machine(machine))) + PathBuf::from(MACHINE_SLICE).join(format!("container@{machine}.service")) } /// Read a single unsigned integer from a one-line cgroup file. @@ -175,3 +185,24 @@ pub async fn gather() -> Vec { } out } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn scope_dir_is_the_keep_unit_service_under_machine_slice() { + // nixos-container `--keep-unit` keeps the cgroup on the service + // unit `container@.service` (literal name, no `\x2d`), + // under machine.slice — NOT a machined `machine-.scope`. + assert_eq!( + scope_dir("h-atlas"), + PathBuf::from("/sys/fs/cgroup/machine.slice/container@h-atlas.service") + ); + // Underscores in agent names are likewise verbatim. + assert_eq!( + scope_dir("h-foo_bar"), + PathBuf::from("/sys/fs/cgroup/machine.slice/container@h-foo_bar.service") + ); + } +}