Files
buildkit/executor/resources/cpu.go
Tonis Tiigi 96879c682d Update critical containerd and Docker deps
Bump containerd/containerd/api, containerd/containerd/v2, and
docker/cli to current critical direct versions. Regenerate vendor and
accept required transitive/go directive updates from containerd.

Adjust resource pointer helpers for Go 1.26 gopls modernize lint.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2026-07-21 17:29:37 -07:00

142 lines
3.0 KiB
Go

package resources
import (
"bufio"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
resourcestypes "github.com/moby/buildkit/executor/resources/types"
"github.com/pkg/errors"
)
const (
cpuUsageUsec = "usage_usec"
cpuUserUsec = "user_usec"
cpuSystemUsec = "system_usec"
cpuNrPeriods = "nr_periods"
cpuNrThrottled = "nr_throttled"
cpuThrottledUsec = "throttled_usec"
)
func getCgroupCPUStat(cgroupPath string) (*resourcestypes.CPUStat, error) {
cpuStat := &resourcestypes.CPUStat{}
// Read cpu.stat file
cpuStatFile, err := os.Open(filepath.Join(cgroupPath, "cpu.stat"))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, err
}
defer cpuStatFile.Close()
scanner := bufio.NewScanner(cpuStatFile)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
key := fields[0]
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
continue
}
switch key {
case cpuUsageUsec:
cpuStat.UsageNanos = new(value * 1000)
case cpuUserUsec:
cpuStat.UserNanos = new(value * 1000)
case cpuSystemUsec:
cpuStat.SystemNanos = new(value * 1000)
case cpuNrPeriods:
cpuStat.NrPeriods = new(uint32)
*cpuStat.NrPeriods = uint32(value)
case cpuNrThrottled:
cpuStat.NrThrottled = new(uint32)
*cpuStat.NrThrottled = uint32(value)
case cpuThrottledUsec:
cpuStat.ThrottledNanos = new(value * 1000)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
// Read cpu.pressure file
pressure, err := parsePressureFile(filepath.Join(cgroupPath, "cpu.pressure"))
if err == nil {
cpuStat.Pressure = pressure
}
return cpuStat, nil
}
func parsePressureFile(filename string) (*resourcestypes.Pressure, error) {
content, err := os.ReadFile(filename)
if err != nil {
if errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTSUP) { // pressure file requires CONFIG_PSI
return nil, nil
}
return nil, err
}
lines := strings.Split(string(content), "\n")
pressure := &resourcestypes.Pressure{}
for _, line := range lines {
// Skip empty lines
if len(strings.TrimSpace(line)) == 0 {
continue
}
fields := strings.Fields(line)
prefix := fields[0]
pressureValues := &resourcestypes.PressureValues{}
for i := 1; i < len(fields); i++ {
keyValue := strings.Split(fields[i], "=")
key := keyValue[0]
valueStr := keyValue[1]
if key == "total" {
totalValue, err := strconv.ParseUint(valueStr, 10, 64)
if err != nil {
return nil, err
}
pressureValues.Total = &totalValue
} else {
value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return nil, err
}
switch key {
case "avg10":
pressureValues.Avg10 = &value
case "avg60":
pressureValues.Avg60 = &value
case "avg300":
pressureValues.Avg300 = &value
}
}
}
switch prefix {
case "some":
pressure.Some = pressureValues
case "full":
pressure.Full = pressureValues
}
}
return pressure, nil
}