Skip to content

Commit

Permalink
Implement GetStat for cpuset cgroup.
Browse files Browse the repository at this point in the history
Co-authored-by: Piotr Wagner <piotr.wagner@intel.com>
Signed-off-by: Paweł Szulik <pawel.szulik@intel.com>
  • Loading branch information
piowag authored and Paweł Szulik committed Jan 19, 2021
1 parent 49cc2a2 commit ab27e12
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 2 deletions.
2 changes: 2 additions & 0 deletions events.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ func convertLibcontainerStats(ls *libcontainer.Stats) *types.Stats {
s.CPU.Throttling.ThrottledPeriods = cg.CpuStats.ThrottlingData.ThrottledPeriods
s.CPU.Throttling.ThrottledTime = cg.CpuStats.ThrottlingData.ThrottledTime

s.CPUSet = types.CPUSet(cg.CPUSetStats)

s.Memory.Cache = cg.MemoryStats.Cache
s.Memory.Kernel = convertMemoryEntry(cg.MemoryStats.KernelUsage)
s.Memory.KernelTCP = convertMemoryEntry(cg.MemoryStats.KernelTCPUsage)
Expand Down
102 changes: 102 additions & 0 deletions libcontainer/cgroups/fs/cpuset.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
package fs

import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"

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

func getCpusetStat(path string, filename string) ([]uint16, error) {
var extracted []uint16
fileContent, err := fscommon.GetCgroupParamString(path, filename)
if err != nil {
return extracted, err
}
if len(fileContent) == 0 {
return extracted, fmt.Errorf("%s found to be empty", filepath.Join(path, filename))
}

for _, s := range strings.Split(fileContent, ",") {
splitted := strings.SplitN(s, "-", 3)
switch len(splitted) {
case 3:
return extracted, fmt.Errorf("invalid values in %s", filepath.Join(path, filename))
case 2:
min, err := strconv.ParseUint(splitted[0], 10, 16)
if err != nil {
return extracted, err
}
max, err := strconv.ParseUint(splitted[1], 10, 16)
if err != nil {
return extracted, err
}
if min > max {
return extracted, fmt.Errorf("invalid values in %s", filepath.Join(path, filename))
}
for i := min; i <= max; i++ {
extracted = append(extracted, uint16(i))
}
case 1:
value, err := strconv.ParseUint(s, 10, 16)
if err != nil {
return extracted, err
}
extracted = append(extracted, uint16(value))
}
}

return extracted, nil
}

func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
var err error

stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}

return nil
}

Expand Down
183 changes: 181 additions & 2 deletions libcontainer/cgroups/fs/cpuset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,42 @@
package fs

import (
"reflect"
"testing"

"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
)

