diff --git a/internal/oci/oci_linux.go b/internal/oci/oci_linux.go index 69cb75b75a6..76d0b4410be 100644 --- a/internal/oci/oci_linux.go +++ b/internal/oci/oci_linux.go @@ -3,9 +3,11 @@ package oci import ( + "bufio" "fmt" "os" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -110,6 +112,18 @@ func (r *runtimeOCI) containerStats(ctr *Container, cgroup string) (*ContainerSt stats.NetInput, stats.NetOutput = getContainerNetIO(netNsPath) } + totalInactiveFile, err := getTotalInactiveFile() + if err != nil { // nolint: gocritic + logrus.Warnf("error in memory working set stats retrieval: %v", err) + } else if stats.MemUsage > totalInactiveFile { + stats.WorkingSetBytes = stats.MemUsage - totalInactiveFile + } else { + logrus.Debugf( + "unable to account working set stats: total_inactive_file (%d) > memory usage (%d)", + totalInactiveFile, stats.MemUsage, + ) + } + return stats, nil } @@ -162,3 +176,40 @@ func metricsToCtrStats(c *Container, m *cgroups.Metrics) *ContainerStats { PIDs: pids, } } + +// getTotalInactiveFile returns the value if `total_inactive_file` as integer +// from `/sys/fs/cgroup/memory/memory.stat`. It returns an error if the file is +// not parsable. +func getTotalInactiveFile() (uint64, error) { + // no cgroupv2 support right now + if isV2, err := cgroups.IsCgroup2UnifiedMode(); err == nil || isV2 { + return 0, nil + } + + const memoryStat = "/sys/fs/cgroup/memory/memory.stat" + const totalInactiveFilePrefix = "total_inactive_file " + f, err := os.Open(memoryStat) + if err != nil { + return 0, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + if strings.Contains(scanner.Text(), totalInactiveFilePrefix) { + val, err := strconv.Atoi( + strings.TrimPrefix(scanner.Text(), totalInactiveFilePrefix), + ) + if err != nil { + return 0, errors.Wrapf(err, "unable to parse total inactive file value") + } + return uint64(val), nil + } + } + + if err := scanner.Err(); err != nil { + return 0, err + } + + return 0, errors.Errorf("%q not found in %v", totalInactiveFilePrefix, memoryStat) +} diff --git a/internal/oci/stats.go b/internal/oci/stats.go index e613a03d54e..3d0701d690d 100644 --- a/internal/oci/stats.go +++ b/internal/oci/stats.go @@ -13,18 +13,19 @@ import ( // ContainerStats contains the statistics information for a running container type ContainerStats struct { - Container string - CPU float64 - CPUNano uint64 - SystemNano int64 - MemUsage uint64 - MemLimit uint64 - MemPerc float64 - NetInput uint64 - NetOutput uint64 - BlockInput uint64 - BlockOutput uint64 - PIDs uint64 + Container string + CPU float64 + CPUNano uint64 + SystemNano int64 + MemUsage uint64 + MemLimit uint64 + MemPerc float64 + NetInput uint64 + NetOutput uint64 + BlockInput uint64 + BlockOutput uint64 + PIDs uint64 + WorkingSetBytes uint64 } // Returns the total number of bytes transmitted and received for the given container stats diff --git a/server/container_stats.go b/server/container_stats.go index fe6a6b37bf7..73d5d7b5957 100644 --- a/server/container_stats.go +++ b/server/container_stats.go @@ -39,7 +39,7 @@ func (s *Server) buildContainerStats(stats *oci.ContainerStats, container *oci.C }, Memory: &pb.MemoryUsage{ Timestamp: stats.SystemNano, - WorkingSetBytes: &pb.UInt64Value{Value: stats.MemUsage}, + WorkingSetBytes: &pb.UInt64Value{Value: stats.WorkingSetBytes}, }, WritableLayer: writableLayer, }