Files
buildkit/executor/resources/memory.go
2025-09-09 14:13:04 +02:00

160 lines
3.6 KiB
Go

package resources
import (
"os"
"path/filepath"
"strconv"
"strings"
resourcestypes "github.com/moby/buildkit/executor/resources/types"
"github.com/pkg/errors"
)
const (
memoryStatFile = "memory.stat"
memoryPressureFile = "memory.pressure"
memoryPeakFile = "memory.peak"
memorySwapCurrentFile = "memory.swap.current"
memoryEventsFile = "memory.events"
)
const (
memoryAnon = "anon"
memoryFile = "file"
memoryKernelStack = "kernel_stack"
memoryPageTables = "pagetables"
memorySock = "sock"
memoryShmem = "shmem"
memoryFileMapped = "file_mapped"
memoryFileDirty = "file_dirty"
memoryFileWriteback = "file_writeback"
memorySlab = "slab"
memoryPgscan = "pgscan"
memoryPgsteal = "pgsteal"
memoryPgfault = "pgfault"
memoryPgmajfault = "pgmajfault"
memoryLow = "low"
memoryHigh = "high"
memoryMax = "max"
memoryOom = "oom"
memoryOomKill = "oom_kill"
)
func getCgroupMemoryStat(path string) (*resourcestypes.MemoryStat, error) {
memoryStat := &resourcestypes.MemoryStat{}
// Parse memory.stat
err := parseKeyValueFile(filepath.Join(path, memoryStatFile), func(key string, value uint64) {
switch key {
case memoryAnon:
memoryStat.Anon = &value
case memoryFile:
memoryStat.File = &value
case memoryKernelStack:
memoryStat.KernelStack = &value
case memoryPageTables:
memoryStat.PageTables = &value
case memorySock:
memoryStat.Sock = &value
case memoryShmem:
memoryStat.Shmem = &value
case memoryFileMapped:
memoryStat.FileMapped = &value
case memoryFileDirty:
memoryStat.FileDirty = &value
case memoryFileWriteback:
memoryStat.FileWriteback = &value
case memorySlab:
memoryStat.Slab = &value
case memoryPgscan:
memoryStat.Pgscan = &value
case memoryPgsteal:
memoryStat.Pgsteal = &value
case memoryPgfault:
memoryStat.Pgfault = &value
case memoryPgmajfault:
memoryStat.Pgmajfault = &value
}
})
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, err
}
pressure, err := parsePressureFile(filepath.Join(path, memoryPressureFile))
if err != nil {
return nil, err
}
if pressure != nil {
memoryStat.Pressure = pressure
}
err = parseKeyValueFile(filepath.Join(path, memoryEventsFile), func(key string, value uint64) {
switch key {
case memoryLow:
memoryStat.LowEvents = value
case memoryHigh:
memoryStat.HighEvents = value
case memoryMax:
memoryStat.MaxEvents = value
case memoryOom:
memoryStat.OomEvents = value
case memoryOomKill:
memoryStat.OomKillEvents = value
}
})
if err != nil {
return nil, err
}
peak, err := parseSingleValueFile(filepath.Join(path, memoryPeakFile))
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
} else {
memoryStat.Peak = &peak
}
swap, err := parseSingleValueFile(filepath.Join(path, memorySwapCurrentFile))
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
} else {
memoryStat.SwapBytes = &swap
}
return memoryStat, nil
}
func parseKeyValueFile(filePath string, callback func(key string, value uint64)) error {
content, err := os.ReadFile(filePath)
if err != nil {
return errors.Wrapf(err, "failed to read %s", filePath)
}
lines := strings.SplitSeq(string(content), "\n")
for line := range lines {
if len(strings.TrimSpace(line)) == 0 {
continue
}
fields := strings.Fields(line)
key := fields[0]
valueStr := fields[1]
value, err := strconv.ParseUint(valueStr, 10, 64)
if err != nil {
return errors.Wrapf(err, "failed to parse value for %s", key)
}
callback(key, value)
}
return nil
}