io.go 5.0 KB

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