github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/cgroup/manager/v1/fs_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2022 The Katalyst Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package v1 21 22 import ( 23 "errors" 24 "fmt" 25 "path" 26 "path/filepath" 27 "strconv" 28 "strings" 29 "syscall" 30 31 "github.com/containerd/cgroups" 32 libcgroups "github.com/opencontainers/runc/libcontainer/cgroups" 33 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 34 "k8s.io/klog/v2" 35 36 "github.com/kubewharf/katalyst-core/pkg/util/cgroup/common" 37 "github.com/kubewharf/katalyst-core/pkg/util/general" 38 ) 39 40 type manager struct{} 41 42 // NewManager return a manager for cgroupv1 43 func NewManager() *manager { 44 return &manager{} 45 } 46 47 func (m *manager) ApplyMemory(absCgroupPath string, data *common.MemoryData) error { 48 if data.LimitInBytes != 0 { 49 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.limit_in_bytes", strconv.FormatInt(data.LimitInBytes, 10)); err != nil { 50 return err 51 } else if applied { 52 klog.Infof("[CgroupV1] apply memory limit_in_bytes successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.LimitInBytes, oldData) 53 } 54 } 55 56 if data.SoftLimitInBytes > 0 { 57 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.soft_limit_in_bytes", strconv.FormatInt(data.SoftLimitInBytes, 10)); err != nil { 58 return err 59 } else if applied { 60 klog.Infof("[CgroupV1] apply memory soft_limit_in_bytes successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.SoftLimitInBytes, oldData) 61 } 62 } 63 64 if data.TCPMemLimitInBytes > 0 { 65 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(data.TCPMemLimitInBytes, 10)); err != nil { 66 return err 67 } else if applied { 68 klog.Infof("[CgroupV1] apply memory kmem.tcp.limit_in_bytes successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.TCPMemLimitInBytes, oldData) 69 } 70 } 71 72 if data.WmarkRatio != 0 { 73 newRatio := fmt.Sprintf("%d", data.WmarkRatio) 74 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.wmark_ratio", newRatio); err != nil { 75 return err 76 } else if applied { 77 klog.Infof("[CgroupV1] apply memory wmark successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.WmarkRatio, oldData) 78 } 79 } 80 return nil 81 } 82 83 func (m *manager) ApplyCPU(absCgroupPath string, data *common.CPUData) error { 84 lastErrors := []error{} 85 if data.Shares != 0 { 86 shares := data.Shares 87 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.shares", strconv.FormatUint(shares, 10)); err != nil { 88 lastErrors = append(lastErrors, err) 89 } else { 90 sharesRead, err := fscommon.GetCgroupParamUint(absCgroupPath, "cpu.shares") 91 if err != nil { 92 lastErrors = append(lastErrors, err) 93 } else { 94 if shares > sharesRead { 95 lastErrors = append(lastErrors, fmt.Errorf("the maximum allowed cpu-shares is %d", sharesRead)) 96 } else if shares < sharesRead { 97 lastErrors = append(lastErrors, fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)) 98 } 99 if applied { 100 klog.Infof("[CgroupV1] apply cpu share successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.Shares, oldData) 101 } 102 } 103 } 104 } 105 106 if data.CpuPeriod != 0 { 107 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.cfs_period_us", strconv.FormatUint(data.CpuPeriod, 10)); err != nil { 108 lastErrors = append(lastErrors, err) 109 } else if applied { 110 klog.Infof("[CgroupV1] apply cpu cfs_period successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.CpuPeriod, oldData) 111 } 112 } 113 114 if data.CpuQuota != 0 { 115 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.cfs_quota_us", strconv.FormatInt(data.CpuQuota, 10)); err != nil { 116 lastErrors = append(lastErrors, err) 117 } else if applied { 118 klog.Infof("[CgroupV1] apply cpu cfs_quota successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.CpuQuota, oldData) 119 } 120 } 121 122 if data.CpuIdlePtr != nil { 123 var cpuIdleValue int64 124 if *data.CpuIdlePtr { 125 cpuIdleValue = 1 126 } else { 127 cpuIdleValue = 0 128 } 129 130 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.idle", strconv.FormatInt(cpuIdleValue, 10)); err != nil { 131 lastErrors = append(lastErrors, err) 132 } else if applied { 133 klog.Infof("[CgroupV1] apply cpu.idle successfully, cgroupPath: %s, data: %d, old data: %s\n", absCgroupPath, cpuIdleValue, oldData) 134 } 135 } 136 137 if len(lastErrors) == 0 { 138 return nil 139 } 140 141 errMsg := "" 142 for i, err := range lastErrors { 143 if i == 0 { 144 errMsg = fmt.Sprintf("%d.%s", i, err.Error()) 145 } else { 146 errMsg = fmt.Sprintf("%s, %d.%s", errMsg, i, err.Error()) 147 } 148 } 149 return fmt.Errorf("%s", errMsg) 150 } 151 152 func (m *manager) ApplyCPUSet(absCgroupPath string, data *common.CPUSetData) error { 153 if len(data.CPUs) != 0 { 154 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpuset.cpus", data.CPUs); err != nil { 155 return err 156 } else if applied { 157 klog.Infof("[CgroupV1] apply cpuset cpus successfully, cgroupPath: %s, data: %v, old data: %v\n", 158 absCgroupPath, data.CPUs, oldData) 159 } 160 } 161 162 if len(data.Migrate) != 0 { 163 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpuset.memory_migrate", "1"); err != nil { 164 klog.Infof("[CgroupV1] apply cpuset memory migrate failed, cgroupPath: %s, data: %v, old data %v\n", 165 absCgroupPath, data.Migrate, oldData) 166 } else if applied { 167 klog.Infof("[CgroupV1] apply cpuset memory migrate successfully, cgroupPath: %s, data: %v, old data %v\n", 168 absCgroupPath, data.Migrate, oldData) 169 } 170 } 171 172 if len(data.Mems) != 0 { 173 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpuset.mems", data.Mems); err != nil { 174 return err 175 } else if applied { 176 klog.Infof("[CgroupV1] apply cpuset mems successfully, cgroupPath: %s, data: %v, old data: %v\n", 177 absCgroupPath, data.Mems, oldData) 178 } 179 } 180 181 return nil 182 } 183 184 func (m *manager) ApplyNetCls(absCgroupPath string, data *common.NetClsData) error { 185 if data.ClassID != 0 { 186 classID := fmt.Sprintf("%d", data.ClassID) 187 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "net_cls.classid", classID); err != nil { 188 return err 189 } else if applied { 190 klog.Infof("[CgroupV1] apply net cls successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.ClassID, oldData) 191 } 192 } 193 194 return nil 195 } 196 197 func (m *manager) ApplyIOCostQoS(absCgroupPath string, devID string, data *common.IOCostQoSData) error { 198 return errors.New("cgroups v1 does not support io.cost.qos") 199 } 200 201 func (m *manager) ApplyIOCostModel(absCgroupPath string, devID string, data *common.IOCostModelData) error { 202 return errors.New("cgroups v1 does not support io.cost.model") 203 } 204 205 func (m *manager) ApplyIOWeight(absCgroupPath string, devID string, weight uint64) error { 206 return errors.New("cgroups v1 does not support io.weight") 207 } 208 209 func (m *manager) ApplyUnifiedData(absCgroupPath, cgroupFileName, data string) error { 210 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, cgroupFileName, data); err != nil { 211 return err 212 } else if applied { 213 klog.Infof("[CgroupV2] apply unified data successfully,"+ 214 " cgroupPath: %s, data: %v, old data: %v\n", path.Join(absCgroupPath, cgroupFileName), data, oldData) 215 } 216 217 return nil 218 } 219 220 func (m *manager) GetMemory(absCgroupPath string) (*common.MemoryStats, error) { 221 memoryStats := &common.MemoryStats{} 222 moduleName := "memory" 223 224 limitFile := strings.Join([]string{moduleName, "limit_in_bytes"}, ".") 225 limit, err := fscommon.GetCgroupParamUint(absCgroupPath, limitFile) 226 if err != nil { 227 return nil, fmt.Errorf("failed to parse %s, %v", limitFile, err) 228 } 229 memoryStats.Limit = limit 230 231 usageFile := strings.Join([]string{moduleName, "usage_in_bytes"}, ".") 232 usage, err := fscommon.GetCgroupParamUint(absCgroupPath, usageFile) 233 if err != nil { 234 return nil, fmt.Errorf("failed to parse %s, %v", usageFile, err) 235 } 236 memoryStats.Usage = usage 237 238 return memoryStats, nil 239 } 240 241 func (m *manager) GetNumaMemory(absCgroupPath string) (map[int]*common.MemoryNumaMetrics, error) { 242 numaStat, err := common.ParseCgroupNumaValue(absCgroupPath, "memory.numa_stat") 243 if err != nil { 244 return nil, err 245 } 246 247 pageSize := uint64(syscall.Getpagesize()) 248 249 result := make(map[int]*common.MemoryNumaMetrics) 250 if anonStat, ok := numaStat["anon"]; ok { 251 for numaID, value := range anonStat { 252 if _, ok := result[numaID]; !ok { 253 result[numaID] = &common.MemoryNumaMetrics{ 254 Anon: value * pageSize, 255 } 256 } else { 257 result[numaID].Anon = value * pageSize 258 } 259 } 260 } 261 262 if anonStat, ok := numaStat["file"]; ok { 263 for numaID, value := range anonStat { 264 if _, ok := result[numaID]; !ok { 265 result[numaID] = &common.MemoryNumaMetrics{ 266 File: value * pageSize, 267 } 268 } else { 269 result[numaID].File = value * pageSize 270 } 271 } 272 } 273 274 return result, nil 275 } 276 277 func (m *manager) GetCPU(absCgroupPath string) (*common.CPUStats, error) { 278 cpuStats := &common.CPUStats{} 279 280 period, err := fscommon.GetCgroupParamUint(absCgroupPath, "cpu.cfs_period_us") 281 if err != nil { 282 return nil, fmt.Errorf("get cfs period %s err, %v", absCgroupPath, err) 283 } 284 285 quota, err := common.GetCgroupParamInt(absCgroupPath, "cpu.cfs_quota_us") 286 if err != nil { 287 return nil, fmt.Errorf("get cfs quota %s err, %v", absCgroupPath, err) 288 } 289 290 cpuStats.CpuPeriod = period 291 cpuStats.CpuQuota = quota 292 return cpuStats, nil 293 } 294 295 func (m *manager) GetCPUSet(absCgroupPath string) (*common.CPUSetStats, error) { 296 cpusetStats := &common.CPUSetStats{} 297 298 var err error 299 cpusetStats.CPUs, err = fscommon.GetCgroupParamString(absCgroupPath, "cpuset.cpus") 300 if err != nil { 301 return nil, fmt.Errorf("read cpuset.cpus failed with error: %v", err) 302 } 303 304 cpusetStats.Mems, err = fscommon.GetCgroupParamString(absCgroupPath, "cpuset.mems") 305 if err != nil { 306 return nil, fmt.Errorf("read cpuset.mems failed with error: %v", err) 307 } 308 309 return cpusetStats, nil 310 } 311 312 func (m *manager) GetIOCostQoS(absCgroupPath string) (map[string]*common.IOCostQoSData, error) { 313 return nil, errors.New("cgroups v1 does not support io.cost.qos") 314 } 315 316 func (m *manager) GetIOCostModel(absCgroupPath string) (map[string]*common.IOCostModelData, error) { 317 return nil, errors.New("cgroups v1 does not support io.cost.model") 318 } 319 320 func (m *manager) GetDeviceIOWeight(absCgroupPath string, devID string) (uint64, bool, error) { 321 return 0, false, errors.New("cgroups v1 does not support io.weight") 322 } 323 324 func (m *manager) GetIOStat(absCgroupPath string) (map[string]map[string]string, error) { 325 return nil, errors.New("cgroups v1 does not support io.stat") 326 } 327 328 func (m *manager) GetMetrics(relCgroupPath string, subsystemMap map[string]struct{}) (*common.CgroupMetrics, error) { 329 errOmit := func(err error) error { 330 return nil 331 } 332 333 subsystems := make(map[cgroups.Name]struct{}) 334 for subsys := range subsystemMap { 335 subsystems[(cgroups.Name)(subsys)] = struct{}{} 336 } 337 338 control, err := cgroups.Load(newHierarchy(subsystems), cgroups.StaticPath(relCgroupPath)) 339 if err != nil { 340 return nil, err 341 } 342 343 stats, err := control.Stat(errOmit) 344 if err != nil { 345 return nil, err 346 } 347 348 cm := &common.CgroupMetrics{ 349 Memory: &common.MemoryMetrics{}, 350 CPU: &common.CPUMetrics{}, 351 Pid: &common.PidMetrics{}, 352 } 353 for subsys := range subsystems { 354 switch subsys { 355 case cgroups.Memory: 356 if stats.Memory == nil { 357 klog.Infof("[cgroupv1] get cgroup stats memory nil, cgroupPath: %v\n", relCgroupPath) 358 } else { 359 cm.Memory.RSS = stats.Memory.TotalRSS 360 cm.Memory.Cache = stats.Memory.TotalCache 361 cm.Memory.Dirty = stats.Memory.TotalDirty 362 cm.Memory.WriteBack = stats.Memory.TotalWriteback 363 cm.Memory.UsageUsage = stats.Memory.Usage.Usage 364 cm.Memory.KernelUsage = stats.Memory.Kernel.Usage 365 cm.Memory.MemSWUsage = stats.Memory.Swap.Usage 366 } 367 case cgroups.Cpu: 368 if stats.CPU == nil { 369 klog.Infof("[cgroupv1] get cgroup stats cpu nil, cgroupPath: %v\n", relCgroupPath) 370 } else { 371 cm.CPU.UsageTotal = stats.CPU.Usage.Total 372 cm.CPU.UsageKernel = stats.CPU.Usage.Kernel 373 cm.CPU.UsageUser = stats.CPU.Usage.User 374 } 375 case cgroups.Pids: 376 if stats.Pids == nil { 377 klog.Infof("[cgroupv1] get cgroup stats pids nil, cgroupPath: %v\n", relCgroupPath) 378 } else { 379 cm.Pid.Current = stats.Pids.Current 380 cm.Pid.Limit = stats.Pids.Limit 381 } 382 } 383 } 384 return cm, nil 385 } 386 387 // GetPids return pids in current cgroup 388 func (m *manager) GetPids(absCgroupPath string) ([]string, error) { 389 pids, err := libcgroups.GetPids(absCgroupPath) 390 if err != nil { 391 return nil, err 392 } 393 394 return general.IntSliceToStringSlice(pids), nil 395 } 396 397 // GetTasks return all threads in current cgroup 398 func (m *manager) GetTasks(absCgroupPath string) ([]string, error) { 399 file := filepath.Join(absCgroupPath, common.CgroupTasksFileV1) 400 tasks, err := common.ReadTasksFile(file) 401 if err != nil { 402 return nil, err 403 } 404 405 return tasks, nil 406 } 407 408 func newHierarchy(enabled map[cgroups.Name]struct{}) cgroups.Hierarchy { 409 return func() ([]cgroups.Subsystem, error) { 410 ss, err := cgroups.V1() 411 if err != nil { 412 return nil, err 413 } 414 var subsystems []cgroups.Subsystem 415 for _, s := range ss { 416 if _, ok := enabled[s.Name()]; ok { 417 subsystems = append(subsystems, s) 418 } 419 } 420 return subsystems, nil 421 } 422 }