Skip to content

Add parsing of memory.events max into Failcnt for cgroup v2 #19

@mansplace

Description

@mansplace

In cgroup v2, memory.failcnt is unavailable; memory.events provides the max failures count.
This patch updates getMemoryDataV2 to read the 'max' field from memory.events and populate MemoryData.Failcnt.
Includes TestStatMemoryPodCgroupFailcnt to cover both presence and absence of memory.events.

diff --git a/fs2/memory.go b/fs2/memory.go
index 7613307..47891ef 100644
--- a/fs2/memory.go
+++ b/fs2/memory.go
@@ -170,6 +170,13 @@ func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
        }
        memoryData.MaxUsage = value
 
+       // Read failcnt (max events) from memory.events for cgroup v2
+       if failcnt, err := fscommon.GetValueByKey(path, "memory.events", "max"); err == nil {
+               memoryData.Failcnt = failcnt
+       } else if !os.IsNotExist(err) {
+               return cgroups.MemoryData{}, err
+       }
+
        return memoryData, nil
 }
 
diff --git a/fs2/memory_test.go b/fs2/memory_test.go
index e46dbe6..1a7c6c9 100644
--- a/fs2/memory_test.go
+++ b/fs2/memory_test.go
@@ -125,6 +125,62 @@ func TestStatMemoryPodCgroup(t *testing.T) {
        }
 }
 
+// Test that memory.events max field is parsed into Failcnt for cgroup v2
+func TestStatMemoryPodCgroupFailcnt(t *testing.T) {
+       // Use a fake cgroupfs.
+       cgroups.TestMode = true
+       fakeCgroupDir := t.TempDir()
+
+       // Prepare minimal cgroup v2 files.
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.stat"), []byte(exampleMemoryStatData), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.current"), []byte("1"), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.max"), []byte("2"), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.peak"), []byte("3"), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       // Write memory.events with max field
+       events := "other_event 5\nmax 42\nsome_event 7\n"
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.events"), []byte(events), 0o644); err != nil {
+               t.Fatal(err)
+       }
+
+       gotStats := cgroups.NewStats()
+       if err := statMemory(fakeCgroupDir, gotStats); err != nil {
+               t.Fatalf("unexpected error: %v", err)
+       }
+       if got := gotStats.MemoryStats.Usage.Failcnt; got != 42 {
+               t.Errorf("expected Failcnt 42, got %d", got)
+       }
+
+       // Missing memory.events should not error and Failcnt remains zero
+       fakeCgroupDir = t.TempDir()
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.stat"), []byte(exampleMemoryStatData), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.current"), []byte("10"), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.max"), []byte("20"), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.peak"), []byte("30"), 0o644); err != nil {
+               t.Fatal(err)
+       }
+       gotStats = cgroups.NewStats()
+       if err := statMemory(fakeCgroupDir, gotStats); err != nil {
+               t.Fatalf("unexpected error when memory.events missing: %v", err)
+       }
+       if got := gotStats.MemoryStats.Usage.Failcnt; got != 0 {
+               t.Errorf("expected Failcnt 0 when memory.events missing, got %d", got)
+       }
+}
+
 func TestRootStatsFromMeminfo(t *testing.T) {
        stats := &cgroups.Stats{
                MemoryStats: cgroups.MemoryStats{

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions