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 }