cgroup: the exec-child cgroup names and the /proc/self/cgroup parser
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 files changed,
+95
−0
internal/cgroup/cgroup.go+52 −0internal/cgroup/cgroup_test.go+43 −0
diff --git a/internal/cgroup/cgroup.go b/internal/cgroup/cgroup.go| new file mode 100644 |
| index 000000000..7562775a8 |
| --- /dev/null |
| +++ b/internal/cgroup/cgroup.go |
| @@ -0,0 +1,52 @@ |
| +// Package cgroup gives each exec-backed child its own cgroup under the |
| +// subtree systemd delegates to carlos-edge.service (Delegate= in the unit |
| +// file), so the metrics sampler can report per-instance figures for routes |
| +// that run as exec children of the agent — today those live inside the |
| +// agent's own cgroup and can never show readings. Design: |
| +// docs/superpowers/specs/2026-08-07-exec-child-accounting-design.md. |
| +// |
| +// The agent is NOT root: everything here works through ownership of the |
| +// delegated subtree, never privilege. When delegation is absent (old unit |
| +// file, a test, darwin) Setup returns a nil *Manager and every method on it |
| +// degrades to today's behavior — fail toward absent readings, never toward |
| +// a broken spawn path. |
| +package cgroup |
| + |
| +import "strings" |
| + |
| +// execPrefix is the ONE definition of the per-host exec cgroup naming — |
| +// activator.ExecName and the sampler's directory match both come through |
| +// this package. |
| +const execPrefix = "carlos-exec@" |
| + |
| +// ExecName is the cgroup directory name — and therefore the metrics sample |
| +// key — for an exec-backed host's children. Deliberately NOT dressed up as |
| +// a fake ".service": an operator who takes a dashboard row to `systemctl |
| +// status` must not be told a unit exists that doesn't. |
| +func ExecName(host string) string { return execPrefix + host } |
| + |
| +// IsExecName reports whether a cgroup directory name is one of our per-host |
| +// exec cgroups: the carlos-exec@ prefix with a non-empty host after it. |
| +func IsExecName(name string) bool { |
| + return strings.HasPrefix(name, execPrefix) && len(name) > len(execPrefix) |
| +} |
| + |
| +// ParseSelfCgroup extracts the cgroup v2 path from /proc/self/cgroup |
| +// content — the "0::" unified-hierarchy line — relative (no leading slash), |
| +// ready to join under /sys/fs/cgroup. Not-ok when there is no v2 line (a |
| +// v1-only box) or the process sits in the root cgroup: neither is a subtree |
| +// systemd could have delegated to us. |
| +func ParseSelfCgroup(data string) (string, bool) { |
| + for _, line := range strings.Split(data, "\n") { |
| + rest, found := strings.CutPrefix(line, "0::") |
| + if !found { |
| + continue |
| + } |
| + p := strings.TrimPrefix(rest, "/") |
| + if p == "" { |
| + return "", false |
| + } |
| + return p, true |
| + } |
| + return "", false |
| +} |
diff --git a/internal/cgroup/cgroup_test.go b/internal/cgroup/cgroup_test.go| new file mode 100644 |
| index 000000000..cba5f40da |
| --- /dev/null |
| +++ b/internal/cgroup/cgroup_test.go |
| @@ -0,0 +1,43 @@ |
| +package cgroup |
| + |
| +import "testing" |
| + |
| +func TestExecNameAndIsExecName(t *testing.T) { |
| + if got := ExecName("keymail.dev"); got != "carlos-exec@keymail.dev" { |
| + t.Fatalf("ExecName = %q, want %q", got, "carlos-exec@keymail.dev") |
| + } |
| + for name, want := range map[string]bool{ |
| + "carlos-exec@keymail.dev": true, |
| + "carlos-exec@": false, // no host — not one of ours |
| + "supervise": false, // the agent's own leaf |
| + "carlos-app@x.service": false, |
| + "cgroup.procs": false, |
| + } { |
| + if got := IsExecName(name); got != want { |
| + t.Errorf("IsExecName(%q) = %v, want %v", name, got, want) |
| + } |
| + } |
| +} |
| + |
| +// TestParseSelfCgroup pins the /proc/self/cgroup v2 line format: the "0::" |
| +// line carries the unified-hierarchy path. v1-only content (no 0:: line) |
| +// and the root cgroup ("0::/") both report not-ok — neither is a subtree |
| +// systemd could have delegated to us. |
| +func TestParseSelfCgroup(t *testing.T) { |
| + cases := []struct { |
| + name, data, want string |
| + ok bool |
| + }{ |
| + {"systemd service", "0::/system.slice/carlos-edge.service\n", "system.slice/carlos-edge.service", true}, |
| + {"hybrid box, v2 line among v1", "12:pids:/system.slice\n0::/system.slice/carlos-edge.service\n", "system.slice/carlos-edge.service", true}, |
| + {"v1 only", "12:pids:/system.slice\n11:cpu:/system.slice\n", "", false}, |
| + {"root cgroup", "0::/\n", "", false}, |
| + {"empty", "", "", false}, |
| + } |
| + for _, c := range cases { |
| + got, ok := ParseSelfCgroup(c.data) |
| + if got != c.want || ok != c.ok { |
| + t.Errorf("%s: ParseSelfCgroup = (%q, %v), want (%q, %v)", c.name, got, ok, c.want, c.ok) |
| + } |
| + } |
| +} |