diff --git a/cli/clistat/cgroup.go b/cli/clistat/cgroup.go index d4fc9976550ce..ffa2cf12b8afd 100644 --- a/cli/clistat/cgroup.go +++ b/cli/clistat/cgroup.go @@ -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 diff --git a/cli/clistat/stat_internal_test.go b/cli/clistat/stat_internal_test.go index 25cb9e099f682..283c455129aa7 100644 --- a/cli/clistat/stat_internal_test.go +++ b/cli/clistat/stat_internal_test.go @@ -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", + } )