...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups/fs

     1  package fs
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"golang.org/x/sys/unix"
    11  
    12  	"github.com/opencontainers/runc/libcontainer/cgroups"
    13  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    14  	"github.com/opencontainers/runc/libcontainer/configs"
    15  )
    16  
    17  type CpusetGroup struct{}
    18  
    19  func (s *CpusetGroup) Name() string {
    20  	return "cpuset"
    21  }
    22  
    23  func (s *CpusetGroup) Apply(path string, r *configs.Resources, pid int) error {
    24  	return s.ApplyDir(path, r, pid)
    25  }
    26  
    27  func (s *CpusetGroup) Set(path string, r *configs.Resources) error {
    28  	if r.CpusetCpus != "" {
    29  		if err := cgroups.WriteFile(path, "cpuset.cpus", r.CpusetCpus); err != nil {
    30  			return err
    31  		}
    32  	}
    33  	if r.CpusetMems != "" {
    34  		if err := cgroups.WriteFile(path, "cpuset.mems", r.CpusetMems); err != nil {
    35  			return err
    36  		}
    37  	}
    38  	return nil
    39  }
    40  
    41  func getCpusetStat(path string, file string) ([]uint16, error) {
    42  	var extracted []uint16
    43  	fileContent, err := fscommon.GetCgroupParamString(path, file)
    44  	if err != nil {
    45  		return extracted, err
    46  	}
    47  	if len(fileContent) == 0 {
    48  		return extracted, &parseError{Path: path, File: file, Err: errors.New("empty file")}
    49  	}
    50  
    51  	for _, s := range strings.Split(fileContent, ",") {
    52  		sp := strings.SplitN(s, "-", 3)
    53  		switch len(sp) {
    54  		case 3:
    55  			return extracted, &parseError{Path: path, File: file, Err: errors.New("extra dash")}
    56  		case 2:
    57  			min, err := strconv.ParseUint(sp[0], 10, 16)
    58  			if err != nil {
    59  				return extracted, &parseError{Path: path, File: file, Err: err}
    60  			}
    61  			max, err := strconv.ParseUint(sp[1], 10, 16)
    62  			if err != nil {
    63  				return extracted, &parseError{Path: path, File: file, Err: err}
    64  			}
    65  			if min > max {
    66  				return extracted, &parseError{Path: path, File: file, Err: errors.New("invalid values, min > max")}
    67  			}
    68  			for i := min; i <= max; i++ {
    69  				extracted = append(extracted, uint16(i))
    70  			}
    71  		case 1:
    72  			value, err := strconv.ParseUint(s, 10, 16)
    73  			if err != nil {
    74  				return extracted, &parseError{Path: path, File: file, Err: err}
    75  			}
    76  			extracted = append(extracted, uint16(value))
    77  		}
    78  	}
    79  
    80  	return extracted, nil
    81  }
    82  
    83  func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
    84  	var err error
    85  
    86  	stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
    87  	if err != nil && !errors.Is(err, os.ErrNotExist) {
    88  		return err
    89  	}
    90  
    91  	stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
    92  	if err != nil && !errors.Is(err, os.ErrNotExist) {
    93  		return err
    94  	}
    95  
    96  	stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
    97  	if err != nil && !errors.Is(err, os.ErrNotExist) {
    98  		return err
    99  	}
   100  
   101  	stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
   102  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   103  		return err
   104  	}
   105  
   106  	stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
   107  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   108  		return err
   109  	}
   110  
   111  	stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
   112  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   113  		return err
   114  	}
   115  
   116  	stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
   117  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   118  		return err
   119  	}
   120  
   121  	stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
   122  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   123  		return err
   124  	}
   125  
   126  	stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
   127  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   128  		return err
   129  	}
   130  
   131  	stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
   132  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   133  		return err
   134  	}
   135  
   136  	stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
   137  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   138  		return err
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func (s *CpusetGroup) ApplyDir(dir string, r *configs.Resources, pid int) error {
   145  	// This might happen if we have no cpuset cgroup mounted.
   146  	// Just do nothing and don't fail.
   147  	if dir == "" {
   148  		return nil
   149  	}
   150  	// 'ensureParent' start with parent because we don't want to
   151  	// explicitly inherit from parent, it could conflict with
   152  	// 'cpuset.cpu_exclusive'.
   153  	if err := cpusetEnsureParent(filepath.Dir(dir)); err != nil {
   154  		return err
   155  	}
   156  	if err := os.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) {
   157  		return err
   158  	}
   159  	// We didn't inherit cpuset configs from parent, but we have
   160  	// to ensure cpuset configs are set before moving task into the
   161  	// cgroup.
   162  	// The logic is, if user specified cpuset configs, use these
   163  	// specified configs, otherwise, inherit from parent. This makes
   164  	// cpuset configs work correctly with 'cpuset.cpu_exclusive', and
   165  	// keep backward compatibility.
   166  	if err := s.ensureCpusAndMems(dir, r); err != nil {
   167  		return err
   168  	}
   169  	// Since we are not using apply(), we need to place the pid
   170  	// into the procs file.
   171  	return cgroups.WriteCgroupProc(dir, pid)
   172  }
   173  
   174  func getCpusetSubsystemSettings(parent string) (cpus, mems string, err error) {
   175  	if cpus, err = cgroups.ReadFile(parent, "cpuset.cpus"); err != nil {
   176  		return
   177  	}
   178  	if mems, err = cgroups.ReadFile(parent, "cpuset.mems"); err != nil {
   179  		return
   180  	}
   181  	return cpus, mems, nil
   182  }
   183  
   184  // cpusetEnsureParent makes sure that the parent directories of current
   185  // are created and populated with the proper cpus and mems files copied
   186  // from their respective parent. It does that recursively, starting from
   187  // the top of the cpuset hierarchy (i.e. cpuset cgroup mount point).
   188  func cpusetEnsureParent(current string) error {
   189  	var st unix.Statfs_t
   190  
   191  	parent := filepath.Dir(current)
   192  	err := unix.Statfs(parent, &st)
   193  	if err == nil && st.Type != unix.CGROUP_SUPER_MAGIC {
   194  		return nil
   195  	}
   196  	// Treat non-existing directory as cgroupfs as it will be created,
   197  	// and the root cpuset directory obviously exists.
   198  	if err != nil && err != unix.ENOENT { //nolint:errorlint // unix errors are bare
   199  		return &os.PathError{Op: "statfs", Path: parent, Err: err}
   200  	}
   201  
   202  	if err := cpusetEnsureParent(parent); err != nil {
   203  		return err
   204  	}
   205  	if err := os.Mkdir(current, 0o755); err != nil && !os.IsExist(err) {
   206  		return err
   207  	}
   208  	return cpusetCopyIfNeeded(current, parent)
   209  }
   210  
   211  // cpusetCopyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
   212  // directory to the current directory if the file's contents are 0
   213  func cpusetCopyIfNeeded(current, parent string) error {
   214  	currentCpus, currentMems, err := getCpusetSubsystemSettings(current)
   215  	if err != nil {
   216  		return err
   217  	}
   218  	parentCpus, parentMems, err := getCpusetSubsystemSettings(parent)
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	if isEmptyCpuset(currentCpus) {
   224  		if err := cgroups.WriteFile(current, "cpuset.cpus", parentCpus); err != nil {
   225  			return err
   226  		}
   227  	}
   228  	if isEmptyCpuset(currentMems) {
   229  		if err := cgroups.WriteFile(current, "cpuset.mems", parentMems); err != nil {
   230  			return err
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  func isEmptyCpuset(str string) bool {
   237  	return str == "" || str == "\n"
   238  }
   239  
   240  func (s *CpusetGroup) ensureCpusAndMems(path string, r *configs.Resources) error {
   241  	if err := s.Set(path, r); err != nil {
   242  		return err
   243  	}
   244  	return cpusetCopyIfNeeded(path, filepath.Dir(path))
   245  }
   246  

View as plain text