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  }