reader.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package cgroup
  2. import (
  3. "path/filepath"
  4. )
  5. // Stats contains metrics and limits from each of the cgroup subsystems.
  6. type Stats struct {
  7. Metadata
  8. CPU *CPUSubsystem `json:"cpu"`
  9. CPUAccounting *CPUAccountingSubsystem `json:"cpuacct"`
  10. Memory *MemorySubsystem `json:"memory"`
  11. BlockIO *BlockIOSubsystem `json:"blkio"`
  12. }
  13. // Metadata contains metadata associated with cgroup stats.
  14. type Metadata struct {
  15. ID string `json:"id,omitempty"` // ID of the cgroup.
  16. Path string `json:"path,omitempty"` // Path to the cgroup relative to the cgroup subsystem's mountpoint.
  17. }
  18. type mount struct {
  19. subsystem string // Subsystem name (e.g. cpuacct).
  20. mountpoint string // Mountpoint of the subsystem (e.g. /cgroup/cpuacct).
  21. path string // Relative path to the cgroup (e.g. /docker/<id>).
  22. id string // ID of the cgroup.
  23. fullPath string // Absolute path to the cgroup. It's the mountpoint joined with the path.
  24. }
  25. // Reader reads cgroup metrics and limits.
  26. type Reader struct {
  27. // Mountpoint of the root filesystem. Defaults to / if not set. This can be
  28. // useful for example if you mount / as /rootfs inside of a container.
  29. rootfsMountpoint string
  30. ignoreRootCgroups bool // Ignore a cgroup when its path is "/".
  31. cgroupMountpoints map[string]string // Mountpoints for each subsystem (e.g. cpu, cpuacct, memory, blkio).
  32. }
  33. // NewReader creates and returns a new Reader.
  34. func NewReader(rootfsMountpoint string, ignoreRootCgroups bool) (*Reader, error) {
  35. if rootfsMountpoint == "" {
  36. rootfsMountpoint = "/"
  37. }
  38. // Determine what subsystems are supported by the kernel.
  39. subsystems, err := SupportedSubsystems(rootfsMountpoint)
  40. if err != nil {
  41. return nil, err
  42. }
  43. // Locate the mountpoints of those subsystems.
  44. mountpoints, err := SubsystemMountpoints(rootfsMountpoint, subsystems)
  45. if err != nil {
  46. return nil, err
  47. }
  48. return &Reader{
  49. rootfsMountpoint: rootfsMountpoint,
  50. ignoreRootCgroups: ignoreRootCgroups,
  51. cgroupMountpoints: mountpoints,
  52. }, nil
  53. }
  54. // GetStatsForProcess returns cgroup metrics and limits associated with a process.
  55. func (r *Reader) GetStatsForProcess(pid int) (*Stats, error) {
  56. // Read /proc/[pid]/cgroup to get the paths to the cgroup metrics.
  57. paths, err := ProcessCgroupPaths(r.rootfsMountpoint, pid)
  58. if err != nil {
  59. return nil, err
  60. }
  61. // Build the full path for the subsystems we are interested in.
  62. mounts := map[string]mount{}
  63. for _, interestedSubsystem := range []string{"blkio", "cpu", "cpuacct", "memory"} {
  64. path, found := paths[interestedSubsystem]
  65. if !found {
  66. continue
  67. }
  68. if path == "/" && r.ignoreRootCgroups {
  69. continue
  70. }
  71. subsystemMount, found := r.cgroupMountpoints[interestedSubsystem]
  72. if !found {
  73. continue
  74. }
  75. mounts[interestedSubsystem] = mount{
  76. subsystem: interestedSubsystem,
  77. mountpoint: subsystemMount,
  78. path: path,
  79. id: filepath.Base(path),
  80. fullPath: filepath.Join(subsystemMount, path),
  81. }
  82. }
  83. stats := Stats{Metadata: getCommonCgroupMetadata(mounts)}
  84. // Collect stats from each cgroup subsystem associated with the task.
  85. if mount, found := mounts["blkio"]; found {
  86. stats.BlockIO = &BlockIOSubsystem{}
  87. err := stats.BlockIO.get(mount.fullPath)
  88. if err != nil {
  89. return nil, err
  90. }
  91. stats.BlockIO.Metadata.ID = mount.id
  92. stats.BlockIO.Metadata.Path = mount.path
  93. }
  94. if mount, found := mounts["cpu"]; found {
  95. stats.CPU = &CPUSubsystem{}
  96. err := stats.CPU.get(mount.fullPath)
  97. if err != nil {
  98. return nil, err
  99. }
  100. stats.CPU.Metadata.ID = mount.id
  101. stats.CPU.Metadata.Path = mount.path
  102. }
  103. if mount, found := mounts["cpuacct"]; found {
  104. stats.CPUAccounting = &CPUAccountingSubsystem{}
  105. err := stats.CPUAccounting.get(mount.fullPath)
  106. if err != nil {
  107. return nil, err
  108. }
  109. stats.CPUAccounting.Metadata.ID = mount.id
  110. stats.CPUAccounting.Metadata.Path = mount.path
  111. }
  112. if mount, found := mounts["memory"]; found {
  113. stats.Memory = &MemorySubsystem{}
  114. err := stats.Memory.get(mount.fullPath)
  115. if err != nil {
  116. return nil, err
  117. }
  118. stats.Memory.Metadata.ID = mount.id
  119. stats.Memory.Metadata.Path = mount.path
  120. }
  121. // Return nil if no metrics were collected.
  122. if stats.BlockIO == nil && stats.CPU == nil && stats.CPUAccounting == nil && stats.Memory == nil {
  123. return nil, nil
  124. }
  125. return &stats, nil
  126. }
  127. // getCommonCgroupMetadata returns Metadata containing the cgroup path and ID
  128. // iff all subsystems share a common path and ID. This is common for
  129. // containerized processes. If there is no common path and ID then the returned
  130. // values are empty strings.
  131. func getCommonCgroupMetadata(mounts map[string]mount) Metadata {
  132. var path string
  133. for _, m := range mounts {
  134. if path == "" {
  135. path = m.path
  136. } else if path != m.path {
  137. // All paths are not the same.
  138. return Metadata{}
  139. }
  140. }
  141. return Metadata{Path: path, ID: filepath.Base(path)}
  142. }