Skip to content

Commit 0965a2d

Browse files
authored
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 #8468
1 parent 6318c4c commit 0965a2d

File tree

2 files changed

+63
-10
lines changed

2 files changed

+63
-10
lines changed

cli/clistat/cgroup.go

+34-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strconv"
77
"strings"
88

9+
"github.com/hashicorp/go-multierror"
910
"github.com/spf13/afero"
1011
"golang.org/x/xerrors"
1112
"tailscale.com/types/ptr"
@@ -15,11 +16,10 @@ import (
1516
// Ref: https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
1617
const (
1718
// CPU usage of all tasks in cgroup in nanoseconds.
18-
cgroupV1CPUAcctUsage = "/sys/fs/cgroup/cpu/cpuacct.usage"
19-
// Alternate path
20-
cgroupV1CPUAcctUsageAlt = "/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage"
19+
cgroupV1CPUAcctUsage = "/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage"
2120
// CFS quota and period for cgroup in MICROseconds
22-
cgroupV1CFSQuotaUs = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us"
21+
cgroupV1CFSQuotaUs = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us"
22+
// CFS period for cgroup in MICROseconds
2323
cgroupV1CFSPeriodUs = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us"
2424
// Maximum memory usable by cgroup in bytes
2525
cgroupV1MemoryMaxUsageBytes = "/sys/fs/cgroup/memory/memory.max_usage_in_bytes"
@@ -153,12 +153,26 @@ func (s *Statter) cGroupV2CPUTotal() (total float64, err error) {
153153
func (s *Statter) cGroupV1CPUTotal() (float64, error) {
154154
periodUs, err := readInt64(s.fs, cgroupV1CFSPeriodUs)
155155
if err != nil {
156-
return 0, xerrors.Errorf("read cpu period: %w", err)
156+
// Try alternate path under /sys/fs/cpu
157+
var merr error
158+
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
159+
periodUs, err = readInt64(s.fs, strings.Replace(cgroupV1CFSPeriodUs, "cpu,cpuacct", "cpu", 1))
160+
if err != nil {
161+
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
162+
return 0, merr
163+
}
157164
}
158165

159166
quotaUs, err := readInt64(s.fs, cgroupV1CFSQuotaUs)
160167
if err != nil {
161-
return 0, xerrors.Errorf("read cpu quota: %w", err)
168+
// Try alternate path under /sys/fs/cpu
169+
var merr error
170+
merr = multierror.Append(merr, xerrors.Errorf("get cpu quota: %w", err))
171+
quotaUs, err = readInt64(s.fs, strings.Replace(cgroupV1CFSQuotaUs, "cpu,cpuacct", "cpu", 1))
172+
if err != nil {
173+
merr = multierror.Append(merr, xerrors.Errorf("get cpu quota: %w", err))
174+
return 0, merr
175+
}
162176
}
163177

164178
if quotaUs < 0 {
@@ -171,18 +185,28 @@ func (s *Statter) cGroupV1CPUTotal() (float64, error) {
171185
func (s *Statter) cGroupV1CPUUsed() (float64, error) {
172186
usageNs, err := readInt64(s.fs, cgroupV1CPUAcctUsage)
173187
if err != nil {
174-
// try alternate path
175-
usageNs, err = readInt64(s.fs, cgroupV1CPUAcctUsageAlt)
188+
// Try alternate path under /sys/fs/cgroup/cpuacct
189+
var merr error
190+
merr = multierror.Append(merr, xerrors.Errorf("read cpu used: %w", err))
191+
usageNs, err = readInt64(s.fs, strings.Replace(cgroupV1CPUAcctUsage, "cpu,cpuacct", "cpuacct", 1))
176192
if err != nil {
177-
return 0, xerrors.Errorf("read cpu used: %w", err)
193+
merr = multierror.Append(merr, xerrors.Errorf("read cpu used: %w", err))
194+
return 0, merr
178195
}
179196
}
180197

181198
// usage is in ns, convert to us
182199
usageNs /= 1000
183200
periodUs, err := readInt64(s.fs, cgroupV1CFSPeriodUs)
184201
if err != nil {
185-
return 0, xerrors.Errorf("get cpu period: %w", err)
202+
// Try alternate path under /sys/fs/cpu
203+
var merr error
204+
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
205+
periodUs, err = readInt64(s.fs, strings.Replace(cgroupV1CFSPeriodUs, "cpu,cpuacct", "cpu", 1))
206+
if err != nil {
207+
merr = multierror.Append(merr, xerrors.Errorf("get cpu period: %w", err))
208+
return 0, merr
209+
}
186210
}
187211

188212
return float64(usageNs) / float64(periodUs), nil

cli/clistat/stat_internal_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@ func TestStatter(t *testing.T) {
153153
assert.Equal(t, "cores", cpu.Unit)
154154
})
155155

156+
t.Run("ContainerCPU/AltPath", func(t *testing.T) {
157+
t.Parallel()
158+
fs := initFS(t, fsContainerCgroupV1AltPath)
159+
fakeWait := func(time.Duration) {
160+
// Fake 1 second in ns of usage
161+
mungeFS(t, fs, "/sys/fs/cgroup/cpuacct/cpuacct.usage", "100000000")
162+
}
163+
s, err := New(WithFS(fs), withNproc(2), withWait(fakeWait))
164+
require.NoError(t, err)
165+
cpu, err := s.ContainerCPU()
166+
require.NoError(t, err)
167+
require.NotNil(t, cpu)
168+
assert.Equal(t, 1.0, cpu.Used)
169+
require.NotNil(t, cpu.Total)
170+
assert.Equal(t, 2.5, *cpu.Total)
171+
assert.Equal(t, "cores", cpu.Unit)
172+
})
173+
156174
t.Run("ContainerMemory", func(t *testing.T) {
157175
t.Parallel()
158176
fs := initFS(t, fsContainerCgroupV1)
@@ -366,4 +384,15 @@ proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`,
366384
cgroupV1MemoryUsageBytes: "536870912",
367385
cgroupV1MemoryStat: "total_inactive_file 268435456",
368386
}
387+
fsContainerCgroupV1AltPath = map[string]string{
388+
procOneCgroup: "0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f",
389+
procMounts: `overlay / overlay rw,relatime,lowerdir=/some/path:/some/path,upperdir=/some/path:/some/path,workdir=/some/path:/some/path 0 0
390+
proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`,
391+
"/sys/fs/cgroup/cpuacct/cpuacct.usage": "0",
392+
"/sys/fs/cgroup/cpu/cpu.cfs_quota_us": "250000",
393+
"/sys/fs/cgroup/cpu/cpu.cfs_period_us": "100000",
394+
cgroupV1MemoryMaxUsageBytes: "1073741824",
395+
cgroupV1MemoryUsageBytes: "536870912",
396+
cgroupV1MemoryStat: "total_inactive_file 268435456",
397+
}
369398
)

0 commit comments

Comments
 (0)