Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions cli/clistat/cgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"
"strings"

"github.com/hashicorp/go-multierror"
"github.com/spf13/afero"
"golang.org/x/xerrors"
"tailscale.com/types/ptr"
Expand All @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -171,18 +185,28 @@ 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
}
}

// usage is in ns, convert to us
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
Expand Down
29 changes: 29 additions & 0 deletions cli/clistat/stat_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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",
}
)