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

     1  package fs2
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"os"
     7  	"strconv"
     8  
     9  	"golang.org/x/sys/unix"
    10  
    11  	"github.com/opencontainers/runc/libcontainer/cgroups"
    12  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    13  	"github.com/opencontainers/runc/libcontainer/configs"
    14  )
    15  
    16  func isCpuSet(r *configs.Resources) bool {
    17  	return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0 || r.CPUIdle != nil || r.CpuBurst != nil
    18  }
    19  
    20  func setCpu(dirPath string, r *configs.Resources) error {
    21  	if !isCpuSet(r) {
    22  		return nil
    23  	}
    24  
    25  	if r.CPUIdle != nil {
    26  		if err := cgroups.WriteFile(dirPath, "cpu.idle", strconv.FormatInt(*r.CPUIdle, 10)); err != nil {
    27  			return err
    28  		}
    29  	}
    30  
    31  	// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
    32  	if r.CpuWeight != 0 {
    33  		if err := cgroups.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(r.CpuWeight, 10)); err != nil {
    34  			return err
    35  		}
    36  	}
    37  
    38  	var burst string
    39  	if r.CpuBurst != nil {
    40  		burst = strconv.FormatUint(*r.CpuBurst, 10)
    41  		if err := cgroups.WriteFile(dirPath, "cpu.max.burst", burst); err != nil {
    42  			// Sometimes when the burst to be set is larger
    43  			// than the current one, it is rejected by the kernel
    44  			// (EINVAL) as old_quota/new_burst exceeds the parent
    45  			// cgroup quota limit. If this happens and the quota is
    46  			// going to be set, ignore the error for now and retry
    47  			// after setting the quota.
    48  			if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
    49  				return err
    50  			}
    51  		} else {
    52  			burst = ""
    53  		}
    54  	}
    55  	if r.CpuQuota != 0 || r.CpuPeriod != 0 {
    56  		str := "max"
    57  		if r.CpuQuota > 0 {
    58  			str = strconv.FormatInt(r.CpuQuota, 10)
    59  		}
    60  		period := r.CpuPeriod
    61  		if period == 0 {
    62  			// This default value is documented in
    63  			// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
    64  			period = 100000
    65  		}
    66  		str += " " + strconv.FormatUint(period, 10)
    67  		if err := cgroups.WriteFile(dirPath, "cpu.max", str); err != nil {
    68  			return err
    69  		}
    70  		if burst != "" {
    71  			if err := cgroups.WriteFile(dirPath, "cpu.max.burst", burst); err != nil {
    72  				return err
    73  			}
    74  		}
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  func statCpu(dirPath string, stats *cgroups.Stats) error {
    81  	const file = "cpu.stat"
    82  	f, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	defer f.Close()
    87  
    88  	sc := bufio.NewScanner(f)
    89  	for sc.Scan() {
    90  		t, v, err := fscommon.ParseKeyValue(sc.Text())
    91  		if err != nil {
    92  			return &parseError{Path: dirPath, File: file, Err: err}
    93  		}
    94  		switch t {
    95  		case "usage_usec":
    96  			stats.CpuStats.CpuUsage.TotalUsage = v * 1000
    97  
    98  		case "user_usec":
    99  			stats.CpuStats.CpuUsage.UsageInUsermode = v * 1000
   100  
   101  		case "system_usec":
   102  			stats.CpuStats.CpuUsage.UsageInKernelmode = v * 1000
   103  
   104  		case "nr_periods":
   105  			stats.CpuStats.ThrottlingData.Periods = v
   106  
   107  		case "nr_throttled":
   108  			stats.CpuStats.ThrottlingData.ThrottledPeriods = v
   109  
   110  		case "throttled_usec":
   111  			stats.CpuStats.ThrottlingData.ThrottledTime = v * 1000
   112  		}
   113  	}
   114  	if err := sc.Err(); err != nil {
   115  		return &parseError{Path: dirPath, File: file, Err: err}
   116  	}
   117  	return nil
   118  }