github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/cgroup/manager/v2/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 v2 21 22 import ( 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "log" 27 "math" 28 "os" 29 "path" 30 "path/filepath" 31 "strconv" 32 "strings" 33 34 cgroupsv2 "github.com/containerd/cgroups/v2" 35 libcgroups "github.com/opencontainers/runc/libcontainer/cgroups" 36 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 37 "k8s.io/klog/v2" 38 39 "github.com/kubewharf/katalyst-core/pkg/util/cgroup/common" 40 "github.com/kubewharf/katalyst-core/pkg/util/general" 41 ) 42 43 type manager struct{} 44 45 // NewManager return a manager for cgroupv2 46 func NewManager() *manager { 47 return &manager{} 48 } 49 50 func (m *manager) ApplyMemory(absCgroupPath string, data *common.MemoryData) error { 51 if data.LimitInBytes != 0 { 52 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.max", numToStr(data.LimitInBytes)); err != nil { 53 return err 54 } else if applied { 55 klog.Infof("[CgroupV2] apply memory max successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.LimitInBytes, oldData) 56 } 57 } 58 59 if data.SoftLimitInBytes > 0 { 60 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.low", numToStr(data.SoftLimitInBytes)); err != nil { 61 return err 62 } else if applied { 63 klog.Infof("[CgroupV2] apply memory low successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.SoftLimitInBytes, oldData) 64 } 65 } 66 67 if data.MinInBytes > 0 { 68 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.min", numToStr(data.MinInBytes)); err != nil { 69 return err 70 } else if applied { 71 klog.Infof("[CgroupV2] apply memory min successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.MinInBytes, oldData) 72 } 73 } 74 75 if data.WmarkRatio != 0 { 76 newRatio := fmt.Sprintf("%d", data.WmarkRatio) 77 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.wmark_ratio", newRatio); err != nil { 78 return err 79 } else if applied { 80 klog.Infof("[CgroupV2] apply memory wmark successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.WmarkRatio, oldData) 81 } 82 } 83 84 if data.SwapMaxInBytes != 0 { 85 // Do Not change swap max setting if SwapMaxInBytes equals to 0 86 var swapMax int64 = 0 87 if data.SwapMaxInBytes > 0 { 88 swapMax = data.SwapMaxInBytes 89 } 90 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "memory.swap.max", fmt.Sprintf("%d", swapMax)); err != nil { 91 return err 92 } else if applied { 93 klog.Infof("[CgroupV2] apply memory swap max successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, swapMax, oldData) 94 } 95 } 96 return nil 97 } 98 99 func (m *manager) ApplyCPU(absCgroupPath string, data *common.CPUData) error { 100 lastErrors := []error{} 101 cpuWeight := libcgroups.ConvertCPUSharesToCgroupV2Value(data.Shares) 102 if cpuWeight != 0 { 103 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.weight", strconv.FormatUint(cpuWeight, 10)); err != nil { 104 lastErrors = append(lastErrors, err) 105 } else if applied { 106 klog.Infof("[CgroupV2] apply cpu weight successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, cpuWeight, oldData) 107 } 108 } 109 110 if data.CpuQuota != 0 || data.CpuPeriod != 0 { 111 str := "max" 112 if data.CpuQuota > 0 { 113 str = strconv.FormatInt(data.CpuQuota, 10) 114 } 115 period := data.CpuPeriod 116 if period == 0 { 117 period = 100000 118 } 119 120 // refer to https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html 121 str += " " + strconv.FormatUint(period, 10) 122 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.max", str); err != nil { 123 lastErrors = append(lastErrors, err) 124 } else if applied { 125 klog.Infof("[CgroupV2] apply cpu max successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, str, oldData) 126 } 127 } 128 129 if data.CpuIdlePtr != nil { 130 var cpuIdleValue int64 131 if *data.CpuIdlePtr { 132 cpuIdleValue = 1 133 } else { 134 cpuIdleValue = 0 135 } 136 137 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpu.idle", strconv.FormatInt(cpuIdleValue, 10)); err != nil { 138 lastErrors = append(lastErrors, err) 139 } else if applied { 140 klog.Infof("[CgroupV2] apply cpu.idle successfully, cgroupPath: %s, data: %d, old data: %s\n", absCgroupPath, cpuIdleValue, oldData) 141 } 142 } 143 144 if len(lastErrors) == 0 { 145 return nil 146 } 147 148 errMsg := "" 149 for i, err := range lastErrors { 150 if i == 0 { 151 errMsg = fmt.Sprintf("%d.%s", i, err.Error()) 152 } else { 153 errMsg = fmt.Sprintf("%s, %d.%s", errMsg, i, err.Error()) 154 } 155 } 156 return fmt.Errorf("%s", errMsg) 157 } 158 159 func (m *manager) ApplyCPUSet(absCgroupPath string, data *common.CPUSetData) error { 160 if len(data.CPUs) != 0 { 161 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpuset.cpus", data.CPUs); err != nil { 162 return err 163 } else if applied { 164 klog.Infof("[CgroupV2] apply cpuset cpus successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.CPUs, oldData) 165 } 166 } 167 168 if len(data.Migrate) != 0 { 169 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpuset.memory_migrate", "1"); err != nil { 170 klog.Infof("[CgroupV2] apply cpuset memory migrate failed, cgroupPath: %s, data: %v, old data %v\n", absCgroupPath, data.Migrate, oldData) 171 } else if applied { 172 klog.Infof("[CgroupV2] apply cpuset memory migrate successfully, cgroupPath: %s, data: %v, old data %v\n", absCgroupPath, data.Migrate, oldData) 173 } 174 } 175 176 if len(data.Mems) != 0 { 177 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "cpuset.mems", data.Mems); err != nil { 178 return err 179 } else if applied { 180 klog.Infof("[CgroupV2] apply cpuset mems successfully, cgroupPath: %s, data: %v, old data: %v\n", absCgroupPath, data.Mems, oldData) 181 } 182 } 183 184 return nil 185 } 186 187 func (m *manager) ApplyNetCls(_ string, _ *common.NetClsData) error { 188 return errors.New("cgroups v2 does not support net_cls cgroup, please use eBPF via external manager") 189 } 190 191 func (m *manager) ApplyIOCostQoS(absCgroupPath string, devID string, data *common.IOCostQoSData) error { 192 if data == nil { 193 return fmt.Errorf("ApplyIOCostQoS got nil data") 194 } 195 196 dataContent := data.String() 197 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "io.cost.qos", fmt.Sprintf("%s %s", devID, dataContent)); err != nil { 198 return err 199 } else if applied { 200 klog.Infof("[CgroupV2] apply io.cost.qos data successfully,"+ 201 "cgroupPath: %s, data: %s, old data: %s\n", absCgroupPath, dataContent, oldData) 202 } 203 204 return nil 205 } 206 207 func (m *manager) ApplyIOCostModel(absCgroupPath string, devID string, data *common.IOCostModelData) error { 208 if data == nil { 209 return fmt.Errorf("ApplyIOCostModel got nil data") 210 } 211 212 dataContent := data.String() 213 if data.CtrlMode == common.IOCostCtrlModeAuto { 214 dataContent = "ctrl=auto" 215 } 216 217 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "io.cost.model", fmt.Sprintf("%s %s", devID, dataContent)); err != nil { 218 return err 219 } else if applied { 220 klog.Infof("[CgroupV2] apply io.cost.model data successfully,"+ 221 "cgroupPath: %s, data: %s, old data: %s\n", absCgroupPath, dataContent, oldData) 222 } 223 224 return nil 225 } 226 227 func (m *manager) ApplyIOWeight(absCgroupPath string, devID string, weight uint64) error { 228 dataContent := fmt.Sprintf("%s %d", devID, weight) 229 230 curWight, found, err := m.GetDeviceIOWeight(absCgroupPath, devID) 231 if err != nil { 232 return fmt.Errorf("try GetDeviceIOWeight before ApplyIOWeight failed with error: %v", err) 233 } 234 235 if found && curWight == weight { 236 klog.Infof("[CgroupV2] io.weight: %d in cgroupPath: %s for device: %s isn't changed, not to apply it", 237 curWight, absCgroupPath, devID) 238 return nil 239 } 240 241 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, "io.weight", dataContent); err != nil { 242 return err 243 } else if applied { 244 klog.Infof("[CgroupV2] apply io.weight for device: %s successfully,"+ 245 "cgroupPath: %s, added data: %s, old data: %s\n", devID, absCgroupPath, dataContent, oldData) 246 } 247 248 return nil 249 } 250 251 func (m *manager) ApplyUnifiedData(absCgroupPath, cgroupFileName, data string) error { 252 if err, applied, oldData := common.WriteFileIfChange(absCgroupPath, cgroupFileName, data); err != nil { 253 return err 254 } else if applied { 255 klog.Infof("[CgroupV2] apply unified data successfully,"+ 256 " cgroupPath: %s, data: %v, old data: %v\n", path.Join(absCgroupPath, cgroupFileName), data, oldData) 257 } 258 259 return nil 260 } 261 262 func (m *manager) GetMemory(absCgroupPath string) (*common.MemoryStats, error) { 263 memoryStats := &common.MemoryStats{} 264 moduleName := "memory" 265 266 limit := strings.Join([]string{moduleName, "max"}, ".") 267 value, err := fscommon.GetCgroupParamUint(absCgroupPath, limit) 268 if err != nil { 269 return nil, fmt.Errorf("get memory %s err, %v", absCgroupPath, err) 270 } 271 memoryStats.Limit = value 272 273 usageFile := strings.Join([]string{moduleName, "current"}, ".") 274 usage, err := fscommon.GetCgroupParamUint(absCgroupPath, usageFile) 275 if err != nil { 276 return nil, fmt.Errorf("failed to parse %s, %v", usageFile, err) 277 } 278 memoryStats.Usage = usage 279 280 return memoryStats, nil 281 } 282 283 func (m *manager) GetNumaMemory(absCgroupPath string) (map[int]*common.MemoryNumaMetrics, error) { 284 numaStat, err := common.ParseCgroupNumaValue(absCgroupPath, "memory.numa_stat") 285 general.Infof("get cgroup %+v numa stat %+v", absCgroupPath, numaStat) 286 if err != nil { 287 return nil, err 288 } 289 290 result := make(map[int]*common.MemoryNumaMetrics) 291 if anonStat, ok := numaStat["anon"]; ok { 292 for numaID, value := range anonStat { 293 if _, ok := result[numaID]; !ok { 294 result[numaID] = &common.MemoryNumaMetrics{ 295 Anon: value, 296 } 297 } else { 298 result[numaID].Anon = value 299 } 300 } 301 } else { 302 general.Warningf("no anon in numa stat,cgroup path:%v", absCgroupPath) 303 } 304 305 if fileStat, ok := numaStat["file"]; ok { 306 for numaID, value := range fileStat { 307 if _, ok := result[numaID]; !ok { 308 result[numaID] = &common.MemoryNumaMetrics{ 309 File: value, 310 } 311 } else { 312 result[numaID].File = value 313 } 314 } 315 } else { 316 general.Warningf("no file in numa stat,cgroup path:%v", absCgroupPath) 317 } 318 319 return result, nil 320 } 321 322 func (m *manager) GetCPUSet(absCgroupPath string) (*common.CPUSetStats, error) { 323 cpusetStats := &common.CPUSetStats{} 324 325 var err error 326 cpusetStats.CPUs, err = fscommon.GetCgroupParamString(absCgroupPath, "cpuset.cpus") 327 if err != nil { 328 return nil, fmt.Errorf("read cpuset.cpus failed with error: %v", err) 329 } 330 331 cpusetStats.Mems, err = fscommon.GetCgroupParamString(absCgroupPath, "cpuset.mems") 332 if err != nil { 333 return nil, fmt.Errorf("read cpuset.mems failed with error: %v", err) 334 } 335 336 return cpusetStats, nil 337 } 338 339 func (m *manager) GetCPU(absCgroupPath string) (*common.CPUStats, error) { 340 cpuStats := &common.CPUStats{} 341 contents, err := ioutil.ReadFile(filepath.Join(absCgroupPath, "cpu.max")) //nolint:gosec 342 if err != nil { 343 return nil, err 344 } 345 346 trimmed := strings.TrimSpace(string(contents)) 347 parts := strings.Split(trimmed, " ") 348 if len(parts) != 2 { 349 return nil, fmt.Errorf("get cpu %s err, %v", absCgroupPath, err) 350 } 351 352 var quota int64 353 if parts[0] == "max" { 354 quota = math.MaxInt64 355 } else { 356 quota, err = strconv.ParseInt(parts[0], 10, 64) 357 if err != nil { 358 return nil, fmt.Errorf("parse int %s err, err %v", parts[0], err) 359 } 360 } 361 362 period, err := strconv.ParseUint(parts[1], 10, 64) 363 if err != nil { 364 return nil, fmt.Errorf("parse uint %s err, err %v", parts[1], err) 365 } 366 367 cpuStats.CpuPeriod = period 368 cpuStats.CpuQuota = quota 369 return cpuStats, nil 370 } 371 372 func (m *manager) GetIOCostQoS(absCgroupPath string) (map[string]*common.IOCostQoSData, error) { 373 contents, err := ioutil.ReadFile(filepath.Join(absCgroupPath, "io.cost.qos")) 374 if err != nil { 375 return nil, err 376 } 377 378 rawStr := strings.TrimRight(string(contents), "\n") 379 qosStrList := strings.Split(rawStr, "\n") 380 381 devIDtoQoSData := make(map[string]*common.IOCostQoSData) 382 for _, str := range qosStrList { 383 if strings.TrimSpace(str) == "" { 384 continue 385 } 386 387 devID, data, err := parseDeviceIOCostQoS(str) 388 if err != nil { 389 general.Errorf("invalid device io cost qos data: %s, err: %v", str, err) 390 continue 391 } 392 393 devIDtoQoSData[devID] = data 394 } 395 396 return devIDtoQoSData, nil 397 } 398 399 func (m *manager) GetIOCostModel(absCgroupPath string) (map[string]*common.IOCostModelData, error) { 400 contents, err := ioutil.ReadFile(filepath.Join(absCgroupPath, "io.cost.model")) 401 if err != nil { 402 return nil, err 403 } 404 405 rawStr := strings.TrimRight(string(contents), "\n") 406 modelStrList := strings.Split(rawStr, "\n") 407 408 devIDtoModelData := make(map[string]*common.IOCostModelData) 409 for _, str := range modelStrList { 410 if strings.TrimSpace(str) == "" { 411 continue 412 } 413 414 devID, data, err := parseDeviceIOCostModel(str) 415 if err != nil { 416 general.Errorf("invalid device io cost model %s, err %v", str, err) 417 continue 418 } 419 420 devIDtoModelData[devID] = data 421 } 422 423 return devIDtoModelData, nil 424 } 425 426 func (m *manager) GetDeviceIOWeight(absCgroupPath string, devID string) (uint64, bool, error) { 427 ioWeightFile := path.Join(absCgroupPath, "io.weight") 428 contents, err := ioutil.ReadFile(ioWeightFile) 429 if err != nil { 430 return 0, false, fmt.Errorf("failed to ReadFile %s, err %v", ioWeightFile, err) 431 } 432 433 rawStr := strings.TrimRight(string(contents), "\n") 434 weightStrList := strings.Split(rawStr, "\n") 435 436 var devWeight string 437 for _, line := range weightStrList { 438 if strings.TrimSpace(line) == "" { 439 continue 440 } 441 442 fields := strings.Fields(line) 443 if len(fields) != 2 { 444 log.Printf("invalid weight line %s in %s", line, ioWeightFile) 445 continue 446 } 447 448 if fields[0] == devID { 449 devWeight = fields[1] 450 break 451 } 452 } 453 454 if devWeight == "" { 455 return 0, false, nil 456 } 457 458 weight, err := strconv.ParseUint(strings.TrimRight(devWeight, "\n"), 10, 64) 459 return weight, true, err 460 } 461 462 func (m *manager) GetIOStat(absCgroupPath string) (map[string]map[string]string, error) { 463 ioStatFile := path.Join(absCgroupPath, "io.stat") 464 contents, err := ioutil.ReadFile(ioStatFile) 465 if err != nil { 466 return nil, fmt.Errorf("failed to ReadFile %s, err %v", ioStatFile, err) 467 } 468 469 rawStr := strings.TrimRight(string(contents), "\n") 470 ioStatStrList := strings.Split(rawStr, "\n") 471 472 devIDtoIOStat := make(map[string]map[string]string) 473 for _, line := range ioStatStrList { 474 if len(strings.TrimSpace(line)) == 0 { 475 continue 476 } 477 478 fields := strings.Fields(line) 479 480 if len(fields) == 0 { 481 return nil, fmt.Errorf("got empty line") 482 } 483 484 devID := fields[0] 485 486 if devIDtoIOStat[devID] == nil { 487 devIDtoIOStat[devID] = make(map[string]string) 488 } 489 490 metrics := fields[1:] 491 for _, m := range metrics { 492 kv := strings.Split(m, "=") 493 if len(kv) != 2 { 494 return nil, fmt.Errorf("invalid metric %s in line %s", m, line) 495 } 496 497 devIDtoIOStat[devID][kv[0]] = kv[1] 498 } 499 } 500 501 return devIDtoIOStat, nil 502 } 503 504 func (m *manager) GetMetrics(relCgroupPath string, _ map[string]struct{}) (*common.CgroupMetrics, error) { 505 c, err := cgroupsv2.LoadManager(common.CgroupFSMountPoint, relCgroupPath) 506 if err != nil { 507 return nil, err 508 } 509 510 stats, err := c.Stat() 511 if err != nil { 512 return nil, err 513 } 514 515 cm := &common.CgroupMetrics{ 516 Memory: &common.MemoryMetrics{}, 517 CPU: &common.CPUMetrics{}, 518 Pid: &common.PidMetrics{}, 519 } 520 if stats.Memory == nil { 521 klog.Infof("[cgroupv2] get cgroup stats memory nil, cgroupPath: %v\n", relCgroupPath) 522 } else { 523 cm.Memory.RSS = stats.Memory.Anon 524 cm.Memory.Dirty = stats.Memory.FileDirty 525 cm.Memory.WriteBack = stats.Memory.FileWriteback 526 cm.Memory.Cache = stats.Memory.File 527 528 cm.Memory.KernelUsage = stats.Memory.KernelStack + stats.Memory.Slab + stats.Memory.Sock 529 cm.Memory.UsageUsage = stats.Memory.Usage 530 cm.Memory.MemSWUsage = stats.Memory.SwapUsage 531 } 532 533 if stats.CPU == nil { 534 klog.Infof("[cgroupv2] get cgroup stats cpu nil, cgroupPath: %v\n", relCgroupPath) 535 } else { 536 cm.CPU.UsageTotal = stats.CPU.UsageUsec * 1000 537 cm.CPU.UsageUser = stats.CPU.UserUsec * 1000 538 cm.CPU.UsageKernel = stats.CPU.SystemUsec * 1000 539 } 540 541 if stats.Pids == nil { 542 klog.Infof("[cgroupv2] get cgroup stats pids nil, cgroupPath: %v\n", relCgroupPath) 543 } else { 544 cm.Pid.Current = stats.Pids.Current 545 cm.Pid.Limit = stats.Pids.Limit 546 } 547 548 return cm, nil 549 } 550 551 // GetPids return pids in current cgroup 552 func (m *manager) GetPids(absCgroupPath string) ([]string, error) { 553 pids, err := libcgroups.GetAllPids(absCgroupPath) 554 if err != nil { 555 return nil, err 556 } 557 return general.IntSliceToStringSlice(pids), nil 558 } 559 560 // GetTasks return all threads in current cgroup 561 func (m *manager) GetTasks(absCgroupPath string) ([]string, error) { 562 var tasks []string 563 err := filepath.Walk(absCgroupPath, func(p string, info os.FileInfo, iErr error) error { 564 if iErr != nil { 565 return iErr 566 } 567 if strings.HasSuffix(info.Name(), ".mount") { 568 return filepath.SkipDir 569 } 570 if info.IsDir() || info.Name() != common.CgroupTasksFileV2 { 571 return nil 572 } 573 574 pids, err := common.ReadTasksFile(p) 575 if err != nil { 576 return err 577 } 578 tasks = append(tasks, pids...) 579 return nil 580 }) 581 return tasks, err 582 } 583 584 func numToStr(value int64) (ret string) { 585 switch { 586 case value == 0: 587 ret = "" 588 case value == -1: 589 ret = "max" 590 default: 591 ret = strconv.FormatInt(value, 10) 592 } 593 594 return ret 595 } 596 597 func parseDeviceIOCostQoS(str string) (string, *common.IOCostQoSData, error) { 598 fields := strings.Fields(str) 599 if len(fields) != 9 { 600 return "", nil, fmt.Errorf("device io cost qos line(%s) does not has 9 fields", str) 601 } 602 603 devID := fields[0] 604 others := fields[1:] 605 ioCostQoSData := &common.IOCostQoSData{} 606 for _, o := range others { 607 kv := strings.Split(o, "=") 608 if len(kv) != 2 { 609 return "", nil, fmt.Errorf("invalid device io cost qos line(%s) with invalid option conf %s", str, o) 610 } 611 612 key := kv[0] 613 valStr := kv[1] 614 615 switch key { 616 case "enable": 617 val, err := strconv.ParseUint(valStr, 10, 32) 618 if err != nil { 619 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 620 } 621 ioCostQoSData.Enable = uint32(val) 622 case "ctrl": 623 ioCostQoSData.CtrlMode = common.IOCostCtrlMode(valStr) 624 case "rpct": 625 val, err := strconv.ParseFloat(valStr, 32) 626 if err != nil { 627 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 628 } 629 ioCostQoSData.ReadLatencyPercent = float32(val) 630 case "rlat": 631 val, err := strconv.ParseUint(valStr, 10, 32) 632 if err != nil { 633 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 634 } 635 ioCostQoSData.ReadLatencyUS = uint32(val) 636 case "wpct": 637 val, err := strconv.ParseFloat(valStr, 32) 638 if err != nil { 639 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 640 } 641 ioCostQoSData.WriteLatencyPercent = float32(val) 642 case "wlat": 643 val, err := strconv.ParseUint(valStr, 10, 32) 644 if err != nil { 645 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 646 } 647 ioCostQoSData.WriteLatencyUS = uint32(val) 648 case "min": 649 val, err := strconv.ParseFloat(valStr, 32) 650 if err != nil { 651 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 652 } 653 ioCostQoSData.VrateMin = float32(val) 654 case "max": 655 val, err := strconv.ParseFloat(valStr, 32) 656 if err != nil { 657 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid value(%s) for option %s", str, valStr, key) 658 } 659 ioCostQoSData.VrateMax = float32(val) 660 default: 661 return "", nil, fmt.Errorf("device io cost qos(%s) has invalid option key %s", str, key) 662 } 663 } 664 665 return devID, ioCostQoSData, nil 666 } 667 668 func parseDeviceIOCostModel(str string) (string, *common.IOCostModelData, error) { 669 fields := strings.Fields(str) 670 if len(fields) != 9 { 671 return "", nil, fmt.Errorf("device io cost model(%s) does not has 9 fields", str) 672 } 673 674 devID := fields[0] 675 others := fields[1:] 676 ioCostModelData := &common.IOCostModelData{} 677 lineLoop: 678 for _, o := range others { 679 kv := strings.Split(o, "=") 680 if len(kv) != 2 { 681 return "", nil, fmt.Errorf("invalid device io cost model(%s) with invalid option conf %s", str, o) 682 } 683 684 key := kv[0] 685 valStr := kv[1] 686 687 switch key { 688 case "ctrl": 689 ioCostModelData.CtrlMode = common.IOCostCtrlMode(valStr) 690 continue lineLoop 691 case "model": 692 ioCostModelData.Model = common.IOCostModel(valStr) 693 continue lineLoop 694 } 695 696 val, err := strconv.ParseUint(valStr, 10, 64) 697 if err != nil { 698 return "", nil, fmt.Errorf("device io cost model(%s) has invalid value(%s) for option %s", str, valStr, key) 699 } 700 701 switch key { 702 case "ctrl": 703 ioCostModelData.CtrlMode = common.IOCostCtrlMode(valStr) 704 case "model": 705 ioCostModelData.Model = common.IOCostModel(valStr) 706 case "rbps": 707 ioCostModelData.ReadBPS = val 708 case "rseqiops": 709 ioCostModelData.ReadSeqIOPS = val 710 case "rrandiops": 711 ioCostModelData.ReadRandIOPS = val 712 case "wbps": 713 ioCostModelData.WriteBPS = val 714 case "wseqiops": 715 ioCostModelData.WriteSeqIOPS = val 716 case "wrandiops": 717 ioCostModelData.WriteRandIOPS = val 718 default: 719 return "", nil, fmt.Errorf("device io cost model(%s) has invalid option key %s", str, key) 720 } 721 } 722 723 return devID, ioCostModelData, nil 724 }