github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs/cpu.go (about)

     1  package fs
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  
    10  	"github.com/opencontainers/runc/libcontainer/cgroups"
    11  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    12  	"github.com/opencontainers/runc/libcontainer/configs"
    13  	"golang.org/x/sys/unix"
    14  )
    15  
    16  type CpuGroup struct{}
    17  
    18  func (s *CpuGroup) Name() string {
    19  	return "cpu"
    20  }
    21  
    22  func (s *CpuGroup) Apply(path string, r *configs.Resources, pid int) error {
    23  	if err := os.MkdirAll(path, 0o755); err != nil {
    24  		return err
    25  	}
    26  	// We should set the real-Time group scheduling settings before moving
    27  	// in the process because if the process is already in SCHED_RR mode
    28  	// and no RT bandwidth is set, adding it will fail.
    29  	if err := s.SetRtSched(path, r); err != nil {
    30  		return err
    31  	}
    32  	// Since we are not using apply(), we need to place the pid
    33  	// into the procs file.
    34  	return cgroups.WriteCgroupProc(path, pid)
    35  }
    36  
    37  func (s *CpuGroup) SetRtSched(path string, r *configs.Resources) error {
    38  	if r.CpuRtPeriod != 0 {
    39  		if err := cgroups.WriteFile(path, "cpu.rt_period_us", strconv.FormatUint(r.CpuRtPeriod, 10)); err != nil {
    40  			return err
    41  		}
    42  	}
    43  	if r.CpuRtRuntime != 0 {
    44  		if err := cgroups.WriteFile(path, "cpu.rt_runtime_us", strconv.FormatInt(r.CpuRtRuntime, 10)); err != nil {
    45  			return err
    46  		}
    47  	}
    48  	return nil
    49  }
    50  
    51  func (s *CpuGroup) Set(path string, r *configs.Resources) error {
    52  	if r.CpuShares != 0 {
    53  		shares := r.CpuShares
    54  		if err := cgroups.WriteFile(path, "cpu.shares", strconv.FormatUint(shares, 10)); err != nil {
    55  			return err
    56  		}
    57  		// read it back
    58  		sharesRead, err := fscommon.GetCgroupParamUint(path, "cpu.shares")
    59  		if err != nil {
    60  			return err
    61  		}
    62  		// ... and check
    63  		if shares > sharesRead {
    64  			return fmt.Errorf("the maximum allowed cpu-shares is %d", sharesRead)
    65  		} else if shares < sharesRead {
    66  			return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
    67  		}
    68  	}
    69  
    70  	var period string
    71  	if r.CpuPeriod != 0 {
    72  		period = strconv.FormatUint(r.CpuPeriod, 10)
    73  		if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
    74  			// Sometimes when the period to be set is smaller
    75  			// than the current one, it is rejected by the kernel
    76  			// (EINVAL) as old_quota/new_period exceeds the parent
    77  			// cgroup quota limit. If this happens and the quota is
    78  			// going to be set, ignore the error for now and retry
    79  			// after setting the quota.
    80  			if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
    81  				return err
    82  			}
    83  		} else {
    84  			period = ""
    85  		}
    86  	}
    87  
    88  	var burst string
    89  	if r.CpuBurst != nil {
    90  		burst = strconv.FormatUint(*r.CpuBurst, 10)
    91  		if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
    92  			if errors.Is(err, unix.ENOENT) {
    93  				// If CPU burst knob is not available (e.g.
    94  				// older kernel), ignore it.
    95  				burst = ""
    96  			} else {
    97  				// Sometimes when the burst to be set is larger
    98  				// than the current one, it is rejected by the kernel
    99  				// (EINVAL) as old_quota/new_burst exceeds the parent
   100  				// cgroup quota limit. If this happens and the quota is
   101  				// going to be set, ignore the error for now and retry
   102  				// after setting the quota.
   103  				if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
   104  					return err
   105  				}
   106  			}
   107  		} else {
   108  			burst = ""
   109  		}
   110  	}
   111  	if r.CpuQuota != 0 {
   112  		if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
   113  			return err
   114  		}
   115  		if period != "" {
   116  			if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
   117  				return err
   118  			}
   119  		}
   120  		if burst != "" {
   121  			if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
   122  				return err
   123  			}
   124  		}
   125  	}
   126  
   127  	if r.CPUIdle != nil {
   128  		idle := strconv.FormatInt(*r.CPUIdle, 10)
   129  		if err := cgroups.WriteFile(path, "cpu.idle", idle); err != nil {
   130  			return err
   131  		}
   132  	}
   133  
   134  	return s.SetRtSched(path, r)
   135  }
   136  
   137  func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
   138  	const file = "cpu.stat"
   139  	f, err := cgroups.OpenFile(path, file, os.O_RDONLY)
   140  	if err != nil {
   141  		if os.IsNotExist(err) {
   142  			return nil
   143  		}
   144  		return err
   145  	}
   146  	defer f.Close()
   147  
   148  	sc := bufio.NewScanner(f)
   149  	for sc.Scan() {
   150  		t, v, err := fscommon.ParseKeyValue(sc.Text())
   151  		if err != nil {
   152  			return &parseError{Path: path, File: file, Err: err}
   153  		}
   154  		switch t {
   155  		case "nr_periods":
   156  			stats.CpuStats.ThrottlingData.Periods = v
   157  
   158  		case "nr_throttled":
   159  			stats.CpuStats.ThrottlingData.ThrottledPeriods = v
   160  
   161  		case "throttled_time":
   162  			stats.CpuStats.ThrottlingData.ThrottledTime = v
   163  		}
   164  	}
   165  	return nil
   166  }