Skip to main content

Module cgroup

Module cgroup 

Source
Expand description

§cgroup module

Per-service cgroup accounting read straight from cgroupfs instead of D-Bus or varlink (issue #221, unblocking the zero-D-Bus goal in #37).

Seven ServiceStats fields describe a service’s cgroup, and every one of them is a plain cgroup v2 file the kernel already exposes:

ServiceStats fieldcgroupfs source
cpuusage_nseccpu.stat → usage_usec × 1000
memory_currentmemory.current
memory_availablemin(memory.max, memory.high) − memory.current, minimized over the slice walk (see below)
tasks_currentpids.current
processesrecursive cgroup.procs walk + main/control PID fold-in
ioread_bytesio.stat → rbytes summed over devices
ioread_operationsio.stat → rios summed over devices

systemd itself reads these files directly (src/core/cgroup.c: unit_get_cpu_usage_raw, unit_get_memory_accounting, unit_get_memory_available, unit_get_tasks_current, unit_get_io_accounting_raw; D-Bus getters in src/core/dbus-unit.c answer u64::MAX when the read fails). There is no accounting API underneath to depend on — the accounting flags only control whether the controller is enabled, which is itself visible as file presence. Dropping the dependency on per-unit accounting flags being enabled is an explicit goal of #221.

§memory_available semantics

Replicates unit_get_memory_available(): walk the unit’s cgroup directory up through each ancestor slice to the filesystem root; at every level that sets memory.max or memory.high, compute min(max, high) − current with that level’s own memory.current, and take the minimum. The root level (the -​-.slice equivalent) is always constrained: its limit is host physical memory (MemTotal from /proc/meminfo) and its current is host memory pressure — which works out byte-exact to MemAvailable from /proc/meminfo (verified live: D-Bus MemoryAvailable for a limitless service equalled MemAvailable exactly). A unit with no limits anywhere therefore reports host MemAvailable, matching D-Bus; a unit with MemoryMax=1G MemoryHigh=512M reported 536870912 − 1368064 = 535502848, byte-exact with D-Bus.

§Why hand-rolled instead of a cgroupfs crate

The ecosystem crates are all synchronous: cgroups-rs (kata, the most downloaded), Meta’s cgroupfs (the closest miss — fd-based openat reads), and youki’s libcgroups are std::fs under the hood and would need spawn_blocking around the same file reads below, plus extra dependencies (nix, serde, oci-spec, …) and more binary size, against the embedded goal. None of them models memory_available either — that is a systemd concept, not a kernel file, and it is where most of this module’s complexity lives. So: tokio::fs reads issued together with tokio::join!, zero new dependencies, cgroup v2 only.

§fs_root and containers

Like collect_unit_files_stats, every path is prefixed with fs_root — empty for the host, /proc/<leader>/root for containers — so container unit cgroups are readable without entering the PID namespace (#211). The ancestor walk stops at the prefixed root (a container’s systemd never sees host slices above its own root), and the root level always uses the host /proc/meminfo, matching what the container’s PID 1 computes via physical_memory() / procfs_memory_get_used().

§Failure semantics

Every field is Option: None means the file was absent or unparsable, and callers map that to u64::MAX — the [not set] sentinel D-Bus uses. processes is a plain count (0 when the cgroup is gone), matching what GetProcesses returns for a dead unit. Callers keep their existing fallback (D-Bus props on the D-Bus path, the Unit.List reply on the varlink path) for fields that come back None, which is also what covers cgroup v1 hosts: no v2 files → all None → fallback, no new failure mode. memory_available included: the host-MemAvailable root level only binds when the unit’s own level was actually readable, so a missing cgroup reports None there like everywhere else instead of silently answering host memory.

Structs§

CgroupStats
Accounting values for one service’s cgroup, read from cgroupfs.

Functions§

read_host_memory
Read the host root-slice level: (MemTotal, host used bytes).
read_service_cgroup
Read every cgroup accounting file for one service in a single parallel batch.

Type Aliases§

HostMemory
Host-wide memory numbers, read once per collection cycle and shared by every service’s memory_available fold: (MemTotal, host used bytes). See read_host_memory for the semantics.