Skip to content

Commit ab27e12

Browse files
piowagPaweł Szulik
authored andcommitted
Implement GetStat for cpuset cgroup.
Co-authored-by: Piotr Wagner <[email protected]> Signed-off-by: Paweł Szulik <[email protected]>
1 parent 49cc2a2 commit ab27e12

File tree

6 files changed

+347
-2
lines changed

6 files changed

+347
-2
lines changed

events.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ func convertLibcontainerStats(ls *libcontainer.Stats) *types.Stats {
132132
s.CPU.Throttling.ThrottledPeriods = cg.CpuStats.ThrottlingData.ThrottledPeriods
133133
s.CPU.Throttling.ThrottledTime = cg.CpuStats.ThrottlingData.ThrottledTime
134134

135+
s.CPUSet = types.CPUSet(cg.CPUSetStats)
136+
135137
s.Memory.Cache = cg.MemoryStats.Cache
136138
s.Memory.Kernel = convertMemoryEntry(cg.MemoryStats.KernelUsage)
137139
s.Memory.KernelTCP = convertMemoryEntry(cg.MemoryStats.KernelTCPUsage)

libcontainer/cgroups/fs/cpuset.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
package fs
44

55
import (
6+
"fmt"
67
"os"
78
"path/filepath"
9+
"strconv"
10+
"strings"
811

912
"github.com/moby/sys/mountinfo"
1013
"github.com/opencontainers/runc/libcontainer/cgroups"
@@ -39,7 +42,106 @@ func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
3942
return nil
4043
}
4144

45+
func getCpusetStat(path string, filename string) ([]uint16, error) {
46+
var extracted []uint16
47+
fileContent, err := fscommon.GetCgroupParamString(path, filename)
48+
if err != nil {
49+
return extracted, err
50+
}
51+
if len(fileContent) == 0 {
52+
return extracted, fmt.Errorf("%s found to be empty", filepath.Join(path, filename))
53+
}
54+
55+
for _, s := range strings.Split(fileContent, ",") {
56+
splitted := strings.SplitN(s, "-", 3)
57+
switch len(splitted) {
58+
case 3:
59+
return extracted, fmt.Errorf("invalid values in %s", filepath.Join(path, filename))
60+
case 2:
61+
min, err := strconv.ParseUint(splitted[0], 10, 16)
62+
if err != nil {
63+
return extracted, err
64+
}
65+
max, err := strconv.ParseUint(splitted[1], 10, 16)
66+
if err != nil {
67+
return extracted, err
68+
}
69+
if min > max {
70+
return extracted, fmt.Errorf("invalid values in %s", filepath.Join(path, filename))
71+
}
72+
for i := min; i <= max; i++ {
73+
extracted = append(extracted, uint16(i))
74+
}
75+
case 1:
76+
value, err := strconv.ParseUint(s, 10, 16)
77+
if err != nil {
78+
return extracted, err
79+
}
80+
extracted = append(extracted, uint16(value))
81+
}
82+
}
83+
84+
return extracted, nil
85+
}
86+
4287
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
88+
var err error
89+
90+
stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
91+
if err != nil && !errors.Is(err, os.ErrNotExist) {
92+
return err
93+
}
94+
95+
stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
96+
if err != nil && !errors.Is(err, os.ErrNotExist) {
97+
return err
98+
}
99+
100+
stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
101+
if err != nil && !errors.Is(err, os.ErrNotExist) {
102+
return err
103+
}
104+
105+
stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
106+
if err != nil && !errors.Is(err, os.ErrNotExist) {
107+
return err
108+
}
109+
110+
stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
111+
if err != nil && !errors.Is(err, os.ErrNotExist) {
112+
return err
113+
}
114+
115+
stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
116+
if err != nil && !errors.Is(err, os.ErrNotExist) {
117+
return err
118+
}
119+
120+
stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
121+
if err != nil && !errors.Is(err, os.ErrNotExist) {
122+
return err
123+
}
124+
125+
stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
126+
if err != nil && !errors.Is(err, os.ErrNotExist) {
127+
return err
128+
}
129+
130+
stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
131+
if err != nil && !errors.Is(err, os.ErrNotExist) {
132+
return err
133+
}
134+
135+
stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
136+
if err != nil && !errors.Is(err, os.ErrNotExist) {
137+
return err
138+
}
139+
140+
stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
141+
if err != nil && !errors.Is(err, os.ErrNotExist) {
142+
return err
143+
}
144+
43145
return nil
44146
}
45147

