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  }