util.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package cgroup
  2. import (
  3. "bufio"
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. )
  13. var (
  14. // ErrCgroupsMissing indicates the /proc/cgroups was not found. This means
  15. // that cgroups were disabled at compile time (CONFIG_CGROUPS=n) or that
  16. // an invalid rootfs path was given.
  17. ErrCgroupsMissing = errors.New("cgroups not found or unsupported by OS")
  18. // ErrInvalidFormat indicates a malformed key/value pair on a line.
  19. ErrInvalidFormat = errors.New("error invalid key/value format")
  20. )
  21. // mountinfo represents a subset of the fields containing /proc/[pid]/mountinfo.
  22. type mountinfo struct {
  23. mountpoint string
  24. filesystemType string
  25. superOptions []string
  26. }
  27. // Parses a cgroup param and returns the key name and value.
  28. func parseCgroupParamKeyValue(t string) (string, uint64, error) {
  29. parts := strings.Fields(t)
  30. if len(parts) != 2 {
  31. return "", 0, ErrInvalidFormat
  32. }
  33. value, err := parseUint([]byte(parts[1]))
  34. if err != nil {
  35. return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err)
  36. }
  37. return parts[0], value, nil
  38. }
  39. // parseUintFromFile reads a single uint value from a file.
  40. func parseUintFromFile(path ...string) (uint64, error) {
  41. value, err := ioutil.ReadFile(filepath.Join(path...))
  42. if err != nil {
  43. // Not all features are implemented/enabled by each OS.
  44. if os.IsNotExist(err) {
  45. return 0, nil
  46. }
  47. return 0, err
  48. }
  49. return parseUint(value)
  50. }
  51. // parseUint reads a single uint value. It will trip any whitespace before
  52. // attempting to parse string. If the value is negative it will return 0.
  53. func parseUint(value []byte) (uint64, error) {
  54. strValue := string(bytes.TrimSpace(value))
  55. uintValue, err := strconv.ParseUint(strValue, 10, 64)
  56. if err != nil {
  57. // Munge negative values to 0.
  58. intValue, intErr := strconv.ParseInt(strValue, 10, 64)
  59. if intErr == nil && intValue < 0 {
  60. return 0, nil
  61. } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 {
  62. return 0, nil
  63. }
  64. return 0, err
  65. }
  66. return uintValue, nil
  67. }
  68. // parseMountinfoLine parses a line from the /proc/[pid]/mountinfo file on
  69. // Linux. The format of the line is specified in section 3.5 of
  70. // https://www.kernel.org/doc/Documentation/filesystems/proc.txt.
  71. func parseMountinfoLine(line string) (mountinfo, error) {
  72. mount := mountinfo{}
  73. fields := strings.Fields(line)
  74. if len(fields) < 10 {
  75. return mount, fmt.Errorf("invalid mountinfo line, expected at least "+
  76. "10 fields but got %d from line='%s'", len(fields), line)
  77. }
  78. mount.mountpoint = fields[4]
  79. var seperatorIndex int
  80. for i, value := range fields {
  81. if value == "-" {
  82. seperatorIndex = i
  83. break
  84. }
  85. }
  86. if fields[seperatorIndex] != "-" {
  87. return mount, fmt.Errorf("invalid mountinfo line, separator ('-') not "+
  88. "found in line='%s'", line)
  89. }
  90. if len(fields)-seperatorIndex-1 < 3 {
  91. return mount, fmt.Errorf("invalid mountinfo line, expected at least "+
  92. "3 fields after seperator but got %d from line='%s'",
  93. len(fields)-seperatorIndex-1, line)
  94. }
  95. fields = fields[seperatorIndex+1:]
  96. mount.filesystemType = fields[0]
  97. mount.superOptions = strings.Split(fields[2], ",")
  98. return mount, nil
  99. }
  100. // SupportedSubsystems returns the subsystems that are supported by the
  101. // kernel. The returned map contains a entry for each subsystem.
  102. func SupportedSubsystems(rootfsMountpoint string) (map[string]struct{}, error) {
  103. if rootfsMountpoint == "" {
  104. rootfsMountpoint = "/"
  105. }
  106. cgroups, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "cgroups"))
  107. if err != nil {
  108. if os.IsNotExist(err) {
  109. return nil, ErrCgroupsMissing
  110. }
  111. return nil, err
  112. }
  113. defer cgroups.Close()
  114. subsystemSet := map[string]struct{}{}
  115. sc := bufio.NewScanner(cgroups)
  116. for sc.Scan() {
  117. line := sc.Text()
  118. // Ignore the header.
  119. if len(line) > 0 && line[0] == '#' {
  120. continue
  121. }
  122. // Parse the cgroup subsystems.
  123. // Format: subsys_name hierarchy num_cgroups enabled
  124. // Example: cpuset 4 1 1
  125. fields := strings.Fields(line)
  126. if len(fields) == 0 {
  127. continue
  128. }
  129. // Check the enabled flag.
  130. if len(fields) > 3 {
  131. enabled := fields[3]
  132. if enabled == "0" {
  133. // Ignore cgroup subsystems that are disabled (via the
  134. // cgroup_disable kernel command-line boot parameter).
  135. continue
  136. }
  137. }
  138. subsystem := fields[0]
  139. subsystemSet[subsystem] = struct{}{}
  140. }
  141. return subsystemSet, sc.Err()
  142. }
  143. // SubsystemMountpoints returns the mountpoints for each of the given subsystems.
  144. // The returned map contains the subsystem name as a key and the value is the
  145. // mountpoint.
  146. func SubsystemMountpoints(rootfsMountpoint string, subsystems map[string]struct{}) (map[string]string, error) {
  147. if rootfsMountpoint == "" {
  148. rootfsMountpoint = "/"
  149. }
  150. mountinfo, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "self", "mountinfo"))
  151. if err != nil {
  152. return nil, err
  153. }
  154. defer mountinfo.Close()
  155. mounts := map[string]string{}
  156. sc := bufio.NewScanner(mountinfo)
  157. for sc.Scan() {
  158. // https://www.kernel.org/doc/Documentation/filesystems/proc.txt
  159. // Example:
  160. // 25 21 0:20 / /cgroup/cpu rw,relatime - cgroup cgroup rw,cpu
  161. line := strings.TrimSpace(sc.Text())
  162. if line == "" {
  163. continue
  164. }
  165. mount, err := parseMountinfoLine(line)
  166. if err != nil {
  167. return nil, err
  168. }
  169. if mount.filesystemType != "cgroup" {
  170. continue
  171. }
  172. if !strings.HasPrefix(mount.mountpoint, rootfsMountpoint) {
  173. continue
  174. }
  175. for _, opt := range mount.superOptions {
  176. // Sometimes the subsystem name is written like "name=blkio".
  177. fields := strings.SplitN(opt, "=", 2)
  178. if len(fields) > 1 {
  179. opt = fields[1]
  180. }
  181. // Test if option is a subsystem name.
  182. if _, found := subsystems[opt]; found {
  183. // Add the subsystem mount if it does not already exist.
  184. if _, exists := mounts[opt]; !exists {
  185. mounts[opt] = mount.mountpoint
  186. }
  187. }
  188. }
  189. }
  190. return mounts, sc.Err()
  191. }
  192. // ProcessCgroupPaths returns the cgroups to which a process belongs and the
  193. // pathname of the cgroup relative to the mountpoint of the subsystem.
  194. func ProcessCgroupPaths(rootfsMountpoint string, pid int) (map[string]string, error) {
  195. if rootfsMountpoint == "" {
  196. rootfsMountpoint = "/"
  197. }
  198. cgroup, err := os.Open(filepath.Join(rootfsMountpoint, "proc", strconv.Itoa(pid), "cgroup"))
  199. if err != nil {
  200. return nil, err
  201. }
  202. defer cgroup.Close()
  203. paths := map[string]string{}
  204. sc := bufio.NewScanner(cgroup)
  205. for sc.Scan() {
  206. // http://man7.org/linux/man-pages/man7/cgroups.7.html
  207. // Format: hierarchy-ID:subsystem-list:cgroup-path
  208. // Example:
  209. // 2:cpu:/docker/b29faf21b7eff959f64b4192c34d5d67a707fe8561e9eaa608cb27693fba4242
  210. line := sc.Text()
  211. fields := strings.Split(line, ":")
  212. if len(fields) != 3 {
  213. continue
  214. }
  215. path := fields[2]
  216. subsystems := strings.Split(fields[1], ",")
  217. for _, subsystem := range subsystems {
  218. paths[subsystem] = path
  219. }
  220. }
  221. return paths, sc.Err()
  222. }