github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs/cpuset.go (about) 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 { 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 }