host: scan carlos.slice too so the edge's metrics row survives its move
carlos-edge.service moved into carlos.slice (isolated-exec design §4.3), so its cgroup is now under /sys/fs/cgroup/carlos.slice/carlos-edge.service rather than system.slice. realUnitReadings only scanned system.slice, so the edge's per-unit metrics row silently stopped being discovered once the unit landed there. Add cgroupCarlosSliceDir as a second scan root beside cgroupSliceDir, and have realUnitReadings scan both: system.slice for every platform unit that has not moved (litestream, adopt, relay, ...), and carlos.slice for the edge. The unit-name key stays carlos-edge.service either way, so the reading published under it is unchanged for anything consuming the metrics. Pin the discovery with a fixture-tree test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZ1ZvWj7naf73GjTX8MBv5
3 files changed,
+74
−0
internal/host/metrics_loop.go+6 −0internal/host/metrics_probes.go+13 −0internal/host/metrics_probes_test.go+55 −0
diff --git a/internal/host/metrics_loop.go b/internal/host/metrics_loop.go| index 58a88aadf..fa4d9e44b 100644 |
| --- a/internal/host/metrics_loop.go |
| +++ b/internal/host/metrics_loop.go |
| @@ -657,6 +657,12 @@ func realSampleProbes(execRoot string, stampedUnits func() []string) sampleProbe |
| // second read is redundant, never contradictory. |
| func realUnitReadings(execRoot string, stamped []string) map[string]unitRaw { |
| units := listCarlosUnits(cgroupSliceDir) |
| + // carlos-edge.service moved to carlos.slice (isolated-exec design §4.3; |
| + // see cgroupCarlosSliceDir's doc comment), so it is found only by also |
| + // scanning that second root. system.slice is still scanned above for |
| + // every other platform unit — litestream, adopt, relay and the rest — |
| + // which have not moved. |
| + units = append(units, listCarlosUnits(cgroupCarlosSliceDir)...) |
| units = append(units, listStampedUnits(cgroupSliceDir, stamped)...) |
| units = append(units, listExecCgroups(execRoot)...) |
| if len(units) == 0 { |
diff --git a/internal/host/metrics_probes.go b/internal/host/metrics_probes.go| index 4a43dfcc8..255e33d9c 100644 |
| --- a/internal/host/metrics_probes.go |
| +++ b/internal/host/metrics_probes.go |
| @@ -48,6 +48,19 @@ func readLoadAvg() (l1, l5, l15 *float64) { |
| // a temp-dir fixture instead of the live kernel. |
| const cgroupSliceDir = "/sys/fs/cgroup/system.slice" |
| +// cgroupCarlosSliceDir is the second root listCarlosUnits and |
| +// listStampedUnits must scan: carlos-edge.service now lives in carlos.slice, |
| +// not system.slice, because the isolated-exec design (§4.3) moved the edge |
| +// there to share its memory wall with the exec-child instances it supervises |
| +// (see internal/host/units/carlos-edge.service's Slice=carlos.slice). On a |
| +// flat fleet — no tenant instances yet delegated their own cgroups — the |
| +// edge's own leaf is still the hierarchical total of everything under it, |
| +// delegated exec children included, so publishing that same |
| +// memory.current/cpu.stat/io.stat reading under the unchanged unit-name key |
| +// "carlos-edge.service" preserves the number operators already look at; only |
| +// the directory that number is read from has moved. |
| +const cgroupCarlosSliceDir = "/sys/fs/cgroup/carlos.slice" |
| + |
| // unitCgroup is one discovered CARLOS unit: its systemd unit name (the key |
| // everything downstream — sample maps, the console's activator.UnitName |
| // match — uses) and the cgroup directory that unit's files actually live in. |
diff --git a/internal/host/metrics_probes_test.go b/internal/host/metrics_probes_test.go| index ee1eaa02f..adae5520e 100644 |
| --- a/internal/host/metrics_probes_test.go |
| +++ b/internal/host/metrics_probes_test.go |
| @@ -182,3 +182,58 @@ func TestListExecCgroupsFindsPerHostDirs(t *testing.T) { |
| t.Fatalf("empty root must scan nothing, got %+v", out) |
| } |
| } |
| + |
| +// TestListCarlosUnitsFindsUnitsUnderCarlosSlice pins the review fix: once |
| +// carlos-edge.service moved into carlos.slice (isolated-exec design §4.3), |
| +// realUnitReadings must scan cgroupCarlosSliceDir as a second root or the |
| +// edge's metrics row silently vanishes. listCarlosUnits itself is root- |
| +// agnostic (it takes root as an argument precisely so callers can point it |
| +// at either slice), so this pins that a fixture shaped like the real |
| +// carlos.slice — a flat carlos-edge.service leaf holding memory.current, the |
| +// same file readUnitRaw actually reads — is discovered from that second |
| +// root, and that scanning it does not also resurface anything from a |
| +// system.slice-shaped root scanned separately. |
| +func TestListCarlosUnitsFindsUnitsUnderCarlosSlice(t *testing.T) { |
| + root := t.TempDir() |
| + carlosSlice := filepath.Join(root, "carlos.slice") |
| + edgeDir := filepath.Join(carlosSlice, "carlos-edge.service") |
| + if err := os.MkdirAll(edgeDir, 0o755); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.WriteFile(filepath.Join(edgeDir, "memory.current"), []byte("12345\n"), 0o644); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + got := listCarlosUnits(carlosSlice) |
| + if len(got) != 1 { |
| + t.Fatalf("discovered %d units under carlos.slice, want 1: %+v", len(got), got) |
| + } |
| + if got[0].name != "carlos-edge.service" || got[0].dir != edgeDir { |
| + t.Fatalf("got %+v, want name carlos-edge.service dir %s", got[0], edgeDir) |
| + } |
| + |
| + // A system.slice-shaped root scanned on its own must not also produce |
| + // the edge: the two roots are independent, so an edge found only under |
| + // carlos.slice must not be double-reported when both scans are combined |
| + // the way realUnitReadings combines them. |
| + systemSlice := filepath.Join(root, "system.slice") |
| + if err := os.MkdirAll(systemSlice, 0o755); err != nil { |
| + t.Fatal(err) |
| + } |
| + systemGot := listCarlosUnits(systemSlice) |
| + if len(systemGot) != 0 { |
| + t.Fatalf("system.slice root found %+v, want none: the edge only lives under carlos.slice now", systemGot) |
| + } |
| + |
| + combined := append(append([]unitCgroup{}, systemGot...), got...) |
| + seen := map[string]bool{} |
| + for _, u := range combined { |
| + if seen[u.name] { |
| + t.Fatalf("unit %q double-reported when combining both scan roots: %+v", u.name, combined) |
| + } |
| + seen[u.name] = true |
| + } |
| + if !seen["carlos-edge.service"] { |
| + t.Fatalf("carlos-edge.service missing from the combined scan: %+v", combined) |
| + } |
| +} |