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  }