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 field | cgroupfs source |
|---|---|
cpuusage_nsec | cpu.stat → usage_usec × 1000 |
memory_current | memory.current |
memory_available | min(memory.max, memory.high) − memory.current, minimized over the slice walk (see below) |
tasks_current | pids.current |
processes | recursive cgroup.procs walk + main/control PID fold-in |
ioread_bytes | io.stat → rbytes summed over devices |
ioread_operations | io.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§
- Cgroup
Stats - 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§
- Host
Memory - Host-wide memory numbers, read once per collection cycle and shared by
every service’s
memory_availablefold:(MemTotal, host used bytes). Seeread_host_memoryfor the semantics.