cgroups2.go (4529B)
1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 //go:build linux 22 // +build linux 23 24 package cgroups 25 26 import ( 27 "bufio" 28 "errors" 29 "fmt" 30 "io" 31 "os" 32 "path" 33 "strconv" 34 "strings" 35 ) 36 37 const ( 38 // _cgroupv2CPUMax is the file name for the CGroup-V2 CPU max and period 39 // parameter. 40 _cgroupv2CPUMax = "cpu.max" 41 // _cgroupFSType is the Linux CGroup-V2 file system type used in 42 // `/proc/$PID/mountinfo`. 43 _cgroupv2FSType = "cgroup2" 44 45 _cgroupv2MountPoint = "/sys/fs/cgroup" 46 47 _cgroupV2CPUMaxDefaultPeriod = 100000 48 _cgroupV2CPUMaxQuotaMax = "max" 49 ) 50 51 const ( 52 _cgroupv2CPUMaxQuotaIndex = iota 53 _cgroupv2CPUMaxPeriodIndex 54 ) 55 56 // ErrNotV2 indicates that the system is not using cgroups2. 57 var ErrNotV2 = errors.New("not using cgroups2") 58 59 // CGroups2 provides access to cgroups data for systems using cgroups2. 60 type CGroups2 struct { 61 mountPoint string 62 groupPath string 63 cpuMaxFile string 64 } 65 66 // NewCGroups2ForCurrentProcess builds a CGroups2 for the current process. 67 // 68 // This returns ErrNotV2 if the system is not using cgroups2. 69 func NewCGroups2ForCurrentProcess() (*CGroups2, error) { 70 return newCGroups2From(_procPathMountInfo, _procPathCGroup) 71 } 72 73 func newCGroups2From(mountInfoPath, procPathCGroup string) (*CGroups2, error) { 74 isV2, err := isCGroupV2(mountInfoPath) 75 if err != nil { 76 return nil, err 77 } 78 79 if !isV2 { 80 return nil, ErrNotV2 81 } 82 83 subsystems, err := parseCGroupSubsystems(procPathCGroup) 84 if err != nil { 85 return nil, err 86 } 87 88 // Find v2 subsystem by looking for the `0` id 89 var v2subsys *CGroupSubsys 90 for _, subsys := range subsystems { 91 if subsys.ID == 0 { 92 v2subsys = subsys 93 break 94 } 95 } 96 97 if v2subsys == nil { 98 return nil, ErrNotV2 99 } 100 101 return &CGroups2{ 102 mountPoint: _cgroupv2MountPoint, 103 groupPath: v2subsys.Name, 104 cpuMaxFile: _cgroupv2CPUMax, 105 }, nil 106 } 107 108 func isCGroupV2(procPathMountInfo string) (bool, error) { 109 var ( 110 isV2 bool 111 newMountPoint = func(mp *MountPoint) error { 112 isV2 = isV2 || (mp.FSType == _cgroupv2FSType && mp.MountPoint == _cgroupv2MountPoint) 113 return nil 114 } 115 ) 116 117 if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { 118 return false, err 119 } 120 121 return isV2, nil 122 } 123 124 // CPUQuota returns the CPU quota applied with the CPU cgroup2 controller. 125 // It is a result of reading cpu quota and period from cpu.max file. 126 // It will return `cpu.max / cpu.period`. If cpu.max is set to max, it returns 127 // (-1, false, nil) 128 func (cg *CGroups2) CPUQuota() (float64, bool, error) { 129 cpuMaxParams, err := os.Open(path.Join(cg.mountPoint, cg.groupPath, cg.cpuMaxFile)) 130 if err != nil { 131 if os.IsNotExist(err) { 132 return -1, false, nil 133 } 134 return -1, false, err 135 } 136 defer cpuMaxParams.Close() 137 138 scanner := bufio.NewScanner(cpuMaxParams) 139 if scanner.Scan() { 140 fields := strings.Fields(scanner.Text()) 141 if len(fields) == 0 || len(fields) > 2 { 142 return -1, false, fmt.Errorf("invalid format") 143 } 144 145 if fields[_cgroupv2CPUMaxQuotaIndex] == _cgroupV2CPUMaxQuotaMax { 146 return -1, false, nil 147 } 148 149 max, err := strconv.Atoi(fields[_cgroupv2CPUMaxQuotaIndex]) 150 if err != nil { 151 return -1, false, err 152 } 153 154 var period int 155 if len(fields) == 1 { 156 period = _cgroupV2CPUMaxDefaultPeriod 157 } else { 158 period, err = strconv.Atoi(fields[_cgroupv2CPUMaxPeriodIndex]) 159 if err != nil { 160 return -1, false, err 161 } 162 } 163 164 return float64(max) / float64(period), true, nil 165 } 166 167 if err := scanner.Err(); err != nil { 168 return -1, false, err 169 } 170 171 return 0, false, io.ErrUnexpectedEOF 172 }