memory.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. // +build linux
  2. package fs
  3. import (
  4. "bufio"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "strconv"
  9. "strings"
  10. "github.com/opencontainers/runc/libcontainer/cgroups"
  11. "github.com/opencontainers/runc/libcontainer/configs"
  12. )
  13. type MemoryGroup struct {
  14. }
  15. func (s *MemoryGroup) Name() string {
  16. return "memory"
  17. }
  18. func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
  19. path, err := d.path("memory")
  20. if err != nil && !cgroups.IsNotFound(err) {
  21. return err
  22. }
  23. if memoryAssigned(d.config) {
  24. if path != "" {
  25. if err := os.MkdirAll(path, 0755); err != nil {
  26. return err
  27. }
  28. }
  29. // We have to set kernel memory here, as we can't change it once
  30. // processes have been attached.
  31. if err := s.SetKernelMemory(path, d.config); err != nil {
  32. return err
  33. }
  34. }
  35. defer func() {
  36. if err != nil {
  37. os.RemoveAll(path)
  38. }
  39. }()
  40. // We need to join memory cgroup after set memory limits, because
  41. // kmem.limit_in_bytes can only be set when the cgroup is empty.
  42. _, err = d.join("memory")
  43. if err != nil && !cgroups.IsNotFound(err) {
  44. return err
  45. }
  46. return nil
  47. }
  48. func (s *MemoryGroup) SetKernelMemory(path string, cgroup *configs.Cgroup) error {
  49. // This has to be done separately because it has special constraints (it
  50. // can't be done after there are processes attached to the cgroup).
  51. if cgroup.Resources.KernelMemory > 0 {
  52. if err := writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemory, 10)); err != nil {
  53. return err
  54. }
  55. }
  56. return nil
  57. }
  58. func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
  59. // When memory and swap memory are both set, we need to handle the cases
  60. // for updating container.
  61. if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap > 0 {
  62. memoryUsage, err := getMemoryData(path, "")
  63. if err != nil {
  64. return err
  65. }
  66. // When update memory limit, we should adapt the write sequence
  67. // for memory and swap memory, so it won't fail because the new
  68. // value and the old value don't fit kernel's validation.
  69. if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) {
  70. if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
  71. return err
  72. }
  73. if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
  74. return err
  75. }
  76. } else {
  77. if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
  78. return err
  79. }
  80. if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
  81. return err
  82. }
  83. }
  84. } else {
  85. if cgroup.Resources.Memory != 0 {
  86. if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
  87. return err
  88. }
  89. }
  90. if cgroup.Resources.MemorySwap > 0 {
  91. if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
  92. return err
  93. }
  94. }
  95. }
  96. return nil
  97. }
  98. func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
  99. if err := setMemoryAndSwap(path, cgroup); err != nil {
  100. return err
  101. }
  102. if cgroup.Resources.MemoryReservation != 0 {
  103. if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil {
  104. return err
  105. }
  106. }
  107. if cgroup.Resources.KernelMemoryTCP != 0 {
  108. if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil {
  109. return err
  110. }
  111. }
  112. if cgroup.Resources.OomKillDisable {
  113. if err := writeFile(path, "memory.oom_control", "1"); err != nil {
  114. return err
  115. }
  116. }
  117. if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 {
  118. return nil
  119. } else if int64(*cgroup.Resources.MemorySwappiness) >= 0 && int64(*cgroup.Resources.MemorySwappiness) <= 100 {
  120. if err := writeFile(path, "memory.swappiness", strconv.FormatInt(*cgroup.Resources.MemorySwappiness, 10)); err != nil {
  121. return err
  122. }
  123. } else {
  124. return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", int64(*cgroup.Resources.MemorySwappiness))
  125. }
  126. return nil
  127. }
  128. func (s *MemoryGroup) Remove(d *cgroupData) error {
  129. return removePath(d.path("memory"))
  130. }
  131. func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
  132. // Set stats from memory.stat.
  133. statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
  134. if err != nil {
  135. if os.IsNotExist(err) {
  136. return nil
  137. }
  138. return err
  139. }
  140. defer statsFile.Close()
  141. sc := bufio.NewScanner(statsFile)
  142. for sc.Scan() {
  143. t, v, err := getCgroupParamKeyValue(sc.Text())
  144. if err != nil {
  145. return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err)
  146. }
  147. stats.MemoryStats.Stats[t] = v
  148. }
  149. stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
  150. memoryUsage, err := getMemoryData(path, "")
  151. if err != nil {
  152. return err
  153. }
  154. stats.MemoryStats.Usage = memoryUsage
  155. swapUsage, err := getMemoryData(path, "memsw")
  156. if err != nil {
  157. return err
  158. }
  159. stats.MemoryStats.SwapUsage = swapUsage
  160. kernelUsage, err := getMemoryData(path, "kmem")
  161. if err != nil {
  162. return err
  163. }
  164. stats.MemoryStats.KernelUsage = kernelUsage
  165. kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
  166. if err != nil {
  167. return err
  168. }
  169. stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
  170. return nil
  171. }
  172. func memoryAssigned(cgroup *configs.Cgroup) bool {
  173. return cgroup.Resources.Memory != 0 ||
  174. cgroup.Resources.MemoryReservation != 0 ||
  175. cgroup.Resources.MemorySwap > 0 ||
  176. cgroup.Resources.KernelMemory > 0 ||
  177. cgroup.Resources.KernelMemoryTCP > 0 ||
  178. cgroup.Resources.OomKillDisable ||
  179. (cgroup.Resources.MemorySwappiness != nil && *cgroup.Resources.MemorySwappiness != -1)
  180. }
  181. func getMemoryData(path, name string) (cgroups.MemoryData, error) {
  182. memoryData := cgroups.MemoryData{}
  183. moduleName := "memory"
  184. if name != "" {
  185. moduleName = strings.Join([]string{"memory", name}, ".")
  186. }
  187. usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".")
  188. maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".")
  189. failcnt := strings.Join([]string{moduleName, "failcnt"}, ".")
  190. limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".")
  191. value, err := getCgroupParamUint(path, usage)
  192. if err != nil {
  193. if moduleName != "memory" && os.IsNotExist(err) {
  194. return cgroups.MemoryData{}, nil
  195. }
  196. return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err)
  197. }
  198. memoryData.Usage = value
  199. value, err = getCgroupParamUint(path, maxUsage)
  200. if err != nil {
  201. if moduleName != "memory" && os.IsNotExist(err) {
  202. return cgroups.MemoryData{}, nil
  203. }
  204. return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err)
  205. }
  206. memoryData.MaxUsage = value
  207. value, err = getCgroupParamUint(path, failcnt)
  208. if err != nil {
  209. if moduleName != "memory" && os.IsNotExist(err) {
  210. return cgroups.MemoryData{}, nil
  211. }
  212. return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err)
  213. }
  214. memoryData.Failcnt = value
  215. value, err = getCgroupParamUint(path, limit)
  216. if err != nil {
  217. if moduleName != "memory" && os.IsNotExist(err) {
  218. return cgroups.MemoryData{}, nil
  219. }
  220. return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err)
  221. }
  222. memoryData.Limit = value
  223. return memoryData, nil
  224. }