func TestCpusetSetCpus(t *testing.T) {
const (
cpus = "0-2,7,12-14\n"
cpuExclusive = "1\n"
mems = "1-4,6,9\n"
memHardwall = "0\n"
memExclusive = "0\n"
memoryMigrate = "1\n"
memorySpreadPage = "0\n"
memorySpeadSlab = "1\n"
memoryPressure = "34377\n"
schedLoadBalance = "1\n"
schedRelaxDomainLevel = "-1\n"
)

var cpusetTestFiles = map[string]string{
"cpuset.cpus": cpus,
"cpuset.cpu_exclusive": cpuExclusive,
"cpuset.mems": mems,
"cpuset.mem_hardwall": memHardwall,
"cpuset.mem_exclusive": memExclusive,
"cpuset.memory_migrate": memoryMigrate,
"cpuset.memory_spread_page": memorySpreadPage,
"cpuset.memory_spread_slab": memorySpeadSlab,
"cpuset.memory_pressure": memoryPressure,
"cpuset.sched_load_balance": schedLoadBalance,
"cpuset.sched_relax_domain_level": schedRelaxDomainLevel,
}

func TestCPUSetSetCpus(t *testing.T) {
helper := NewCgroupTestUtil("cpuset", t)
defer helper.cleanup()

Expand Down Expand Up @@ -37,7 +67,7 @@ func TestCpusetSetCpus(t *testing.T) {
}
}

func TestCpusetSetMems(t *testing.T) {
func TestCPUSetSetMems(t *testing.T) {
helper := NewCgroupTestUtil("cpuset", t)
defer helper.cleanup()

Expand Down Expand Up @@ -65,3 +95,152 @@ func TestCpusetSetMems(t *testing.T) {
t.Fatal("Got the wrong value, set cpuset.mems failed.")
}
}

func TestCPUSetStatsCorrect(t *testing.T) {
helper := NewCgroupTestUtil("cpuset", t)
defer helper.cleanup()
helper.writeFileContents(cpusetTestFiles)

cpuset := &CpusetGroup{}
actualStats := *cgroups.NewStats()
err := cpuset.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
expectedStats := cgroups.CPUSetStats{
CPUs: []uint16{0, 1, 2, 7, 12, 13, 14},
CPUExclusive: 1,
Mems: []uint16{1, 2, 3, 4, 6, 9},
MemoryMigrate: 1,
MemHardwall: 0,
MemExclusive: 0,
MemorySpreadPage: 0,
MemorySpreadSlab: 1,
MemoryPressure: 34377,
SchedLoadBalance: 1,
SchedRelaxDomainLevel: -1}
if !reflect.DeepEqual(expectedStats, actualStats.CPUSetStats) {
t.Fatalf("Expected Cpuset stats usage %#v but found %#v",
expectedStats, actualStats.CPUSetStats)
}

}

func TestCPUSetStatsMissingFiles(t *testing.T) {
for _, testCase := range []struct {
desc string
filename, contents string
removeFile bool
}{
{
desc: "empty cpus file",
filename: "cpuset.cpus",
contents: "",
removeFile: false,
},
{
desc: "empty mems file",
filename: "cpuset.mems",
contents: "",
removeFile: false,
},
{
desc: "corrupted cpus file",
filename: "cpuset.cpus",
contents: "0-3,*4^2",
removeFile: false,
},
{
desc: "corrupted mems file",
filename: "cpuset.mems",
contents: "0,1,2-5,8-7",
removeFile: false,
},
{
desc: "missing cpu_exclusive file",
filename: "cpuset.cpu_exclusive",
contents: "",
removeFile: true,
},
{
desc: "missing memory_migrate file",
filename: "cpuset.memory_migrate",
contents: "",
removeFile: true,
},
{
desc: "missing mem_hardwall file",
filename: "cpuset.mem_hardwall",
contents: "",
removeFile: true,
},
{
desc: "missing mem_exclusive file",
filename: "cpuset.mem_exclusive",
contents: "",
removeFile: true,
},
{
desc: "missing memory_spread_page file",
filename: "cpuset.memory_spread_page",
contents: "",
removeFile: true,
},
{
desc: "missing memory_spread_slab file",
filename: "cpuset.memory_spread_slab",
contents: "",
removeFile: true,
},
{
desc: "missing memory_pressure file",
filename: "cpuset.memory_pressure",
contents: "",
removeFile: true,
},
{
desc: "missing sched_load_balance file",
filename: "cpuset.sched_load_balance",
contents: "",
removeFile: true,
},
{
desc: "missing sched_relax_domain_level file",
filename: "cpuset.sched_relax_domain_level",
contents: "",
removeFile: true,
},
} {
t.Run(testCase.desc, func(t *testing.T) {
helper := NewCgroupTestUtil("cpuset", t)
defer helper.cleanup()

tempCpusetTestFiles := map[string]string{}
for i, v := range cpusetTestFiles {
tempCpusetTestFiles[i] = v
}

if testCase.removeFile {
delete(tempCpusetTestFiles, testCase.filename)
helper.writeFileContents(tempCpusetTestFiles)
cpuset := &CpusetGroup{}
actualStats := *cgroups.NewStats()
err := cpuset.GetStats(helper.CgroupPath, &actualStats)

if err != nil {
t.Errorf("failed unexpectedly: %q", err)
}
} else {
tempCpusetTestFiles[testCase.filename] = testCase.contents
helper.writeFileContents(tempCpusetTestFiles)
cpuset := &CpusetGroup{}
actualStats := *cgroups.NewStats()
err := cpuset.GetStats(helper.CgroupPath, &actualStats)

if err == nil {
t.Error("failed to return expected error")
}
}
})
}
}
19 changes: 19 additions & 0 deletions libcontainer/cgroups/fscommon/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ func GetCgroupParamUint(path, file string) (uint64, error) {
return res, nil
}

// GetCgroupParamInt reads a single int64 value from specified cgroup file.
// If the value read is "max", the math.MaxInt64 is returned.
func GetCgroupParamInt(path, file string) (int64, error) {
contents, err := ReadFile(path, file)
if err != nil {
return 0, err
}
contents = strings.TrimSpace(contents)
if contents == "max" {
return math.MaxInt64, nil
}

res, err := strconv.ParseInt(contents, 10, 64)
if err != nil {
return res, fmt.Errorf("unable to parse %q as a int from Cgroup file %q", contents, path+"/"+file)
}
return res, nil
}

// GetCgroupParamString reads a string from the specified cgroup file.
func GetCgroupParamString(path, file string) (string, error) {
contents, err := ReadFile(path, file)
Expand Down
Loading

0 comments on commit ab27e12

Please sign in to comment.