...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups/fs2

     1  package fs2
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/sirupsen/logrus"
    12  
    13  	"github.com/opencontainers/runc/libcontainer/cgroups"
    14  	"github.com/opencontainers/runc/libcontainer/configs"
    15  )
    16  
    17  func isIoSet(r *configs.Resources) bool {
    18  	return r.BlkioWeight != 0 ||
    19  		len(r.BlkioWeightDevice) > 0 ||
    20  		len(r.BlkioThrottleReadBpsDevice) > 0 ||
    21  		len(r.BlkioThrottleWriteBpsDevice) > 0 ||
    22  		len(r.BlkioThrottleReadIOPSDevice) > 0 ||
    23  		len(r.BlkioThrottleWriteIOPSDevice) > 0
    24  }
    25  
    26  // bfqDeviceWeightSupported checks for per-device BFQ weight support (added
    27  // in kernel v5.4, commit 795fe54c2a8) by reading from "io.bfq.weight".
    28  func bfqDeviceWeightSupported(bfq *os.File) bool {
    29  	if bfq == nil {
    30  		return false
    31  	}
    32  	_, _ = bfq.Seek(0, 0)
    33  	buf := make([]byte, 32)
    34  	_, _ = bfq.Read(buf)
    35  	// If only a single number (default weight) if read back, we have older kernel.
    36  	_, err := strconv.ParseInt(string(bytes.TrimSpace(buf)), 10, 64)
    37  	return err != nil
    38  }
    39  
    40  func setIo(dirPath string, r *configs.Resources) error {
    41  	if !isIoSet(r) {
    42  		return nil
    43  	}
    44  
    45  	// If BFQ IO scheduler is available, use it.
    46  	var bfq *os.File
    47  	if r.BlkioWeight != 0 || len(r.BlkioWeightDevice) > 0 {
    48  		var err error
    49  		bfq, err = cgroups.OpenFile(dirPath, "io.bfq.weight", os.O_RDWR)
    50  		if err == nil {
    51  			defer bfq.Close()
    52  		} else if !os.IsNotExist(err) {
    53  			return err
    54  		}
    55  	}
    56  
    57  	if r.BlkioWeight != 0 {
    58  		if bfq != nil { // Use BFQ.
    59  			if _, err := bfq.WriteString(strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
    60  				return err
    61  			}
    62  		} else {
    63  			// Fallback to io.weight with a conversion scheme.
    64  			v := cgroups.ConvertBlkIOToIOWeightValue(r.BlkioWeight)
    65  			if err := cgroups.WriteFile(dirPath, "io.weight", strconv.FormatUint(v, 10)); err != nil {
    66  				return err
    67  			}
    68  		}
    69  	}
    70  	if bfqDeviceWeightSupported(bfq) {
    71  		for _, wd := range r.BlkioWeightDevice {
    72  			if _, err := bfq.WriteString(wd.WeightString() + "\n"); err != nil {
    73  				return fmt.Errorf("setting device weight %q: %w", wd.WeightString(), err)
    74  			}
    75  		}
    76  	}
    77  	for _, td := range r.BlkioThrottleReadBpsDevice {
    78  		if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("rbps")); err != nil {
    79  			return err
    80  		}
    81  	}
    82  	for _, td := range r.BlkioThrottleWriteBpsDevice {
    83  		if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wbps")); err != nil {
    84  			return err
    85  		}
    86  	}
    87  	for _, td := range r.BlkioThrottleReadIOPSDevice {
    88  		if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("riops")); err != nil {
    89  			return err
    90  		}
    91  	}
    92  	for _, td := range r.BlkioThrottleWriteIOPSDevice {
    93  		if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wiops")); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func readCgroup2MapFile(dirPath string, name string) (map[string][]string, error) {
   102  	ret := map[string][]string{}
   103  	f, err := cgroups.OpenFile(dirPath, name, os.O_RDONLY)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	defer f.Close()
   108  	scanner := bufio.NewScanner(f)
   109  	for scanner.Scan() {
   110  		line := scanner.Text()
   111  		parts := strings.Fields(line)
   112  		if len(parts) < 2 {
   113  			continue
   114  		}
   115  		ret[parts[0]] = parts[1:]
   116  	}
   117  	if err := scanner.Err(); err != nil {
   118  		return nil, &parseError{Path: dirPath, File: name, Err: err}
   119  	}
   120  	return ret, nil
   121  }
   122  
   123  func statIo(dirPath string, stats *cgroups.Stats) error {
   124  	const file = "io.stat"
   125  	values, err := readCgroup2MapFile(dirPath, file)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	// more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
   130  	var parsedStats cgroups.BlkioStats
   131  	for k, v := range values {
   132  		d := strings.Split(k, ":")
   133  		if len(d) != 2 {
   134  			continue
   135  		}
   136  		major, err := strconv.ParseUint(d[0], 10, 64)
   137  		if err != nil {
   138  			return &parseError{Path: dirPath, File: file, Err: err}
   139  		}
   140  		minor, err := strconv.ParseUint(d[1], 10, 64)
   141  		if err != nil {
   142  			return &parseError{Path: dirPath, File: file, Err: err}
   143  		}
   144  
   145  		for _, item := range v {
   146  			d := strings.Split(item, "=")
   147  			if len(d) != 2 {
   148  				continue
   149  			}
   150  			op := d[0]
   151  
   152  			// Map to the cgroupv1 naming and layout (in separate tables).
   153  			var targetTable *[]cgroups.BlkioStatEntry
   154  			switch op {
   155  			// Equivalent to cgroupv1's blkio.io_service_bytes.
   156  			case "rbytes":
   157  				op = "Read"
   158  				targetTable = &parsedStats.IoServiceBytesRecursive
   159  			case "wbytes":
   160  				op = "Write"
   161  				targetTable = &parsedStats.IoServiceBytesRecursive
   162  			// Equivalent to cgroupv1's blkio.io_serviced.
   163  			case "rios":
   164  				op = "Read"
   165  				targetTable = &parsedStats.IoServicedRecursive
   166  			case "wios":
   167  				op = "Write"
   168  				targetTable = &parsedStats.IoServicedRecursive
   169  			default:
   170  				// Skip over entries we cannot map to cgroupv1 stats for now.
   171  				// In the future we should expand the stats struct to include
   172  				// them.
   173  				logrus.Debugf("cgroupv2 io stats: skipping over unmappable %s entry", item)
   174  				continue
   175  			}
   176  
   177  			value, err := strconv.ParseUint(d[1], 10, 64)
   178  			if err != nil {
   179  				return &parseError{Path: dirPath, File: file, Err: err}
   180  			}
   181  
   182  			entry := cgroups.BlkioStatEntry{
   183  				Op:    op,
   184  				Major: major,
   185  				Minor: minor,
   186  				Value: value,
   187  			}
   188  			*targetTable = append(*targetTable, entry)
   189  		}
   190  	}
   191  	stats.BlkioStats = parsedStats
   192  	return nil
   193  }
   194  

View as plain text