Skip to content

Commit

Permalink
cgroupv1: check that cpuset and memory controllers are exported
Browse files Browse the repository at this point in the history
kernel v6.11 introduced new CONFIG_MEMCG_V1 and CONFIG_CPUSETS_V1 [1]
that changed how userspace detects the compiled cgroup controllers,
if the new CONFIG_MEMCG_V1=n is not set then the memory and same for
cpuset controller will not be exported in /proc/cgroups.

Update parseCgroupv1SubSysIds() that parses cgroup controllers to
ensure that we have the memory and cpuset controllers compiled
and exported, and in case of failures report that the user needs
kernel configs:
CONFIG_MEMCG=y and CONFIG_MEMCG_V1=y
CONFIG_CPUSETS=y and CONFIG_CPUSETS_V1=y

We need them to be compiled and exported in /proc/cgroups since we
use the line order and number as an indication for their index in the
css_set to fetch the related subsystem and its cgroup. They are
compiled in configs, same for /proc/cgroups since its content is
compile time generated.

That index allows us to work and track the right cgroup hierarchy in
cgroupv1 otherwise we will operate on the wrong hierarchy that
probably does not have proper cgroup tracking.

The css_set index is saved in the bpf map 'tg_conf_map' in field
tg_cgrpv1_subsys_idx from user space during startup, and used later
by bpf code to track processes and associate related cgroups.

[1] "af000ce85293b8e60"
"cgroup: Do not report unavailable v1 controllers in /proc/cgroups"

Signed-off-by: Djalal Harouni <[email protected]>
  • Loading branch information
tixxdz committed Oct 31, 2024
1 parent 44d0b1d commit 70a9d19
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 14 deletions.
34 changes: 20 additions & 14 deletions pkg/cgroups/cgroups.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ func GetCgroupIdFromPath(cgroupPath string) (uint64, error) {
return fh.Id, nil
}

// parseCgroupv1SubSysIds() parse cgroupv1 controllers and save their
// hierarchy IDs and related css indexes.
// If the 'memory' or 'cpuset' are not detected we fail, as we use them
// from BPF side to gather cgroup information and we need them to be
// exported by the kernel since their corresponding index allows us to
// fetch the cgroup from the corresponding cgroup subsystem state.
func parseCgroupv1SubSysIds(filePath string) error {
var allcontrollers []string

Expand All @@ -198,7 +204,6 @@ func parseCgroupv1SubSysIds(filePath string) error {
defer file.Close()

fscanner := bufio.NewScanner(file)
fixed := false
idx := 0
fscanner.Scan() // ignore first entry
for fscanner.Scan() {
Expand All @@ -224,7 +229,6 @@ func parseCgroupv1SubSysIds(filePath string) error {
CgroupControllers[i].Id = uint32(id)
CgroupControllers[i].Idx = uint32(idx)
CgroupControllers[i].Active = true
fixed = true
} else {
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.fs": cgroupFSPath,
Expand All @@ -241,17 +245,8 @@ func parseCgroupv1SubSysIds(filePath string) error {
"cgroup.controllers": fmt.Sprintf("[%s]", strings.Join(allcontrollers, " ")),
}).Debugf("Cgroupv1 available controllers")

// Could not find 'memory', 'pids' nor 'cpuset' controllers, are they compiled in?
if !fixed {
err = fmt.Errorf("detect cgroupv1 controllers IDs from '%s' failed", filePath)
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.fs": cgroupFSPath,
}).WithError(err).Warnf("Cgroupv1 controllers 'memory', 'pids' and 'cpuset' are missing")
return err
}

for _, controller := range CgroupControllers {
// Print again everything that is available or not
// Print again everything that is available and if not, fail with error
if controller.Active {
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.fs": cgroupFSPath,
Expand All @@ -260,9 +255,20 @@ func parseCgroupv1SubSysIds(filePath string) error {
"cgroup.controller.index": controller.Idx,
}).Infof("Cgroupv1 supported controller '%s' is active on the system", controller.Name)
} else {
var err error
// Warn with error
err = fmt.Errorf("controller '%s' is not active", controller.Name)
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Cgroupv1 supported controller is missing")
if controller.Name == "memory" {
err = fmt.Errorf("Cgroupv1 controller 'memory' is not active, ensure kernel CONFIG_MEMCG=y and CONFIG_MEMCG_V1=y are set")
} else if controller.Name == "cpuset" {
err = fmt.Errorf("Cgroupv1 controller 'cpuset' is not active, ensure kernel CONFIG_CPUSETS=y and CONFIG_CPUSETS_V1=y are set")
} else {
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).Warnf("Cgroupv1 '%s' supported controller is missing", controller.Name)
}

if err != nil {
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Cgroupv1 '%s' supported controller is missing", controller.Name)
return err
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions pkg/cgroups/cgroups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,26 @@ func TestCgroupNameFromCStr(t *testing.T) {
}
}

// Ensure that Cgroupv1 controllers discovery fails if no 'cpuset' and no 'memory'
func TestParseCgroupSubSysIdsWithoutMemoryCpuset(t *testing.T) {
testDir := t.TempDir()
invalid_cgroupv1_controllers :=
`
#subsys_name hierarchy num_cgroups enabled
cpu 6 78 1
cpuacct 6 78 1
blkio 4 78 1
perf_event 8 2 1
`

file := filepath.Join(testDir, "testfile")
err := os.WriteFile(file, []byte(invalid_cgroupv1_controllers), 0644)
require.NoError(t, err)

err = parseCgroupv1SubSysIds(file)
require.Error(t, err)
}

func TestParseCgroupSubSysIds(t *testing.T) {

testDir := t.TempDir()
Expand Down

0 comments on commit 70a9d19

Please sign in to comment.