fix(cli/clistat): read from alternate cgroup path (#8591)

* Attempts reading cgroupv1 quota, period, usage from /sys/fs/cgroup/cpu,cpuacct by default
* Fall back to /sys/fs/cgroup/cpu for v1 quota and period
* Fall back to /sys/fs/cgroup/cpuacct for v1 usage

Fixes https://github.com/coder/coder/issues/8468
This commit is contained in:
Cian Johnston
2023-07-19 16:25:26 +01:00
committed by GitHub
parent 6318c4c09f
commit 0965a2de70
2 changed files with 63 additions and 10 deletions
+34 -10
View File
@@ -6,6 +6,7 @@ import (
"strconv"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/spf13/afero"
"golang.org/x/xerrors"
"tailscale.com/types/ptr"
@@ -15,11 +16,10 @@ import (
// Ref: https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
const (
// CPU usage of all tasks in cgroup in nanoseconds.
cgroupV1CPUAcctUsage = "/sys/fs/cgroup/cpu/cpuacct.usage"
// Alternate path
cgroupV1CPUAcctUsageAlt = "/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage"
cgroupV1CPUAcctUsage = "/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage"
// CFS quota and period for cgroup in MICROseconds
cgroupV1CFSQuotaUs = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us"
cgroupV1CFSQuotaUs = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us"
// CFS period for cgroup in MICROseconds
cgroupV1CFSPeriodUs = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us"
// Maximum memory usable by cgroup in bytes
cgroupV1MemoryMaxUsageBytes = "/sys/fs/cgroup/memory/memory.max_usage_in_bytes"
@@ -153,12 +153,26 @@ func (s *Statter) cGroupV2CPUTotal() (total float64, err error) {
func (s *Statter) cGroupV1CPUTotal() (float64, error) {
periodUs, err := readInt64(s.fs, cgroupV1CFSPeriodUs)
if err != nil {
return 0, xerrors.Errorf("read cpu period: %w", err)
// Try alternate path under /sys/fs/cpu
var merr error
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
periodUs, err = readInt64(s.fs, strings.Replace(cgroupV1CFSPeriodUs, "cpu,cpuacct", "cpu", 1))
if err != nil {
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
return 0, merr
}
}
quotaUs, err := readInt64(s.fs, cgroupV1CFSQuotaUs)
if err != nil {
return 0, xerrors.Errorf("read cpu quota: %w", err)
// Try alternate path under /sys/fs/cpu
var merr error
merr = multierror.Append(merr, xerrors.Errorf("get cpu quota: %w", err))
quotaUs, err = readInt64(s.fs, strings.Replace(cgroupV1CFSQuotaUs, "cpu,cpuacct", "cpu", 1))
if err != nil {
merr = multierror.Append(merr, xerrors.Errorf("get cpu quota: %w", err))
return 0, merr
}
}
if quotaUs < 0 {
@@ -171,10 +185,13 @@ func (s *Statter) cGroupV1CPUTotal() (float64, error) {
func (s *Statter) cGroupV1CPUUsed() (float64, error) {
usageNs, err := readInt64(s.fs, cgroupV1CPUAcctUsage)
if err != nil {
// try alternate path
usageNs, err = readInt64(s.fs, cgroupV1CPUAcctUsageAlt)
// Try alternate path under /sys/fs/cgroup/cpuacct
var merr error
merr = multierror.Append(merr, xerrors.Errorf("read cpu used: %w", err))
usageNs, err = readInt64(s.fs, strings.Replace(cgroupV1CPUAcctUsage, "cpu,cpuacct", "cpuacct", 1))
if err != nil {
return 0, xerrors.Errorf("read cpu used: %w", err)
merr = multierror.Append(merr, xerrors.Errorf("read cpu used: %w", err))
return 0, merr
}
}
@@ -182,7 +199,14 @@ func (s *Statter) cGroupV1CPUUsed() (float64, error) {
usageNs /= 1000
periodUs, err := readInt64(s.fs, cgroupV1CFSPeriodUs)
if err != nil {
return 0, xerrors.Errorf("get cpu period: %w", err)
// Try alternate path under /sys/fs/cpu
var merr error
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
periodUs, err = readInt64(s.fs, strings.Replace(cgroupV1CFSPeriodUs, "cpu,cpuacct", "cpu", 1))
if err != nil {
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
return 0, merr
}
}
return float64(usageNs) / float64(periodUs), nil
+29
View File
@@ -153,6 +153,24 @@ func TestStatter(t *testing.T) {
assert.Equal(t, "cores", cpu.Unit)
})
t.Run("ContainerCPU/AltPath", func(t *testing.T) {
t.Parallel()
fs := initFS(t, fsContainerCgroupV1AltPath)
fakeWait := func(time.Duration) {
// Fake 1 second in ns of usage
mungeFS(t, fs, "/sys/fs/cgroup/cpuacct/cpuacct.usage", "100000000")
}
s, err := New(WithFS(fs), withNproc(2), withWait(fakeWait))
require.NoError(t, err)
cpu, err := s.ContainerCPU()
require.NoError(t, err)
require.NotNil(t, cpu)
assert.Equal(t, 1.0, cpu.Used)
require.NotNil(t, cpu.Total)
assert.Equal(t, 2.5, *cpu.Total)
assert.Equal(t, "cores", cpu.Unit)
})
t.Run("ContainerMemory", func(t *testing.T) {
t.Parallel()
fs := initFS(t, fsContainerCgroupV1)
@@ -366,4 +384,15 @@ proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`,
cgroupV1MemoryUsageBytes: "536870912",
cgroupV1MemoryStat: "total_inactive_file 268435456",
}
fsContainerCgroupV1AltPath = map[string]string{
procOneCgroup: "0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f",
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`,
"/sys/fs/cgroup/cpuacct/cpuacct.usage": "0",
"/sys/fs/cgroup/cpu/cpu.cfs_quota_us": "250000",
"/sys/fs/cgroup/cpu/cpu.cfs_period_us": "100000",
cgroupV1MemoryMaxUsageBytes: "1073741824",
cgroupV1MemoryUsageBytes: "536870912",
cgroupV1MemoryStat: "total_inactive_file 268435456",
}
)