github.com/intel/goresctrl@v0.5.0/pkg/cgroups/cgroupblkio.go (about) 1 // Copyright 2020-2021 Intel Corporation. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cgroups 16 17 import ( 18 "errors" 19 "fmt" 20 "strconv" 21 "strings" 22 ) 23 24 // cgroups blkio parameter filenames. 25 var blkioWeightFiles = []string{"blkio.bfq.weight", "blkio.weight"} 26 var blkioWeightDeviceFiles = []string{"blkio.bfq.weight_device", "blkio.weight_device"} 27 var blkioThrottleReadBpsFiles = []string{"blkio.throttle.read_bps_device"} 28 var blkioThrottleWriteBpsFiles = []string{"blkio.throttle.write_bps_device"} 29 var blkioThrottleReadIOPSFiles = []string{"blkio.throttle.read_iops_device"} 30 var blkioThrottleWriteIOPSFiles = []string{"blkio.throttle.write_iops_device"} 31 32 // BlockIOParameters contains cgroups blockio controller parameters. 33 // 34 // Effects of Weight and Rate values in SetBlkioParameters(): 35 // Value | Effect 36 // -------+------------------------------------------------------------------- 37 // 38 // -1 | Do not write to cgroups, value is missing. 39 // 0 | Write to cgroups, will clear the setting as specified in cgroups blkio interface. 40 // other | Write to cgroups, sets the value. 41 type BlockIOParameters struct { 42 Weight int64 43 WeightDevice DeviceWeights 44 ThrottleReadBpsDevice DeviceRates 45 ThrottleWriteBpsDevice DeviceRates 46 ThrottleReadIOPSDevice DeviceRates 47 ThrottleWriteIOPSDevice DeviceRates 48 } 49 50 // DeviceWeight contains values for 51 // - blkio.[io-scheduler].weight 52 type DeviceWeight struct { 53 Major int64 54 Minor int64 55 Weight int64 56 } 57 58 // DeviceRate contains values for 59 // - blkio.throttle.read_bps_device 60 // - blkio.throttle.write_bps_device 61 // - blkio.throttle.read_iops_device 62 // - blkio.throttle.write_iops_device 63 type DeviceRate struct { 64 Major int64 65 Minor int64 66 Rate int64 67 } 68 69 // DeviceWeights contains weights for devices. 70 type DeviceWeights []DeviceWeight 71 72 // DeviceRates contains throttling rates for devices. 73 type DeviceRates []DeviceRate 74 75 // DeviceParameters interface provides functions common to DeviceWeights and DeviceRates. 76 type DeviceParameters interface { 77 Append(maj, min, val int64) 78 Update(maj, min, val int64) 79 } 80 81 // Append appends (major, minor, value) to DeviceWeights slice. 82 func (w *DeviceWeights) Append(maj, min, val int64) { 83 *w = append(*w, DeviceWeight{Major: maj, Minor: min, Weight: val}) 84 } 85 86 // Append appends (major, minor, value) to DeviceRates slice. 87 func (r *DeviceRates) Append(maj, min, val int64) { 88 *r = append(*r, DeviceRate{Major: maj, Minor: min, Rate: val}) 89 } 90 91 // Update updates device weight in DeviceWeights slice, or appends it if not found. 92 func (w *DeviceWeights) Update(maj, min, val int64) { 93 for index, devWeight := range *w { 94 if devWeight.Major == maj && devWeight.Minor == min { 95 (*w)[index].Weight = val 96 return 97 } 98 } 99 w.Append(maj, min, val) 100 } 101 102 // Update updates device rate in DeviceRates slice, or appends it if not found. 103 func (r *DeviceRates) Update(maj, min, val int64) { 104 for index, devRate := range *r { 105 if devRate.Major == maj && devRate.Minor == min { 106 (*r)[index].Rate = val 107 return 108 } 109 } 110 r.Append(maj, min, val) 111 } 112 113 // NewBlockIOParameters creates new BlockIOParameters instance. 114 func NewBlockIOParameters() BlockIOParameters { 115 return BlockIOParameters{ 116 Weight: -1, 117 } 118 } 119 120 // NewDeviceWeight creates new DeviceWeight instance. 121 func NewDeviceWeight() DeviceWeight { 122 return DeviceWeight{ 123 Major: -1, 124 Minor: -1, 125 Weight: -1, 126 } 127 } 128 129 // NewDeviceRate creates new DeviceRate instance. 130 func NewDeviceRate() DeviceRate { 131 return DeviceRate{ 132 Major: -1, 133 Minor: -1, 134 Rate: -1, 135 } 136 } 137 138 type devMajMin struct { 139 Major int64 140 Minor int64 141 } 142 143 // ResetBlkioParameters adds new, changes existing and removes missing blockIO parameters in cgroupsDir. 144 func ResetBlkioParameters(groupDir string, blockIO BlockIOParameters) error { 145 errs := []error{} 146 oldBlockIO, _ := GetBlkioParameters(groupDir) 147 newBlockIO := NewBlockIOParameters() 148 newBlockIO.Weight = blockIO.Weight 149 newBlockIO.WeightDevice = resetDevWeights(oldBlockIO.WeightDevice, blockIO.WeightDevice) 150 newBlockIO.ThrottleReadBpsDevice = resetDevRates(oldBlockIO.ThrottleReadBpsDevice, blockIO.ThrottleReadBpsDevice) 151 newBlockIO.ThrottleWriteBpsDevice = resetDevRates(oldBlockIO.ThrottleWriteBpsDevice, blockIO.ThrottleWriteBpsDevice) 152 newBlockIO.ThrottleReadIOPSDevice = resetDevRates(oldBlockIO.ThrottleReadIOPSDevice, blockIO.ThrottleReadIOPSDevice) 153 newBlockIO.ThrottleWriteIOPSDevice = resetDevRates(oldBlockIO.ThrottleWriteIOPSDevice, blockIO.ThrottleWriteIOPSDevice) 154 errs = append(errs, SetBlkioParameters(groupDir, newBlockIO)) 155 return errors.Join(errs...) 156 } 157 158 // resetDevWeights adds wanted weight parameters to new and resets unwanted weights. 159 func resetDevWeights(old, wanted []DeviceWeight) []DeviceWeight { 160 new := []DeviceWeight{} 161 seenDev := map[devMajMin]bool{} 162 for _, wdp := range wanted { 163 seenDev[devMajMin{wdp.Major, wdp.Minor}] = true 164 new = append(new, wdp) 165 } 166 for _, wdp := range old { 167 if !seenDev[devMajMin{wdp.Major, wdp.Minor}] { 168 new = append(new, DeviceWeight{wdp.Major, wdp.Minor, 0}) 169 } 170 } 171 return new 172 } 173 174 // resetDevRates adds wanted rate parameters to new and resets unwanted rates. 175 func resetDevRates(old, wanted []DeviceRate) []DeviceRate { 176 new := []DeviceRate{} 177 seenDev := map[devMajMin]bool{} 178 for _, rdp := range wanted { 179 new = append(new, rdp) 180 seenDev[devMajMin{rdp.Major, rdp.Minor}] = true 181 } 182 for _, rdp := range old { 183 if !seenDev[devMajMin{rdp.Major, rdp.Minor}] { 184 new = append(new, DeviceRate{rdp.Major, rdp.Minor, 0}) 185 } 186 } 187 return new 188 } 189 190 // GetBlkioParameters returns BlockIO parameters from files in cgroups blkio controller directory. 191 func GetBlkioParameters(group string) (BlockIOParameters, error) { 192 errs := []error{} 193 blockIO := NewBlockIOParameters() 194 195 errs = append(errs, readWeight(group, blkioWeightFiles, &blockIO.Weight)) 196 errs = append(errs, readDeviceParameters(group, blkioWeightDeviceFiles, &blockIO.WeightDevice)) 197 errs = append(errs, readDeviceParameters(group, blkioThrottleReadBpsFiles, &blockIO.ThrottleReadBpsDevice)) 198 errs = append(errs, readDeviceParameters(group, blkioThrottleWriteBpsFiles, &blockIO.ThrottleWriteBpsDevice)) 199 errs = append(errs, readDeviceParameters(group, blkioThrottleReadIOPSFiles, &blockIO.ThrottleReadIOPSDevice)) 200 errs = append(errs, readDeviceParameters(group, blkioThrottleWriteIOPSFiles, &blockIO.ThrottleWriteIOPSDevice)) 201 return blockIO, errors.Join(errs...) 202 } 203 204 // readWeight parses int64 from a cgroups entry. 205 func readWeight(groupDir string, filenames []string, rv *int64) error { 206 contents, err := readFirstFile(groupDir, filenames) 207 if err != nil { 208 return err 209 } 210 parsed, err := strconv.ParseInt(strings.TrimSuffix(contents, "\n"), 10, 64) 211 if err != nil { 212 return fmt.Errorf("parsing weight from %#v found in %v failed: %w", contents, filenames, err) 213 } 214 *rv = parsed 215 return nil 216 } 217 218 // readDeviceParameters parses device lines used for weights and throttling rates. 219 func readDeviceParameters(groupDir string, filenames []string, params DeviceParameters) error { 220 errs := []error{} 221 contents, err := readFirstFile(groupDir, filenames) 222 if err != nil { 223 return err 224 } 225 for _, line := range strings.Split(contents, "\n") { 226 // Device weight files may have "default NNN" line at the beginning. Skip it. 227 if line == "" || strings.HasPrefix(line, "default ") { 228 continue 229 } 230 // Expect syntax MAJOR:MINOR VALUE 231 devVal := strings.Split(line, " ") 232 if len(devVal) != 2 { 233 errs = append(errs, fmt.Errorf("invalid line %q, single space expected", line)) 234 continue 235 } 236 majMin := strings.Split(devVal[0], ":") 237 if len(majMin) != 2 { 238 errs = append(errs, fmt.Errorf("invalid line %q, single colon expected before space", line)) 239 continue 240 } 241 major, majErr := strconv.ParseInt(majMin[0], 10, 64) 242 minor, minErr := strconv.ParseInt(majMin[1], 10, 64) 243 value, valErr := strconv.ParseInt(devVal[1], 10, 64) 244 if majErr != nil || minErr != nil || valErr != nil { 245 errs = append(errs, fmt.Errorf("invalid number when parsing \"major:minor value\" from \"%s:%s %s\"", majMin[0], majMin[1], devVal[1])) 246 continue 247 } 248 params.Append(major, minor, value) 249 } 250 return errors.Join(errs...) 251 } 252 253 // readFirstFile returns contents of the first successfully read entry. 254 func readFirstFile(groupDir string, filenames []string) (string, error) { 255 errs := []error{} 256 // If reading all the files fails, return list of read errors. 257 for _, filename := range filenames { 258 content, err := Blkio.Group(groupDir).Read(filename) 259 if err == nil { 260 return content, nil 261 } 262 errs = append(errs, err) 263 } 264 err := errors.Join(errs...) 265 if err != nil { 266 return "", fmt.Errorf("could not read any of files %q: %w", filenames, err) 267 } 268 return "", nil 269 } 270 271 // SetBlkioParameters writes BlockIO parameters to files in cgroups blkio contoller directory. 272 func SetBlkioParameters(group string, blockIO BlockIOParameters) error { 273 errs := []error{} 274 if blockIO.Weight >= 0 { 275 errs = append(errs, writeFirstFile(group, blkioWeightFiles, "%d", blockIO.Weight)) 276 } 277 for _, wd := range blockIO.WeightDevice { 278 errs = append(errs, writeFirstFile(group, blkioWeightDeviceFiles, "%d:%d %d", wd.Major, wd.Minor, wd.Weight)) 279 } 280 for _, rd := range blockIO.ThrottleReadBpsDevice { 281 errs = append(errs, writeFirstFile(group, blkioThrottleReadBpsFiles, "%d:%d %d", rd.Major, rd.Minor, rd.Rate)) 282 } 283 for _, rd := range blockIO.ThrottleWriteBpsDevice { 284 errs = append(errs, writeFirstFile(group, blkioThrottleWriteBpsFiles, "%d:%d %d", rd.Major, rd.Minor, rd.Rate)) 285 } 286 for _, rd := range blockIO.ThrottleReadIOPSDevice { 287 errs = append(errs, writeFirstFile(group, blkioThrottleReadIOPSFiles, "%d:%d %d", rd.Major, rd.Minor, rd.Rate)) 288 } 289 for _, rd := range blockIO.ThrottleWriteIOPSDevice { 290 errs = append(errs, writeFirstFile(group, blkioThrottleWriteIOPSFiles, "%d:%d %d", rd.Major, rd.Minor, rd.Rate)) 291 } 292 return errors.Join(errs...) 293 } 294 295 // writeFirstFile writes content to the first existing file in the list under groupDir. 296 func writeFirstFile(groupDir string, filenames []string, format string, args ...interface{}) error { 297 errs := []error{} 298 // Returns list of errors from writes, list of single error due to all filenames missing or nil on success. 299 for _, filename := range filenames { 300 if err := Blkio.Group(groupDir).Write(filename, format, args...); err != nil { 301 errs = append(errs, err) 302 continue 303 } 304 return nil 305 } 306 err := errors.Join(errs...) 307 if err != nil { 308 data := fmt.Sprintf(format, args...) 309 return fmt.Errorf("writing all files %v failed, errors: %w, content %q", filenames, err, data) 310 } 311 return nil 312 }