carlos / platform Public

zdd plan 2 task 5: the sampler counts a populated generation parent

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 26a6ef7196529793028ddd5061f58de66d9ebfd8 parent f5df5d6
2 files changed, +49 −1
  • internal/host/metrics_probes.go +14 −1
  • internal/host/metrics_probes_test.go +35 −0
diff --git a/internal/host/metrics_probes.go b/internal/host/metrics_probes.go
index 255e33d9c..6ebe91951 100644
--- a/internal/host/metrics_probes.go
+++ b/internal/host/metrics_probes.go
@@ -265,10 +265,23 @@ func listExecCgroups(root string) []unitCgroup {
}
dir := filepath.Join(root, entry.Name())
procs, err := os.ReadFile(filepath.Join(dir, "cgroup.procs"))
- if err != nil || len(strings.TrimSpace(string(procs))) == 0 {
+ if err != nil {
+ continue
+ }
+ // A generation parent (zero-downtime spec §7.3) holds no process of
+ // its own; its generations do. cgroup.events "populated 1" is the
+ // kernel's word that something under it is alive.
+ if len(strings.TrimSpace(string(procs))) == 0 && !subtreePopulated(dir) {
continue
}
units = append(units, unitCgroup{name: entry.Name(), dir: dir})
}
return units
}
+
+// subtreePopulated reports whether dir's cgroup.events says a process is
+// alive anywhere under it. Unreadable is no: absence is the data.
+func subtreePopulated(dir string) bool {
+ b, err := os.ReadFile(filepath.Join(dir, "cgroup.events"))
+ return err == nil && strings.Contains(string(b), "populated 1")
+}
diff --git a/internal/host/metrics_probes_test.go b/internal/host/metrics_probes_test.go
index 65100a32a..b155c8cee 100644
--- a/internal/host/metrics_probes_test.go
+++ b/internal/host/metrics_probes_test.go
@@ -263,3 +263,38 @@ func TestDBFileBytes(t *testing.T) {
t.Fatalf("a directory = %d, want nil", *got)
}
}
+
+// A generation parent (zero-downtime spec §7.3) holds no process of its
+// own, so its cgroup.procs is empty while its generations run. It must
+// still be sampled, under its own name: its memory, cpu, io and pids
+// files already count the whole subtree.
+func TestListExecCgroupsCountsAGenerationParent(t *testing.T) {
+ root := t.TempDir()
+ mk := func(rel, file, body string) {
+ t.Helper()
+ dir := filepath.Join(root, rel)
+ if err := os.MkdirAll(dir, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if file != "" {
+ if err := os.WriteFile(filepath.Join(dir, file), []byte(body), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }
+ mk("carlos-exec@flat.example.com", "cgroup.procs", "1234\n")
+ mk("carlos-exec@gens.example.com", "cgroup.procs", "")
+ mk("carlos-exec@gens.example.com", "cgroup.events", "populated 1\nfrozen 0\n")
+ mk("carlos-exec@gens.example.com/g7", "cgroup.procs", "5678\n")
+ mk("carlos-exec@empty.example.com", "cgroup.procs", "")
+ mk("carlos-exec@empty.example.com", "cgroup.events", "populated 0\nfrozen 0\n")
+
+ got := listExecCgroups(root)
+ names := map[string]bool{}
+ for _, u := range got {
+ names[u.name] = true
+ }
+ if len(got) != 2 || !names["carlos-exec@flat.example.com"] || !names["carlos-exec@gens.example.com"] {
+ t.Fatalf("sampled %+v, want the flat host and the populated generation parent", got)
+ }
+}