Skip to content

Commit ef7e64f

Browse files
authored
Merge pull request #2839 from giuseppe/cpu-fixes-cgroupv2
helpers: fix reading cpu stats on cgroup v2
2 parents 42c236e + 70b339d commit ef7e64f

23 files changed

+275
-22
lines changed

container/common/helpers.go

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ var bootTime = func() time.Time {
6161
}()
6262

6363
func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem bool) (info.ContainerSpec, error) {
64+
return getSpecInternal(cgroupPaths, machineInfoFactory, hasNetwork, hasFilesystem, cgroups.IsCgroup2UnifiedMode())
65+
}
66+
67+
func getSpecInternal(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem, cgroup2UnifiedMode bool) (info.ContainerSpec, error) {
6468
var spec info.ContainerSpec
6569

6670
// Assume unified hierarchy containers.
@@ -77,7 +81,7 @@ func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoF
7781
// Use clone_children/events as a workaround as it isn't usually modified. It is only likely changed
7882
// immediately after creating a container. If the directory modified time is lower, we use that.
7983
cgroupPathFile := path.Join(cgroupPathDir, "cgroup.clone_children")
80-
if cgroups.IsCgroup2UnifiedMode() {
84+
if cgroup2UnifiedMode {
8185
cgroupPathFile = path.Join(cgroupPathDir, "cgroup.events")
8286
}
8387
fi, err := os.Stat(cgroupPathFile)
@@ -103,17 +107,43 @@ func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoF
103107
cpuRoot, ok := cgroupPaths["cpu"]
104108
if ok {
105109
if utils.FileExists(cpuRoot) {
106-
spec.HasCpu = true
107-
spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares")
108-
spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us")
109-
quota := readString(cpuRoot, "cpu.cfs_quota_us")
110-
111-
if quota != "" && quota != "-1" {
112-
val, err := strconv.ParseUint(quota, 10, 64)
113-
if err != nil {
114-
klog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err)
115-
} else {
116-
spec.Cpu.Quota = val
110+
if cgroup2UnifiedMode {
111+
spec.HasCpu = true
112+
113+
weight := readUInt64(cpuRoot, "cpu.weight")
114+
if weight > 0 {
115+
limit, err := convertCPUWeightToCPULimit(weight)
116+
if err != nil {
117+
klog.Errorf("GetSpec: Failed to read CPULimit from %q: %s", path.Join(cpuRoot, "cpu.weight"), err)
118+
} else {
119+
spec.Cpu.Limit = limit
120+
}
121+
}
122+
max := readString(cpuRoot, "cpu.max")
123+
if max != "" {
124+
splits := strings.SplitN(max, " ", 2)
125+
if len(splits) != 2 {
126+
klog.Errorf("GetSpec: Failed to parse CPUmax from %q", path.Join(cpuRoot, "cpu.max"))
127+
} else {
128+
if splits[0] != "max" {
129+
spec.Cpu.Quota = parseUint64String(splits[0])
130+
}
131+
spec.Cpu.Period = parseUint64String(splits[1])
132+
}
133+
}
134+
} else {
135+
spec.HasCpu = true
136+
spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares")
137+
spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us")
138+
quota := readString(cpuRoot, "cpu.cfs_quota_us")
139+
140+
if quota != "" && quota != "-1" {
141+
val, err := strconv.ParseUint(quota, 10, 64)
142+
if err != nil {
143+
klog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err)
144+
} else {
145+
spec.Cpu.Quota = val
146+
}
117147
}
118148
}
119149
}
@@ -126,7 +156,7 @@ func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoF
126156
if utils.FileExists(cpusetRoot) {
127157
spec.HasCpu = true
128158
mask := ""
129-
if cgroups.IsCgroup2UnifiedMode() {
159+
if cgroup2UnifiedMode {
130160
mask = readString(cpusetRoot, "cpuset.cpus.effective")
131161
} else {
132162
mask = readString(cpusetRoot, "cpuset.cpus")
@@ -138,20 +168,20 @@ func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoF
138168
// Memory
139169
memoryRoot, ok := cgroupPaths["memory"]
140170
if ok {
141-
if !cgroups.IsCgroup2UnifiedMode() {
142-
if utils.FileExists(memoryRoot) {
143-
spec.HasMemory = true
144-
spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes")
145-
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes")
146-
spec.Memory.Reservation = readUInt64(memoryRoot, "memory.soft_limit_in_bytes")
147-
}
148-
} else {
171+
if cgroup2UnifiedMode {
149172
if utils.FileExists(path.Join(memoryRoot, "memory.max")) {
150173
spec.HasMemory = true
151174
spec.Memory.Reservation = readUInt64(memoryRoot, "memory.high")
152175
spec.Memory.Limit = readUInt64(memoryRoot, "memory.max")
153176
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.swap.max")
154177
}
178+
} else {
179+
if utils.FileExists(memoryRoot) {
180+
spec.HasMemory = true
181+
spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes")
182+
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes")
183+
spec.Memory.Reservation = readUInt64(memoryRoot, "memory.soft_limit_in_bytes")
184+
}
155185
}
156186
}
157187

@@ -176,7 +206,7 @@ func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoF
176206
spec.HasFilesystem = hasFilesystem
177207

178208
ioControllerName := "blkio"
179-
if cgroups.IsCgroup2UnifiedMode() {
209+
if cgroup2UnifiedMode {
180210
ioControllerName = "io"
181211
}
182212
if blkioRoot, ok := cgroupPaths[ioControllerName]; ok && utils.FileExists(blkioRoot) {
@@ -201,6 +231,37 @@ func readString(dirpath string, file string) string {
201231
return strings.TrimSpace(string(out))
202232
}
203233

234+
// Convert from [1-10000] to [2-262144]
235+
func convertCPUWeightToCPULimit(weight uint64) (uint64, error) {
236+
const (
237+
// minWeight is the lowest value possible for cpu.weight
238+
minWeight = 1
239+
// maxWeight is the highest value possible for cpu.weight
240+
maxWeight = 10000
241+
)
242+
if weight < minWeight || weight > maxWeight {
243+
return 0, fmt.Errorf("convertCPUWeightToCPULimit: invalid cpu weight: %v", weight)
244+
}
245+
return 2 + ((weight-1)*262142)/9999, nil
246+
}
247+
248+
func parseUint64String(strValue string) uint64 {
249+
if strValue == "max" {
250+
return math.MaxUint64
251+
}
252+
if strValue == "" {
253+
return 0
254+
}
255+
256+
val, err := strconv.ParseUint(strValue, 10, 64)
257+
if err != nil {
258+
klog.Errorf("parseUint64String: Failed to parse int %q: %s", strValue, err)
259+
return 0
260+
}
261+
262+
return val
263+
}
264+
204265
func readUInt64(dirpath string, file string) uint64 {
205266
out := readString(dirpath, file)
206267
if out == "max" {

container/common/helpers_test.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@
1515
package common
1616

1717
import (
18+
"errors"
19+
"math"
20+
"os"
21+
"path/filepath"
1822
"testing"
23+
24+
info "github.com/google/cadvisor/info/v1"
25+
v2 "github.com/google/cadvisor/info/v2"
26+
"github.com/stretchr/testify/assert"
1927
)
2028

2129
func BenchmarkListDirectories(b *testing.B) {
@@ -26,3 +34,166 @@ func BenchmarkListDirectories(b *testing.B) {
2634
}
2735
}
2836
}
37+
38+
func TestConvertCpuWeightToCpuLimit(t *testing.T) {
39+
limit, err := convertCPUWeightToCPULimit(1)
40+
if err != nil {
41+
t.Fatalf("Error in convertCPUWeightToCPULimit: %s", err)
42+
}
43+
if limit != 2 {
44+
t.Fatalf("convertCPUWeightToCPULimit(1) != 2")
45+
}
46+
limit, err = convertCPUWeightToCPULimit(10000)
47+
if err != nil {
48+
t.Fatalf("Error in convertCPUWeightToCPULimit: %s", err)
49+
}
50+
if limit != 262144 {
51+
t.Fatalf("convertCPUWeightToCPULimit(10000) != 262144")
52+
}
53+
_, err = convertCPUWeightToCPULimit(0)
54+
if err == nil {
55+
t.Fatalf("convertCPUWeightToCPULimit(0) must raise an error")
56+
}
57+
_, err = convertCPUWeightToCPULimit(10001)
58+
if err == nil {
59+
t.Fatalf("convertCPUWeightToCPULimit(10001) must raise an error")
60+
}
61+
}
62+
63+
func TestParseUint64String(t *testing.T) {
64+
if parseUint64String("1000") != 1000 {
65+
t.Fatalf("parseUint64String(\"1000\") != 1000")
66+
}
67+
if parseUint64String("-1") != 0 {
68+
t.Fatalf("parseUint64String(\"-1\") != 0")
69+
}
70+
if parseUint64String("0") != 0 {
71+
t.Fatalf("parseUint64String(\"0\") != 0")
72+
}
73+
if parseUint64String("not-a-number") != 0 {
74+
t.Fatalf("parseUint64String(\"not-a-number\") != 0")
75+
}
76+
if parseUint64String(" 1000 ") != 0 {
77+
t.Fatalf("parseUint64String(\" 1000 \") != 0")
78+
}
79+
if parseUint64String("18446744073709551615") != 18446744073709551615 {
80+
t.Fatalf("parseUint64String(\"18446744073709551615\") != 18446744073709551615")
81+
}
82+
}
83+
84+
type mockInfoProvider struct {
85+
options v2.RequestOptions
86+
}
87+
88+
func (m *mockInfoProvider) GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) {
89+
m.options = options
90+
return map[string]*info.ContainerInfo{}, nil
91+
}
92+
93+
func (m *mockInfoProvider) GetVersionInfo() (*info.VersionInfo, error) {
94+
return nil, errors.New("not supported")
95+
}
96+
97+
func (m *mockInfoProvider) GetMachineInfo() (*info.MachineInfo, error) {
98+
return &info.MachineInfo{
99+
NumCores: 7,
100+
}, nil
101+
}
102+
103+
func TestGetSpecCgroupV1(t *testing.T) {
104+
root, err := os.Getwd()
105+
if err != nil {
106+
t.Fatalf("getwd: %s", err)
107+
}
108+
109+
cgroupPaths := map[string]string{
110+
"memory": filepath.Join(root, "test_resources/cgroup_v1/test1/memory"),
111+
"cpu": filepath.Join(root, "test_resources/cgroup_v1/test1/cpu"),
112+
"cpuset": filepath.Join(root, "test_resources/cgroup_v1/test1/cpuset"),
113+
"pids": filepath.Join(root, "test_resources/cgroup_v1/test1/pids"),
114+
}
115+
116+
spec, err := getSpecInternal(cgroupPaths, &mockInfoProvider{}, false, false, false)
117+
assert.Nil(t, err)
118+
119+
assert.True(t, spec.HasMemory)
120+
assert.EqualValues(t, spec.Memory.Limit, 123456789)
121+
assert.EqualValues(t, spec.Memory.SwapLimit, 13579)
122+
assert.EqualValues(t, spec.Memory.Reservation, 24680)
123+
124+
assert.True(t, spec.HasCpu)
125+
assert.EqualValues(t, spec.Cpu.Limit, 1025)
126+
assert.EqualValues(t, spec.Cpu.Period, 100010)
127+
assert.EqualValues(t, spec.Cpu.Quota, 20000)
128+
129+
assert.EqualValues(t, spec.Cpu.Mask, "0-5")
130+
131+
assert.True(t, spec.HasProcesses)
132+
assert.EqualValues(t, spec.Processes.Limit, 1027)
133+
134+
assert.False(t, spec.HasHugetlb)
135+
assert.False(t, spec.HasDiskIo)
136+
}
137+
138+
func TestGetSpecCgroupV2(t *testing.T) {
139+
root, err := os.Getwd()
140+
if err != nil {
141+
t.Fatalf("getwd: %s", err)
142+
}
143+
144+
cgroupPaths := map[string]string{
145+
"memory": filepath.Join(root, "test_resources/cgroup_v2/test1"),
146+
"cpu": filepath.Join(root, "test_resources/cgroup_v2/test1"),
147+
"cpuset": filepath.Join(root, "test_resources/cgroup_v2/test1"),
148+
"pids": filepath.Join(root, "test_resources/cgroup_v2/test1"),
149+
}
150+
151+
spec, err := getSpecInternal(cgroupPaths, &mockInfoProvider{}, false, false, true)
152+
assert.Nil(t, err)
153+
154+
assert.True(t, spec.HasMemory)
155+
assert.EqualValues(t, spec.Memory.Limit, 123456789)
156+
assert.EqualValues(t, spec.Memory.SwapLimit, 13579)
157+
assert.EqualValues(t, spec.Memory.Reservation, 24680)
158+
159+
assert.True(t, spec.HasCpu)
160+
assert.EqualValues(t, spec.Cpu.Limit, 1286)
161+
assert.EqualValues(t, spec.Cpu.Period, 100010)
162+
assert.EqualValues(t, spec.Cpu.Quota, 20000)
163+
164+
assert.EqualValues(t, spec.Cpu.Mask, "0-5")
165+
166+
assert.True(t, spec.HasProcesses)
167+
assert.EqualValues(t, spec.Processes.Limit, 1027)
168+
169+
assert.False(t, spec.HasHugetlb)
170+
assert.False(t, spec.HasDiskIo)
171+
}
172+
173+
func TestGetSpecCgroupV2Max(t *testing.T) {
174+
root, err := os.Getwd()
175+
assert.Nil(t, err)
176+
177+
cgroupPaths := map[string]string{
178+
"memory": filepath.Join(root, "test_resources/cgroup_v2/test2"),
179+
"cpu": filepath.Join(root, "test_resources/cgroup_v2/test2"),
180+
"pids": filepath.Join(root, "test_resources/cgroup_v2/test2"),
181+
}
182+
183+
spec, err := getSpecInternal(cgroupPaths, &mockInfoProvider{}, false, false, true)
184+
assert.Nil(t, err)
185+
186+
max := uint64(math.MaxUint64)
187+
188+
assert.True(t, spec.HasMemory)
189+
assert.EqualValues(t, spec.Memory.Limit, max)
190+
assert.EqualValues(t, spec.Memory.SwapLimit, max)
191+
assert.EqualValues(t, spec.Memory.Reservation, max)
192+
193+
assert.True(t, spec.HasCpu)
194+
assert.EqualValues(t, spec.Cpu.Limit, 1286)
195+
assert.EqualValues(t, spec.Cpu.Period, 100010)
196+
assert.EqualValues(t, spec.Cpu.Quota, 0)
197+
198+
assert.EqualValues(t, spec.Processes.Limit, max)
199+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
100010
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
20000
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1025
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0-5
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
123456789
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
13579
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
24680
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1027

0 commit comments

Comments
 (0)