cgroup.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package cgroup
  2. import (
  3. "fmt"
  4. "github.com/coroot/coroot-node-agent/common"
  5. "github.com/coroot/coroot-node-agent/flags"
  6. "io/ioutil"
  7. "k8s.io/klog/v2"
  8. "os"
  9. "path"
  10. "regexp"
  11. "strings"
  12. "time"
  13. )
  14. var (
  15. cgRoot = *flags.CgroupRoot
  16. dockerIdRegexp = regexp.MustCompile(`([a-z0-9]{64})`)
  17. crioIdRegexp = regexp.MustCompile(`crio-([a-z0-9]{64})`)
  18. lxcIdRegexp = regexp.MustCompile(`/lxc/([^/]+)`)
  19. systemSliceIdRegexp = regexp.MustCompile(`(/system\.slice/([^/]+))`)
  20. )
  21. type ContainerType uint8
  22. const (
  23. ContainerTypeUnknown ContainerType = iota
  24. ContainerTypeStandaloneProcess
  25. ContainerTypeDocker
  26. ContainerTypeCrio
  27. ContainerTypeLxc
  28. ContainerTypeSystemdService
  29. )
  30. func (t ContainerType) String() string {
  31. switch t {
  32. case ContainerTypeStandaloneProcess:
  33. return "standalone"
  34. case ContainerTypeDocker:
  35. return "docker"
  36. case ContainerTypeCrio:
  37. return "crio"
  38. case ContainerTypeLxc:
  39. return "lxc"
  40. case ContainerTypeSystemdService:
  41. return "systemd"
  42. default:
  43. return "unknown"
  44. }
  45. }
  46. type Stats struct {
  47. CpuUsageSeconds float64
  48. ThrottledTimeSeconds float64
  49. CpuQuotaCores float64
  50. MemoryRssBytes float64
  51. MemoryCacheBytes float64
  52. MemoryLimitBytes float64
  53. Blkio map[string]BlkioStat
  54. }
  55. type Cgroup struct {
  56. Id string
  57. ContainerType ContainerType
  58. ContainerId string
  59. prevStats *Stats
  60. subsystems map[string]string
  61. }
  62. func (cg *Cgroup) GetStats() *Stats {
  63. cur, err := cg.getCurrentStats()
  64. if err != nil {
  65. if !common.IsNotExist(err) {
  66. klog.Warningf("failed to get cgroup stats: %s", err)
  67. }
  68. return nil
  69. }
  70. var res *Stats
  71. if cg.prevStats != nil {
  72. res = &Stats{
  73. CpuUsageSeconds: cur.CpuUsageSeconds - cg.prevStats.CpuUsageSeconds,
  74. ThrottledTimeSeconds: cur.ThrottledTimeSeconds - cg.prevStats.ThrottledTimeSeconds,
  75. CpuQuotaCores: cur.CpuQuotaCores,
  76. MemoryRssBytes: cur.MemoryRssBytes,
  77. MemoryCacheBytes: cur.MemoryCacheBytes,
  78. MemoryLimitBytes: cur.MemoryLimitBytes,
  79. Blkio: map[string]BlkioStat{},
  80. }
  81. for majorMinor, stat := range cur.Blkio {
  82. prev, ok := cg.prevStats.Blkio[majorMinor]
  83. if !ok {
  84. continue
  85. }
  86. res.Blkio[majorMinor] = BlkioStat{
  87. ReadOps: stat.ReadOps - prev.ReadOps,
  88. WriteOps: stat.WriteOps - prev.WriteOps,
  89. ReadBytes: stat.ReadBytes - prev.ReadBytes,
  90. WrittenBytes: stat.WrittenBytes - prev.WrittenBytes,
  91. }
  92. }
  93. }
  94. cg.prevStats = cur
  95. return res
  96. }
  97. func (cg *Cgroup) getCurrentStats() (*Stats, error) {
  98. stats := &Stats{}
  99. var err error
  100. if stats.CpuUsageSeconds, err = cg.CpuUsageSeconds(); err != nil {
  101. return nil, err
  102. }
  103. if stats.ThrottledTimeSeconds, err = cg.ThrottledTimeSeconds(); err != nil {
  104. return nil, err
  105. }
  106. if stats.CpuQuotaCores, err = cg.CpuQuotaCores(); err != nil {
  107. return nil, err
  108. }
  109. m, err := cg.MemoryStat()
  110. if err != nil {
  111. return nil, err
  112. }
  113. stats.MemoryRssBytes = float64(m.RSS)
  114. stats.MemoryCacheBytes = float64(m.Cache)
  115. l, err := cg.MemoryLimitBytes()
  116. if err != nil {
  117. return nil, err
  118. }
  119. stats.MemoryLimitBytes = float64(l)
  120. stats.Blkio, err = cg.BlkioStat()
  121. if err != nil {
  122. return nil, err
  123. }
  124. return stats, nil
  125. }
  126. func (cg *Cgroup) CreatedAt() time.Time {
  127. fi, err := os.Stat(path.Join(cgRoot, "cpu", cg.subsystems["cpu"]))
  128. if err != nil {
  129. if !common.IsNotExist(err) {
  130. klog.Errorln(err)
  131. }
  132. return time.Time{}
  133. }
  134. return fi.ModTime()
  135. }
  136. func NewFromProcessCgroupFile(filePath string) (*Cgroup, error) {
  137. data, err := ioutil.ReadFile(filePath)
  138. if err != nil {
  139. return nil, err
  140. }
  141. cg := &Cgroup{
  142. subsystems: map[string]string{},
  143. }
  144. for _, line := range strings.Split(string(data), "\n") {
  145. parts := strings.SplitN(line, ":", 3)
  146. if len(parts) < 3 {
  147. continue
  148. }
  149. for _, cgType := range strings.Split(parts[1], ",") {
  150. cg.subsystems[cgType] = parts[2]
  151. }
  152. }
  153. cg.Id = cg.subsystems["cpu"]
  154. if cg.ContainerType, cg.ContainerId, err = containerByCgroup(cg.Id); err != nil {
  155. return nil, err
  156. }
  157. return cg, nil
  158. }
  159. func containerByCgroup(path string) (ContainerType, string, error) {
  160. prefix := strings.Split(strings.TrimLeft(path, "/"), "/")[0]
  161. switch prefix {
  162. case "", "user.slice", "init.scope":
  163. return ContainerTypeStandaloneProcess, "", nil
  164. case "docker":
  165. matches := dockerIdRegexp.FindStringSubmatch(path)
  166. if matches == nil {
  167. return ContainerTypeUnknown, "", fmt.Errorf("invalid docker cgroup %s", path)
  168. }
  169. return ContainerTypeDocker, matches[1], nil
  170. case "kubepods":
  171. crioMatches := crioIdRegexp.FindStringSubmatch(path)
  172. if crioMatches != nil {
  173. return ContainerTypeCrio, crioMatches[1], nil
  174. }
  175. matches := dockerIdRegexp.FindStringSubmatch(path)
  176. if matches == nil {
  177. return ContainerTypeUnknown, "", fmt.Errorf("invalid docker cgroup %s", path)
  178. }
  179. return ContainerTypeDocker, matches[1], nil
  180. case "lxc":
  181. matches := lxcIdRegexp.FindStringSubmatch(path)
  182. if matches == nil {
  183. return ContainerTypeUnknown, "", fmt.Errorf("invalid lxc cgroup %s", path)
  184. }
  185. return ContainerTypeLxc, matches[1], nil
  186. case "system.slice":
  187. matches := systemSliceIdRegexp.FindStringSubmatch(path)
  188. if matches == nil {
  189. return ContainerTypeUnknown, "", fmt.Errorf("invalid systemd cgroup %s", path)
  190. }
  191. return ContainerTypeSystemdService, matches[1], nil
  192. }
  193. return ContainerTypeUnknown, "", fmt.Errorf("unknown container: %s", path)
  194. }