libcontainer/cgroups/fs/cpuset_test.go

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,42 @@
33
package fs
44

55
import (
6+
"reflect"
67
"testing"
78

9+
"github.com/opencontainers/runc/libcontainer/cgroups"
810
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
911
)
1012

11-
func TestCpusetSetCpus(t *testing.T) {
13+
const (
14+
cpus = "0-2,7,12-14\n"
15+
cpuExclusive = "1\n"
16+
mems = "1-4,6,9\n"
17+
memHardwall = "0\n"
18+
memExclusive = "0\n"
19+
memoryMigrate = "1\n"
20+
memorySpreadPage = "0\n"
21+
memorySpeadSlab = "1\n"
22+
memoryPressure = "34377\n"
23+
schedLoadBalance = "1\n"
24+
schedRelaxDomainLevel = "-1\n"
25+
)
26+
27+
var cpusetTestFiles = map[string]string{
28+
"cpuset.cpus": cpus,
29+
"cpuset.cpu_exclusive": cpuExclusive,
30+
"cpuset.mems": mems,
31+
"cpuset.mem_hardwall": memHardwall,
32+
"cpuset.mem_exclusive": memExclusive,
33+
"cpuset.memory_migrate": memoryMigrate,
34+
"cpuset.memory_spread_page": memorySpreadPage,
35+
"cpuset.memory_spread_slab": memorySpeadSlab,
36+
"cpuset.memory_pressure": memoryPressure,
37+
"cpuset.sched_load_balance": schedLoadBalance,
38+
"cpuset.sched_relax_domain_level": schedRelaxDomainLevel,
39+
}
40+
41+
func TestCPUSetSetCpus(t *testing.T) {
1242
helper := NewCgroupTestUtil("cpuset", t)
1343
defer helper.cleanup()
1444

@@ -37,7 +67,7 @@ func TestCpusetSetCpus(t *testing.T) {
3767
}
3868
}
3969

40-
func TestCpusetSetMems(t *testing.T) {
70+
func TestCPUSetSetMems(t *testing.T) {
4171
helper := NewCgroupTestUtil("cpuset", t)
4272
defer helper.cleanup()
4373

@@ -65,3 +95,152 @@ func TestCpusetSetMems(t *testing.T) {
6595
t.Fatal("Got the wrong value, set cpuset.mems failed.")
6696
}
6797
}
98+
99+
func TestCPUSetStatsCorrect(t *testing.T) {
100+
helper := NewCgroupTestUtil("cpuset", t)
101+
defer helper.cleanup()
102+
helper.writeFileContents(cpusetTestFiles)
103+
104+
cpuset := &CpusetGroup{}
105+
actualStats := *cgroups.NewStats()
106+
err := cpuset.GetStats(helper.CgroupPath, &actualStats)
107+
if err != nil {
108+
t.Fatal(err)
109+
}
110+
expectedStats := cgroups.CPUSetStats{
111+
CPUs: []uint16{0, 1, 2, 7, 12, 13, 14},
112+
CPUExclusive: 1,
113+
Mems: []uint16{1, 2, 3, 4, 6, 9},
114+
MemoryMigrate: 1,
115+
MemHardwall: 0,
116+
MemExclusive: 0,
117+
MemorySpreadPage: 0,
118+
MemorySpreadSlab: 1,
119+
MemoryPressure: 34377,
120+
SchedLoadBalance: 1,
121+
SchedRelaxDomainLevel: -1}
122+
if !reflect.DeepEqual(expectedStats, actualStats.CPUSetStats) {
123+
t.Fatalf("Expected Cpuset stats usage %#v but found %#v",
124+
expectedStats, actualStats.CPUSetStats)
125+
}
126+
127+
}
128+
129+
func TestCPUSetStatsMissingFiles(t *testing.T) {
130+
for _, testCase := range []struct {
131+
desc string
132+
filename, contents string
133+
removeFile bool
134+
}{
135+
{
136+
desc: "empty cpus file",
137+
filename: "cpuset.cpus",
138+
contents: "",
139+
removeFile: false,
140+
},
141+
{
142+
desc: "empty mems file",
143+
filename: "cpuset.mems",
144+
contents: "",
145+
removeFile: false,
146+
},
147+
{
148+
desc: "corrupted cpus file",
149+
filename: "cpuset.cpus",
150+
contents: "0-3,*4^2",
151+
removeFile: false,
152+
},
153+
{
154+
desc: "corrupted mems file",
155+
filename: "cpuset.mems",
156+
contents: "0,1,2-5,8-7",
157+
removeFile: false,
158+
},
159+
{
160+
desc: "missing cpu_exclusive file",
161+
filename: "cpuset.cpu_exclusive",
162+
contents: "",
163+
removeFile: true,
164+
},
165+
{
166+
desc: "missing memory_migrate file",
167+
filename: "cpuset.memory_migrate",
168+
contents: "",
169+
removeFile: true,
170+
},
171+
{
172+
desc: "missing mem_hardwall file",
173+
filename: "cpuset.mem_hardwall",
174+
contents: "",
175+
removeFile: true,
176+
},
177+
{
178+
desc: "missing mem_exclusive file",
179+
filename: "cpuset.mem_exclusive",
180+
contents: "",
181+
removeFile: true,
182+
},
183+
{
184+
desc: "missing memory_spread_page file",
185+
filename: "cpuset.memory_spread_page",
186+
contents: "",
187+
removeFile: true,
188+
},
189+
{
190+
desc: "missing memory_spread_slab file",
191+
filename: "cpuset.memory_spread_slab",
192+
contents: "",
193+
removeFile: true,
194+
},
195+
{
196+
desc: "missing memory_pressure file",
197+
filename: "cpuset.memory_pressure",
198+
contents: "",
199+
removeFile: true,
200+
},
201+
{
202+
desc: "missing sched_load_balance file",
203+
filename: "cpuset.sched_load_balance",
204+
contents: "",
205+
removeFile: true,
206+
},
207+
{
208+
desc: "missing sched_relax_domain_level file",
209+
filename: "cpuset.sched_relax_domain_level",
210+
contents: "",
211+
removeFile: true,
212+
},
213+
} {
214+
t.Run(testCase.desc, func(t *testing.T) {
215+
helper := NewCgroupTestUtil("cpuset", t)
216+
defer helper.cleanup()
217+
218+
tempCpusetTestFiles := map[string]string{}
219+
for i, v := range cpusetTestFiles {
220+
tempCpusetTestFiles[i] = v
221+
}
222+
223+
if testCase.removeFile {
224+
delete(tempCpusetTestFiles, testCase.filename)
225+
helper.writeFileContents(tempCpusetTestFiles)
226+
cpuset := &CpusetGroup{}
227+
actualStats := *cgroups.NewStats()
228+
err := cpuset.GetStats(helper.CgroupPath, &actualStats)
229+
230+
if err != nil {
231+
t.Errorf("failed unexpectedly: %q", err)
232+
}
233+
} else {
234+
tempCpusetTestFiles[testCase.filename] = testCase.contents
235+
helper.writeFileContents(tempCpusetTestFiles)
236+
cpuset := &CpusetGroup{}
237+
actualStats := *cgroups.NewStats()
238+
err := cpuset.GetStats(helper.CgroupPath, &actualStats)
239+
240+
if err == nil {
241+
t.Error("failed to return expected error")
242+
}
243+
}
244+
})
245+
}
246+
}

libcontainer/cgroups/fscommon/utils.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@ func GetCgroupParamUint(path, file string) (uint64, error) {
7272
return res, nil
7373
}
7474

75+
// GetCgroupParamInt reads a single int64 value from specified cgroup file.
76+
// If the value read is "max", the math.MaxInt64 is returned.
77+
func GetCgroupParamInt(path, file string) (int64, error) {
78+
contents, err := ReadFile(path, file)
79+
if err != nil {
80+
return 0, err
81+
}
82+
contents = strings.TrimSpace(contents)
83+
if contents == "max" {
84+
return math.MaxInt64, nil
85+
}
86+
87+
res, err := strconv.ParseInt(contents, 10, 64)
88+
if err != nil {
89+
return res, fmt.Errorf("unable to parse %q as a int from Cgroup file %q", contents, path+"/"+file)
90+
}
91+
return res, nil
92+
}
93+
7594
// GetCgroupParamString reads a string from the specified cgroup file.
7695
func GetCgroupParamString(path, file string) (string, error) {
7796
contents, err := ReadFile(path, file)

0 commit comments

Comments
 (0)