cpuset.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package fs
  2. import (
  3. "errors"
  4. "os"
  5. "path/filepath"
  6. "strconv"
  7. "strings"
  8. "golang.org/x/sys/unix"
  9. "github.com/opencontainers/runc/libcontainer/cgroups"
  10. "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
  11. "github.com/opencontainers/runc/libcontainer/configs"
  12. )
  13. type CpusetGroup struct{}
  14. func (s *CpusetGroup) Name() string {
  15. return "cpuset"
  16. }
  17. func (s *CpusetGroup) Apply(path string, r *configs.Resources, pid int) error {
  18. return s.ApplyDir(path, r, pid)
  19. }
  20. func (s *CpusetGroup) Set(path string, r *configs.Resources) error {
  21. if r.CpusetCpus != "" {
  22. if err := cgroups.WriteFile(path, "cpuset.cpus", r.CpusetCpus); err != nil {
  23. return err
  24. }
  25. }
  26. if r.CpusetMems != "" {
  27. if err := cgroups.WriteFile(path, "cpuset.mems", r.CpusetMems); err != nil {
  28. return err
  29. }
  30. }
  31. return nil
  32. }
  33. func getCpusetStat(path string, file string) ([]uint16, error) {
  34. var extracted []uint16
  35. fileContent, err := fscommon.GetCgroupParamString(path, file)
  36. if err != nil {
  37. return extracted, err
  38. }
  39. if len(fileContent) == 0 {
  40. return extracted, &parseError{Path: path, File: file, Err: errors.New("empty file")}
  41. }
  42. for _, s := range strings.Split(fileContent, ",") {
  43. sp := strings.SplitN(s, "-", 3)
  44. switch len(sp) {
  45. case 3:
  46. return extracted, &parseError{Path: path, File: file, Err: errors.New("extra dash")}
  47. case 2:
  48. min, err := strconv.ParseUint(sp[0], 10, 16)
  49. if err != nil {
  50. return extracted, &parseError{Path: path, File: file, Err: err}
  51. }
  52. max, err := strconv.ParseUint(sp[1], 10, 16)
  53. if err != nil {
  54. return extracted, &parseError{Path: path, File: file, Err: err}
  55. }
  56. if min > max {
  57. return extracted, &parseError{Path: path, File: file, Err: errors.New("invalid values, min > max")}
  58. }
  59. for i := min; i <= max; i++ {
  60. extracted = append(extracted, uint16(i))
  61. }
  62. case 1:
  63. value, err := strconv.ParseUint(s, 10, 16)
  64. if err != nil {
  65. return extracted, &parseError{Path: path, File: file, Err: err}
  66. }
  67. extracted = append(extracted, uint16(value))
  68. }
  69. }
  70. return extracted, nil
  71. }
  72. func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
  73. var err error
  74. stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
  75. if err != nil && !errors.Is(err, os.ErrNotExist) {
  76. return err
  77. }
  78. stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
  79. if err != nil && !errors.Is(err, os.ErrNotExist) {
  80. return err
  81. }
  82. stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
  83. if err != nil && !errors.Is(err, os.ErrNotExist) {
  84. return err
  85. }
  86. stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
  87. if err != nil && !errors.Is(err, os.ErrNotExist) {
  88. return err
  89. }
  90. stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
  91. if err != nil && !errors.Is(err, os.ErrNotExist) {
  92. return err
  93. }
  94. stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
  95. if err != nil && !errors.Is(err, os.ErrNotExist) {
  96. return err
  97. }
  98. stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
  99. if err != nil && !errors.Is(err, os.ErrNotExist) {
  100. return err
  101. }
  102. stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
  103. if err != nil && !errors.Is(err, os.ErrNotExist) {
  104. return err
  105. }
  106. stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
  107. if err != nil && !errors.Is(err, os.ErrNotExist) {
  108. return err
  109. }
  110. stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
  111. if err != nil && !errors.Is(err, os.ErrNotExist) {
  112. return err
  113. }
  114. stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
  115. if err != nil && !errors.Is(err, os.ErrNotExist) {
  116. return err
  117. }
  118. return nil
  119. }
  120. func (s *CpusetGroup) ApplyDir(dir string, r *configs.Resources, pid int) error {
  121. // This might happen if we have no cpuset cgroup mounted.
  122. // Just do nothing and don't fail.
  123. if dir == "" {
  124. return nil
  125. }
  126. // 'ensureParent' start with parent because we don't want to
  127. // explicitly inherit from parent, it could conflict with
  128. // 'cpuset.cpu_exclusive'.
  129. if err := cpusetEnsureParent(filepath.Dir(dir)); err != nil {
  130. return err
  131. }
  132. if err := os.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) {
  133. return err
  134. }
  135. // We didn't inherit cpuset configs from parent, but we have
  136. // to ensure cpuset configs are set before moving task into the
  137. // cgroup.
  138. // The logic is, if user specified cpuset configs, use these
  139. // specified configs, otherwise, inherit from parent. This makes
  140. // cpuset configs work correctly with 'cpuset.cpu_exclusive', and
  141. // keep backward compatibility.
  142. if err := s.ensureCpusAndMems(dir, r); err != nil {
  143. return err
  144. }
  145. // Since we are not using apply(), we need to place the pid
  146. // into the procs file.
  147. return cgroups.WriteCgroupProc(dir, pid)
  148. }
  149. func getCpusetSubsystemSettings(parent string) (cpus, mems string, err error) {
  150. if cpus, err = cgroups.ReadFile(parent, "cpuset.cpus"); err != nil {
  151. return
  152. }
  153. if mems, err = cgroups.ReadFile(parent, "cpuset.mems"); err != nil {
  154. return
  155. }
  156. return cpus, mems, nil
  157. }
  158. // cpusetEnsureParent makes sure that the parent directories of current
  159. // are created and populated with the proper cpus and mems files copied
  160. // from their respective parent. It does that recursively, starting from
  161. // the top of the cpuset hierarchy (i.e. cpuset cgroup mount point).
  162. func cpusetEnsureParent(current string) error {
  163. var st unix.Statfs_t
  164. parent := filepath.Dir(current)
  165. err := unix.Statfs(parent, &st)
  166. if err == nil && st.Type != unix.CGROUP_SUPER_MAGIC {
  167. return nil
  168. }
  169. // Treat non-existing directory as cgroupfs as it will be created,
  170. // and the root cpuset directory obviously exists.
  171. if err != nil && err != unix.ENOENT { //nolint:errorlint // unix errors are bare
  172. return &os.PathError{Op: "statfs", Path: parent, Err: err}
  173. }
  174. if err := cpusetEnsureParent(parent); err != nil {
  175. return err
  176. }
  177. if err := os.Mkdir(current, 0o755); err != nil && !os.IsExist(err) {
  178. return err
  179. }
  180. return cpusetCopyIfNeeded(current, parent)
  181. }
  182. // cpusetCopyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
  183. // directory to the current directory if the file's contents are 0
  184. func cpusetCopyIfNeeded(current, parent string) error {
  185. currentCpus, currentMems, err := getCpusetSubsystemSettings(current)
  186. if err != nil {
  187. return err
  188. }
  189. parentCpus, parentMems, err := getCpusetSubsystemSettings(parent)
  190. if err != nil {
  191. return err
  192. }
  193. if isEmptyCpuset(currentCpus) {
  194. if err := cgroups.WriteFile(current, "cpuset.cpus", parentCpus); err != nil {
  195. return err
  196. }
  197. }
  198. if isEmptyCpuset(currentMems) {
  199. if err := cgroups.WriteFile(current, "cpuset.mems", parentMems); err != nil {
  200. return err
  201. }
  202. }
  203. return nil
  204. }
  205. func isEmptyCpuset(str string) bool {
  206. return str == "" || str == "\n"
  207. }
  208. func (s *CpusetGroup) ensureCpusAndMems(path string, r *configs.Resources) error {
  209. if err := s.Set(path, r); err != nil {
  210. return err
  211. }
  212. return cpusetCopyIfNeeded(path, filepath.Dir(path))
  213. }