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 }