blkio.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. Copyright The containerd Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cgroup1
  14. import (
  15. "bufio"
  16. "fmt"
  17. "io"
  18. "os"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
  23. specs "github.com/opencontainers/runtime-spec/specs-go"
  24. )
  25. // NewBlkio returns a Blkio controller given the root folder of cgroups.
  26. // It may optionally accept other configuration options, such as ProcRoot(path)
  27. func NewBlkio(root string, options ...func(controller *blkioController)) *blkioController {
  28. ctrl := &blkioController{
  29. root: filepath.Join(root, string(Blkio)),
  30. procRoot: "/proc",
  31. }
  32. for _, opt := range options {
  33. opt(ctrl)
  34. }
  35. return ctrl
  36. }
  37. // ProcRoot overrides the default location of the "/proc" filesystem
  38. func ProcRoot(path string) func(controller *blkioController) {
  39. return func(c *blkioController) {
  40. c.procRoot = path
  41. }
  42. }
  43. type blkioController struct {
  44. root string
  45. procRoot string
  46. }
  47. func (b *blkioController) Name() Name {
  48. return Blkio
  49. }
  50. func (b *blkioController) Path(path string) string {
  51. return filepath.Join(b.root, path)
  52. }
  53. func (b *blkioController) Create(path string, resources *specs.LinuxResources) error {
  54. if err := os.MkdirAll(b.Path(path), defaultDirPerm); err != nil {
  55. return err
  56. }
  57. if resources.BlockIO == nil {
  58. return nil
  59. }
  60. for _, t := range createBlkioSettings(resources.BlockIO) {
  61. if t.value != nil {
  62. if err := os.WriteFile(
  63. filepath.Join(b.Path(path), "blkio."+t.name),
  64. t.format(t.value),
  65. defaultFilePerm,
  66. ); err != nil {
  67. return err
  68. }
  69. }
  70. }
  71. return nil
  72. }
  73. func (b *blkioController) Update(path string, resources *specs.LinuxResources) error {
  74. return b.Create(path, resources)
  75. }
  76. func (b *blkioController) Stat(path string, stats *v1.Metrics) error {
  77. stats.Blkio = &v1.BlkIOStat{}
  78. var settings []blkioStatSettings
  79. // Try to read CFQ stats available on all CFQ enabled kernels first
  80. if _, err := os.Lstat(filepath.Join(b.Path(path), "blkio.io_serviced_recursive")); err == nil {
  81. settings = []blkioStatSettings{
  82. {
  83. name: "sectors_recursive",
  84. entry: &stats.Blkio.SectorsRecursive,
  85. },
  86. {
  87. name: "io_service_bytes_recursive",
  88. entry: &stats.Blkio.IoServiceBytesRecursive,
  89. },
  90. {
  91. name: "io_serviced_recursive",
  92. entry: &stats.Blkio.IoServicedRecursive,
  93. },
  94. {
  95. name: "io_queued_recursive",
  96. entry: &stats.Blkio.IoQueuedRecursive,
  97. },
  98. {
  99. name: "io_service_time_recursive",
  100. entry: &stats.Blkio.IoServiceTimeRecursive,
  101. },
  102. {
  103. name: "io_wait_time_recursive",
  104. entry: &stats.Blkio.IoWaitTimeRecursive,
  105. },
  106. {
  107. name: "io_merged_recursive",
  108. entry: &stats.Blkio.IoMergedRecursive,
  109. },
  110. {
  111. name: "time_recursive",
  112. entry: &stats.Blkio.IoTimeRecursive,
  113. },
  114. }
  115. }
  116. f, err := os.Open(filepath.Join(b.procRoot, "partitions"))
  117. if err != nil {
  118. return err
  119. }
  120. defer f.Close()
  121. devices, err := getDevices(f)
  122. if err != nil {
  123. return err
  124. }
  125. var size int
  126. for _, t := range settings {
  127. if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
  128. return err
  129. }
  130. size += len(*t.entry)
  131. }
  132. if size > 0 {
  133. return nil
  134. }
  135. // Even the kernel is compiled with the CFQ scheduler, the cgroup may not use
  136. // block devices with the CFQ scheduler. If so, we should fallback to throttle.* files.
  137. settings = []blkioStatSettings{
  138. {
  139. name: "throttle.io_serviced",
  140. entry: &stats.Blkio.IoServicedRecursive,
  141. },
  142. {
  143. name: "throttle.io_service_bytes",
  144. entry: &stats.Blkio.IoServiceBytesRecursive,
  145. },
  146. }
  147. for _, t := range settings {
  148. if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
  149. return err
  150. }
  151. }
  152. return nil
  153. }
  154. func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]*v1.BlkIOEntry) error {
  155. f, err := os.Open(filepath.Join(b.Path(path), "blkio."+name))
  156. if err != nil {
  157. return err
  158. }
  159. defer f.Close()
  160. sc := bufio.NewScanner(f)
  161. for sc.Scan() {
  162. // format: dev type amount
  163. fields := strings.FieldsFunc(sc.Text(), splitBlkIOStatLine)
  164. if len(fields) < 3 {
  165. if len(fields) == 2 && fields[0] == "Total" {
  166. // skip total line
  167. continue
  168. } else {
  169. return fmt.Errorf("invalid line found while parsing %s: %s", path, sc.Text())
  170. }
  171. }
  172. major, err := strconv.ParseUint(fields[0], 10, 64)
  173. if err != nil {
  174. return err
  175. }
  176. minor, err := strconv.ParseUint(fields[1], 10, 64)
  177. if err != nil {
  178. return err
  179. }
  180. op := ""
  181. valueField := 2
  182. if len(fields) == 4 {
  183. op = fields[2]
  184. valueField = 3
  185. }
  186. v, err := strconv.ParseUint(fields[valueField], 10, 64)
  187. if err != nil {
  188. return err
  189. }
  190. *entry = append(*entry, &v1.BlkIOEntry{
  191. Device: devices[deviceKey{major, minor}],
  192. Major: major,
  193. Minor: minor,
  194. Op: op,
  195. Value: v,
  196. })
  197. }
  198. return sc.Err()
  199. }
  200. func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings {
  201. settings := []blkioSettings{}
  202. if blkio.Weight != nil {
  203. settings = append(settings,
  204. blkioSettings{
  205. name: "weight",
  206. value: blkio.Weight,
  207. format: uintf,
  208. })
  209. }
  210. if blkio.LeafWeight != nil {
  211. settings = append(settings,
  212. blkioSettings{
  213. name: "leaf_weight",
  214. value: blkio.LeafWeight,
  215. format: uintf,
  216. })
  217. }
  218. for _, wd := range blkio.WeightDevice {
  219. if wd.Weight != nil {
  220. settings = append(settings,
  221. blkioSettings{
  222. name: "weight_device",
  223. value: wd,
  224. format: weightdev,
  225. })
  226. }
  227. if wd.LeafWeight != nil {
  228. settings = append(settings,
  229. blkioSettings{
  230. name: "leaf_weight_device",
  231. value: wd,
  232. format: weightleafdev,
  233. })
  234. }
  235. }
  236. for _, t := range []struct {
  237. name string
  238. list []specs.LinuxThrottleDevice
  239. }{
  240. {
  241. name: "throttle.read_bps_device",
  242. list: blkio.ThrottleReadBpsDevice,
  243. },
  244. {
  245. name: "throttle.read_iops_device",
  246. list: blkio.ThrottleReadIOPSDevice,
  247. },
  248. {
  249. name: "throttle.write_bps_device",
  250. list: blkio.ThrottleWriteBpsDevice,
  251. },
  252. {
  253. name: "throttle.write_iops_device",
  254. list: blkio.ThrottleWriteIOPSDevice,
  255. },
  256. } {
  257. for _, td := range t.list {
  258. settings = append(settings, blkioSettings{
  259. name: t.name,
  260. value: td,
  261. format: throttleddev,
  262. })
  263. }
  264. }
  265. return settings
  266. }
  267. type blkioSettings struct {
  268. name string
  269. value interface{}
  270. format func(v interface{}) []byte
  271. }
  272. type blkioStatSettings struct {
  273. name string
  274. entry *[]*v1.BlkIOEntry
  275. }
  276. func uintf(v interface{}) []byte {
  277. return []byte(strconv.FormatUint(uint64(*v.(*uint16)), 10))
  278. }
  279. func weightdev(v interface{}) []byte {
  280. wd := v.(specs.LinuxWeightDevice)
  281. return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.Weight))
  282. }
  283. func weightleafdev(v interface{}) []byte {
  284. wd := v.(specs.LinuxWeightDevice)
  285. return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.LeafWeight))
  286. }
  287. func throttleddev(v interface{}) []byte {
  288. td := v.(specs.LinuxThrottleDevice)
  289. return []byte(fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate))
  290. }
  291. func splitBlkIOStatLine(r rune) bool {
  292. return r == ' ' || r == ':'
  293. }
  294. type deviceKey struct {
  295. major, minor uint64
  296. }
  297. // getDevices makes a best effort attempt to read all the devices into a map
  298. // keyed by major and minor number. Since devices may be mapped multiple times,
  299. // we err on taking the first occurrence.
  300. func getDevices(r io.Reader) (map[deviceKey]string, error) {
  301. var (
  302. s = bufio.NewScanner(r)
  303. devices = make(map[deviceKey]string)
  304. )
  305. for i := 0; s.Scan(); i++ {
  306. if i < 2 {
  307. continue
  308. }
  309. fields := strings.Fields(s.Text())
  310. major, err := strconv.Atoi(fields[0])
  311. if err != nil {
  312. return nil, err
  313. }
  314. minor, err := strconv.Atoi(fields[1])
  315. if err != nil {
  316. return nil, err
  317. }
  318. key := deviceKey{
  319. major: uint64(major),
  320. minor: uint64(minor),
  321. }
  322. if _, ok := devices[key]; ok {
  323. continue
  324. }
  325. devices[key] = filepath.Join("/dev", fields[3])
  326. }
  327. return devices, s.Err()
  328. }