| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- package fs2
- import (
- "bufio"
- "bytes"
- "fmt"
- "os"
- "strconv"
- "strings"
- "github.com/sirupsen/logrus"
- "github.com/opencontainers/runc/libcontainer/cgroups"
- "github.com/opencontainers/runc/libcontainer/configs"
- )
- func isIoSet(r *configs.Resources) bool {
- return r.BlkioWeight != 0 ||
- len(r.BlkioWeightDevice) > 0 ||
- len(r.BlkioThrottleReadBpsDevice) > 0 ||
- len(r.BlkioThrottleWriteBpsDevice) > 0 ||
- len(r.BlkioThrottleReadIOPSDevice) > 0 ||
- len(r.BlkioThrottleWriteIOPSDevice) > 0
- }
- // bfqDeviceWeightSupported checks for per-device BFQ weight support (added
- // in kernel v5.4, commit 795fe54c2a8) by reading from "io.bfq.weight".
- func bfqDeviceWeightSupported(bfq *os.File) bool {
- if bfq == nil {
- return false
- }
- _, _ = bfq.Seek(0, 0)
- buf := make([]byte, 32)
- _, _ = bfq.Read(buf)
- // If only a single number (default weight) if read back, we have older kernel.
- _, err := strconv.ParseInt(string(bytes.TrimSpace(buf)), 10, 64)
- return err != nil
- }
- func setIo(dirPath string, r *configs.Resources) error {
- if !isIoSet(r) {
- return nil
- }
- // If BFQ IO scheduler is available, use it.
- var bfq *os.File
- if r.BlkioWeight != 0 || len(r.BlkioWeightDevice) > 0 {
- var err error
- bfq, err = cgroups.OpenFile(dirPath, "io.bfq.weight", os.O_RDWR)
- if err == nil {
- defer bfq.Close()
- } else if !os.IsNotExist(err) {
- return err
- }
- }
- if r.BlkioWeight != 0 {
- if bfq != nil { // Use BFQ.
- if _, err := bfq.WriteString(strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
- return err
- }
- } else {
- // Fallback to io.weight with a conversion scheme.
- v := cgroups.ConvertBlkIOToIOWeightValue(r.BlkioWeight)
- if err := cgroups.WriteFile(dirPath, "io.weight", strconv.FormatUint(v, 10)); err != nil {
- return err
- }
- }
- }
- if bfqDeviceWeightSupported(bfq) {
- for _, wd := range r.BlkioWeightDevice {
- if _, err := bfq.WriteString(wd.WeightString() + "\n"); err != nil {
- return fmt.Errorf("setting device weight %q: %w", wd.WeightString(), err)
- }
- }
- }
- for _, td := range r.BlkioThrottleReadBpsDevice {
- if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("rbps")); err != nil {
- return err
- }
- }
- for _, td := range r.BlkioThrottleWriteBpsDevice {
- if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wbps")); err != nil {
- return err
- }
- }
- for _, td := range r.BlkioThrottleReadIOPSDevice {
- if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("riops")); err != nil {
- return err
- }
- }
- for _, td := range r.BlkioThrottleWriteIOPSDevice {
- if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wiops")); err != nil {
- return err
- }
- }
- return nil
- }
- func readCgroup2MapFile(dirPath string, name string) (map[string][]string, error) {
- ret := map[string][]string{}
- f, err := cgroups.OpenFile(dirPath, name, os.O_RDONLY)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- line := scanner.Text()
- parts := strings.Fields(line)
- if len(parts) < 2 {
- continue
- }
- ret[parts[0]] = parts[1:]
- }
- if err := scanner.Err(); err != nil {
- return nil, &parseError{Path: dirPath, File: name, Err: err}
- }
- return ret, nil
- }
- func statIo(dirPath string, stats *cgroups.Stats) error {
- const file = "io.stat"
- values, err := readCgroup2MapFile(dirPath, file)
- if err != nil {
- return err
- }
- // more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
- var parsedStats cgroups.BlkioStats
- for k, v := range values {
- d := strings.Split(k, ":")
- if len(d) != 2 {
- continue
- }
- major, err := strconv.ParseUint(d[0], 10, 64)
- if err != nil {
- return &parseError{Path: dirPath, File: file, Err: err}
- }
- minor, err := strconv.ParseUint(d[1], 10, 64)
- if err != nil {
- return &parseError{Path: dirPath, File: file, Err: err}
- }
- for _, item := range v {
- d := strings.Split(item, "=")
- if len(d) != 2 {
- continue
- }
- op := d[0]
- // Map to the cgroupv1 naming and layout (in separate tables).
- var targetTable *[]cgroups.BlkioStatEntry
- switch op {
- // Equivalent to cgroupv1's blkio.io_service_bytes.
- case "rbytes":
- op = "Read"
- targetTable = &parsedStats.IoServiceBytesRecursive
- case "wbytes":
- op = "Write"
- targetTable = &parsedStats.IoServiceBytesRecursive
- // Equivalent to cgroupv1's blkio.io_serviced.
- case "rios":
- op = "Read"
- targetTable = &parsedStats.IoServicedRecursive
- case "wios":
- op = "Write"
- targetTable = &parsedStats.IoServicedRecursive
- default:
- // Skip over entries we cannot map to cgroupv1 stats for now.
- // In the future we should expand the stats struct to include
- // them.
- logrus.Debugf("cgroupv2 io stats: skipping over unmappable %s entry", item)
- continue
- }
- value, err := strconv.ParseUint(d[1], 10, 64)
- if err != nil {
- return &parseError{Path: dirPath, File: file, Err: err}
- }
- entry := cgroups.BlkioStatEntry{
- Op: op,
- Major: major,
- Minor: minor,
- Value: value,
- }
- *targetTable = append(*targetTable, entry)
- }
- }
- stats.BlkioStats = parsedStats
- return nil
- }
|