cpuacct.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package fs
  2. import (
  3. "bufio"
  4. "os"
  5. "strconv"
  6. "strings"
  7. "github.com/opencontainers/runc/libcontainer/cgroups"
  8. "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
  9. "github.com/opencontainers/runc/libcontainer/configs"
  10. )
  11. const (
  12. cgroupCpuacctStat = "cpuacct.stat"
  13. cgroupCpuacctUsageAll = "cpuacct.usage_all"
  14. nanosecondsInSecond = 1000000000
  15. userModeColumn = 1
  16. kernelModeColumn = 2
  17. cuacctUsageAllColumnsNumber = 3
  18. // The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
  19. // on Linux it's a constant which is safe to be hard coded,
  20. // so we can avoid using cgo here. For details, see:
  21. // https://github.com/containerd/cgroups/pull/12
  22. clockTicks uint64 = 100
  23. )
  24. type CpuacctGroup struct{}
  25. func (s *CpuacctGroup) Name() string {
  26. return "cpuacct"
  27. }
  28. func (s *CpuacctGroup) Apply(path string, _ *configs.Resources, pid int) error {
  29. return apply(path, pid)
  30. }
  31. func (s *CpuacctGroup) Set(_ string, _ *configs.Resources) error {
  32. return nil
  33. }
  34. func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
  35. if !cgroups.PathExists(path) {
  36. return nil
  37. }
  38. userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
  39. if err != nil {
  40. return err
  41. }
  42. totalUsage, err := fscommon.GetCgroupParamUint(path, "cpuacct.usage")
  43. if err != nil {
  44. return err
  45. }
  46. percpuUsage, err := getPercpuUsage(path)
  47. if err != nil {
  48. return err
  49. }
  50. percpuUsageInKernelmode, percpuUsageInUsermode, err := getPercpuUsageInModes(path)
  51. if err != nil {
  52. return err
  53. }
  54. stats.CpuStats.CpuUsage.TotalUsage = totalUsage
  55. stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
  56. stats.CpuStats.CpuUsage.PercpuUsageInKernelmode = percpuUsageInKernelmode
  57. stats.CpuStats.CpuUsage.PercpuUsageInUsermode = percpuUsageInUsermode
  58. stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
  59. stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
  60. return nil
  61. }
  62. // Returns user and kernel usage breakdown in nanoseconds.
  63. func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
  64. var userModeUsage, kernelModeUsage uint64
  65. const (
  66. userField = "user"
  67. systemField = "system"
  68. file = cgroupCpuacctStat
  69. )
  70. // Expected format:
  71. // user <usage in ticks>
  72. // system <usage in ticks>
  73. data, err := cgroups.ReadFile(path, file)
  74. if err != nil {
  75. return 0, 0, err
  76. }
  77. // TODO: use strings.SplitN instead.
  78. fields := strings.Fields(data)
  79. if len(fields) < 4 || fields[0] != userField || fields[2] != systemField {
  80. return 0, 0, malformedLine(path, file, data)
  81. }
  82. if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
  83. return 0, 0, &parseError{Path: path, File: file, Err: err}
  84. }
  85. if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil {
  86. return 0, 0, &parseError{Path: path, File: file, Err: err}
  87. }
  88. return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
  89. }
  90. func getPercpuUsage(path string) ([]uint64, error) {
  91. const file = "cpuacct.usage_percpu"
  92. percpuUsage := []uint64{}
  93. data, err := cgroups.ReadFile(path, file)
  94. if err != nil {
  95. return percpuUsage, err
  96. }
  97. // TODO: use strings.SplitN instead.
  98. for _, value := range strings.Fields(data) {
  99. value, err := strconv.ParseUint(value, 10, 64)
  100. if err != nil {
  101. return percpuUsage, &parseError{Path: path, File: file, Err: err}
  102. }
  103. percpuUsage = append(percpuUsage, value)
  104. }
  105. return percpuUsage, nil
  106. }
  107. func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) {
  108. usageKernelMode := []uint64{}
  109. usageUserMode := []uint64{}
  110. const file = cgroupCpuacctUsageAll
  111. fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
  112. if os.IsNotExist(err) {
  113. return usageKernelMode, usageUserMode, nil
  114. } else if err != nil {
  115. return nil, nil, err
  116. }
  117. defer fd.Close()
  118. scanner := bufio.NewScanner(fd)
  119. scanner.Scan() // skipping header line
  120. for scanner.Scan() {
  121. lineFields := strings.SplitN(scanner.Text(), " ", cuacctUsageAllColumnsNumber+1)
  122. if len(lineFields) != cuacctUsageAllColumnsNumber {
  123. continue
  124. }
  125. usageInKernelMode, err := strconv.ParseUint(lineFields[kernelModeColumn], 10, 64)
  126. if err != nil {
  127. return nil, nil, &parseError{Path: path, File: file, Err: err}
  128. }
  129. usageKernelMode = append(usageKernelMode, usageInKernelMode)
  130. usageInUserMode, err := strconv.ParseUint(lineFields[userModeColumn], 10, 64)
  131. if err != nil {
  132. return nil, nil, &parseError{Path: path, File: file, Err: err}
  133. }
  134. usageUserMode = append(usageUserMode, usageInUserMode)
  135. }
  136. if err := scanner.Err(); err != nil {
  137. return nil, nil, &parseError{Path: path, File: file, Err: err}
  138. }
  139. return usageKernelMode, usageUserMode, nil
  140. }