blkio.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package cgroups
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. "strconv"
  9. "strings"
  10. "syscall"
  11. specs "github.com/opencontainers/runtime-spec/specs-go"
  12. )
  13. func NewBlkio(root string) *blkioController {
  14. return &blkioController{
  15. root: filepath.Join(root, string(Blkio)),
  16. }
  17. }
  18. type blkioController struct {
  19. root string
  20. }
  21. func (b *blkioController) Name() Name {
  22. return Blkio
  23. }
  24. func (b *blkioController) Path(path string) string {
  25. return filepath.Join(b.root, path)
  26. }
  27. func (b *blkioController) Create(path string, resources *specs.LinuxResources) error {
  28. if err := os.MkdirAll(b.Path(path), defaultDirPerm); err != nil {
  29. return err
  30. }
  31. if resources.BlockIO == nil {
  32. return nil
  33. }
  34. for _, t := range createBlkioSettings(resources.BlockIO) {
  35. if t.value != nil {
  36. if err := ioutil.WriteFile(
  37. filepath.Join(b.Path(path), fmt.Sprintf("blkio.%s", t.name)),
  38. t.format(t.value),
  39. defaultFilePerm,
  40. ); err != nil {
  41. return err
  42. }
  43. }
  44. }
  45. return nil
  46. }
  47. func (b *blkioController) Update(path string, resources *specs.LinuxResources) error {
  48. return b.Create(path, resources)
  49. }
  50. func (b *blkioController) Stat(path string, stats *Metrics) error {
  51. stats.Blkio = &BlkIOStat{}
  52. settings := []blkioStatSettings{
  53. {
  54. name: "throttle.io_serviced",
  55. entry: &stats.Blkio.IoServicedRecursive,
  56. },
  57. {
  58. name: "throttle.io_service_bytes",
  59. entry: &stats.Blkio.IoServiceBytesRecursive,
  60. },
  61. }
  62. // Try to read CFQ stats available on all CFQ enabled kernels first
  63. if _, err := os.Lstat(filepath.Join(b.Path(path), fmt.Sprintf("blkio.io_serviced_recursive"))); err == nil {
  64. settings = append(settings,
  65. blkioStatSettings{
  66. name: "sectors_recursive",
  67. entry: &stats.Blkio.SectorsRecursive,
  68. },
  69. blkioStatSettings{
  70. name: "io_service_bytes_recursive",
  71. entry: &stats.Blkio.IoServiceBytesRecursive,
  72. },
  73. blkioStatSettings{
  74. name: "io_serviced_recursive",
  75. entry: &stats.Blkio.IoServicedRecursive,
  76. },
  77. blkioStatSettings{
  78. name: "io_queued_recursive",
  79. entry: &stats.Blkio.IoQueuedRecursive,
  80. },
  81. blkioStatSettings{
  82. name: "io_service_time_recursive",
  83. entry: &stats.Blkio.IoServiceTimeRecursive,
  84. },
  85. blkioStatSettings{
  86. name: "io_wait_time_recursive",
  87. entry: &stats.Blkio.IoWaitTimeRecursive,
  88. },
  89. blkioStatSettings{
  90. name: "io_merged_recursive",
  91. entry: &stats.Blkio.IoMergedRecursive,
  92. },
  93. blkioStatSettings{
  94. name: "time_recursive",
  95. entry: &stats.Blkio.IoTimeRecursive,
  96. },
  97. )
  98. }
  99. devices, err := getDevices("/dev")
  100. if err != nil {
  101. return err
  102. }
  103. for _, t := range settings {
  104. if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
  105. return err
  106. }
  107. }
  108. return nil
  109. }
  110. func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]*BlkIOEntry) error {
  111. f, err := os.Open(filepath.Join(b.Path(path), fmt.Sprintf("blkio.%s", name)))
  112. if err != nil {
  113. return err
  114. }
  115. defer f.Close()
  116. sc := bufio.NewScanner(f)
  117. for sc.Scan() {
  118. if err := sc.Err(); err != nil {
  119. return err
  120. }
  121. // format: dev type amount
  122. fields := strings.FieldsFunc(sc.Text(), splitBlkIOStatLine)
  123. if len(fields) < 3 {
  124. if len(fields) == 2 && fields[0] == "Total" {
  125. // skip total line
  126. continue
  127. } else {
  128. return fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
  129. }
  130. }
  131. major, err := strconv.ParseUint(fields[0], 10, 64)
  132. if err != nil {
  133. return err
  134. }
  135. minor, err := strconv.ParseUint(fields[1], 10, 64)
  136. if err != nil {
  137. return err
  138. }
  139. op := ""
  140. valueField := 2
  141. if len(fields) == 4 {
  142. op = fields[2]
  143. valueField = 3
  144. }
  145. v, err := strconv.ParseUint(fields[valueField], 10, 64)
  146. if err != nil {
  147. return err
  148. }
  149. *entry = append(*entry, &BlkIOEntry{
  150. Device: devices[deviceKey{major, minor}],
  151. Major: major,
  152. Minor: minor,
  153. Op: op,
  154. Value: v,
  155. })
  156. }
  157. return nil
  158. }
  159. func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings {
  160. settings := []blkioSettings{
  161. {
  162. name: "weight",
  163. value: blkio.Weight,
  164. format: uintf,
  165. },
  166. {
  167. name: "leaf_weight",
  168. value: blkio.LeafWeight,
  169. format: uintf,
  170. },
  171. }
  172. for _, wd := range blkio.WeightDevice {
  173. settings = append(settings,
  174. blkioSettings{
  175. name: "weight_device",
  176. value: wd,
  177. format: weightdev,
  178. },
  179. blkioSettings{
  180. name: "leaf_weight_device",
  181. value: wd,
  182. format: weightleafdev,
  183. })
  184. }
  185. for _, t := range []struct {
  186. name string
  187. list []specs.LinuxThrottleDevice
  188. }{
  189. {
  190. name: "throttle.read_bps_device",
  191. list: blkio.ThrottleReadBpsDevice,
  192. },
  193. {
  194. name: "throttle.read_iops_device",
  195. list: blkio.ThrottleReadIOPSDevice,
  196. },
  197. {
  198. name: "throttle.write_bps_device",
  199. list: blkio.ThrottleWriteBpsDevice,
  200. },
  201. {
  202. name: "throttle.write_iops_device",
  203. list: blkio.ThrottleWriteIOPSDevice,
  204. },
  205. } {
  206. for _, td := range t.list {
  207. settings = append(settings, blkioSettings{
  208. name: t.name,
  209. value: td,
  210. format: throttleddev,
  211. })
  212. }
  213. }
  214. return settings
  215. }
  216. type blkioSettings struct {
  217. name string
  218. value interface{}
  219. format func(v interface{}) []byte
  220. }
  221. type blkioStatSettings struct {
  222. name string
  223. entry *[]*BlkIOEntry
  224. }
  225. func uintf(v interface{}) []byte {
  226. return []byte(strconv.FormatUint(uint64(*v.(*uint16)), 10))
  227. }
  228. func weightdev(v interface{}) []byte {
  229. wd := v.(specs.LinuxWeightDevice)
  230. return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight))
  231. }
  232. func weightleafdev(v interface{}) []byte {
  233. wd := v.(specs.LinuxWeightDevice)
  234. return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight))
  235. }
  236. func throttleddev(v interface{}) []byte {
  237. td := v.(specs.LinuxThrottleDevice)
  238. return []byte(fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate))
  239. }
  240. func splitBlkIOStatLine(r rune) bool {
  241. return r == ' ' || r == ':'
  242. }
  243. type deviceKey struct {
  244. major, minor uint64
  245. }
  246. // getDevices makes a best effort attempt to read all the devices into a map
  247. // keyed by major and minor number. Since devices may be mapped multiple times,
  248. // we err on taking the first occurrence.
  249. func getDevices(path string) (map[deviceKey]string, error) {
  250. // TODO(stevvooe): We are ignoring lots of errors. It might be kind of
  251. // challenging to debug this if we aren't mapping devices correctly.
  252. // Consider logging these errors.
  253. devices := map[deviceKey]string{}
  254. if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
  255. if err != nil {
  256. return err
  257. }
  258. switch {
  259. case fi.IsDir():
  260. switch fi.Name() {
  261. case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":
  262. return filepath.SkipDir
  263. default:
  264. return nil
  265. }
  266. case fi.Name() == "console":
  267. return nil
  268. default:
  269. if fi.Mode()&os.ModeDevice == 0 {
  270. // skip non-devices
  271. return nil
  272. }
  273. st, ok := fi.Sys().(*syscall.Stat_t)
  274. if !ok {
  275. return fmt.Errorf("%s: unable to convert to system stat", p)
  276. }
  277. key := deviceKey{major(st.Rdev), minor(st.Rdev)}
  278. if _, ok := devices[key]; ok {
  279. return nil // skip it if we have already populated the path.
  280. }
  281. devices[key] = p
  282. }
  283. return nil
  284. }); err != nil {
  285. return nil, err
  286. }
  287. return devices, nil
  288. }
  289. func major(devNumber uint64) uint64 {
  290. return (devNumber >> 8) & 0xfff
  291. }
  292. func minor(devNumber uint64) uint64 {
  293. return (devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)
  294. }