diff --git a/cgroupv2.go b/cgroupv2.go index 40e18fd..3c8d6c8 100644 --- a/cgroupv2.go +++ b/cgroupv2.go @@ -179,10 +179,59 @@ func (s cgroupV2Statter) memoryMaxBytes() (*float64, error) { return ptr.To(float64(maxUsageBytes)), nil } -func (s cgroupV2Statter) memory(p Prefix) (*Result, error) { +func (s cgroupV2Statter) memoryCurrentBytes() (int64, error) { memoryUsagePath := filepath.Join(s.path, cgroupV2MemoryUsageBytes) + + currUsageBytes, err := readInt64(s.fs, memoryUsagePath) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return 0, xerrors.Errorf("read memory current: %w", err) + } + + // If the memory current file does not exist, and we have a parent, + // we shall call the parent to find its current memory value. + if s.parent != nil { + result, err := s.parent.memoryCurrentBytes() + if err != nil { + return 0, xerrors.Errorf("read parent memory current: %w", err) + } + return result, nil + } + + // We have no parent, and no memory current file, so return the error. + return 0, xerrors.Errorf("read memory current: %w", err) + } + + return currUsageBytes, nil +} + +func (s cgroupV2Statter) memoryInactiveFileBytes() (int64, error) { memoryStatPath := filepath.Join(s.path, cgroupV2MemoryStat) + inactiveFileBytes, err := readInt64Prefix(s.fs, memoryStatPath, "inactive_file") + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return 0, xerrors.Errorf("read memory stat inactive_file: %w", err) + } + + // If the memory stat file does not exist, and we have a parent, + // we shall call the parent to find its inactive_file value. + if s.parent != nil { + result, err := s.parent.memoryInactiveFileBytes() + if err != nil { + return 0, xerrors.Errorf("read parent memory stat inactive_file: %w", err) + } + return result, nil + } + + // We have no parent, and no memory stat file, so return the error. + return 0, xerrors.Errorf("read memory stat inactive_file: %w", err) + } + + return inactiveFileBytes, nil +} + +func (s cgroupV2Statter) memory(p Prefix) (*Result, error) { // https://docs.kernel.org/6.17/admin-guide/cgroup-v2.html#memory-interface-files r := &Result{ Unit: "B", @@ -194,12 +243,12 @@ func (s cgroupV2Statter) memory(p Prefix) (*Result, error) { r.Total = total } - currUsageBytes, err := readInt64(s.fs, memoryUsagePath) + currUsageBytes, err := s.memoryCurrentBytes() if err != nil { return nil, xerrors.Errorf("read memory usage: %w", err) } - inactiveFileBytes, err := readInt64Prefix(s.fs, memoryStatPath, "inactive_file") + inactiveFileBytes, err := s.memoryInactiveFileBytes() if err != nil { return nil, xerrors.Errorf("read memory stats: %w", err) } diff --git a/stat_internal_test.go b/stat_internal_test.go index 745fa29..cc139de 100644 --- a/stat_internal_test.go +++ b/stat_internal_test.go @@ -491,6 +491,36 @@ func TestStatter(t *testing.T) { assert.Nil(t, mem.Total) assert.Equal(t, "B", mem.Unit) }) + + t.Run("Memory/CurrentInParent", func(t *testing.T) { + t.Parallel() + + fs := initFS(t, fsContainerCgroupV2KubernetesMissingMemoryCurrent) + s, err := New(WithFS(fs), withNoWait, withIsCgroupV2(true)) + require.NoError(t, err) + + mem, err := s.ContainerMemory(PrefixDefault) + require.NoError(t, err) + + require.NotNil(t, mem) + assert.Equal(t, 268435456.0, mem.Used) + assert.Equal(t, "B", mem.Unit) + }) + + t.Run("Memory/StatInParent", func(t *testing.T) { + t.Parallel() + + fs := initFS(t, fsContainerCgroupV2KubernetesMissingMemoryStat) + s, err := New(WithFS(fs), withNoWait, withIsCgroupV2(true)) + require.NoError(t, err) + + mem, err := s.ContainerMemory(PrefixDefault) + require.NoError(t, err) + + require.NotNil(t, mem) + assert.Equal(t, 268435456.0, mem.Used) + assert.Equal(t, "B", mem.Unit) + }) }) }) } @@ -805,6 +835,36 @@ sysboxfs /proc/sys sysboxfs rw,nosuid,nodev,noexec,relatime 0 0`, filepath.Join(cgroupRootPath, "init.scope", cgroupV2MemoryStat): "inactive_file 268435456", filepath.Join(cgroupRootPath, "init.scope", cgroupV2MemoryUsageBytes): "536870912", } + // Variant where child has memory.stat but NOT memory.current (should inherit from root) + fsContainerCgroupV2KubernetesMissingMemoryCurrent = map[string]string{ + procOneCgroup: "0::/", + procSelfCgroup: fmt.Sprintf("0::%s", fsContainerCgroupV2KubernetesPath), + procMounts: `overlay / overlay rw,relatime,lowerdir=/some/path:/some/path,upperdir=/some/path:/some/path,workdir=/some/path:/some/path 0 0 +proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`, + sysCgroupType: "domain", + + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2CPUMax): "max 100000", + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2CPUStat): "usage_usec 0", + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2MemoryMaxBytes): "max", + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2MemoryStat): "inactive_file 268435456", + // memory.current purposefully missing at child - should inherit from root + filepath.Join(cgroupRootPath, cgroupV2MemoryUsageBytes): "536870912", + } + // Variant where child has memory.current but NOT memory.stat (should inherit from root) + fsContainerCgroupV2KubernetesMissingMemoryStat = map[string]string{ + procOneCgroup: "0::/", + procSelfCgroup: fmt.Sprintf("0::%s", fsContainerCgroupV2KubernetesPath), + procMounts: `overlay / overlay rw,relatime,lowerdir=/some/path:/some/path,upperdir=/some/path:/some/path,workdir=/some/path:/some/path 0 0 +proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`, + sysCgroupType: "domain", + + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2CPUMax): "max 100000", + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2CPUStat): "usage_usec 0", + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2MemoryMaxBytes): "max", + filepath.Join(cgroupRootPath, fsContainerCgroupV2KubernetesPath, cgroupV2MemoryUsageBytes): "536870912", + // memory.stat purposefully missing at child - should inherit from root + filepath.Join(cgroupRootPath, cgroupV2MemoryStat): "inactive_file 268435456", + } fsContainerCgroupV1 = map[string]string{ procOneCgroup: "0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f", procSelfCgroup: "0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f",