memory.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /*
  2. Copyright The containerd Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cgroup1
  14. import (
  15. "bufio"
  16. "fmt"
  17. "io"
  18. "os"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
  23. specs "github.com/opencontainers/runtime-spec/specs-go"
  24. "golang.org/x/sys/unix"
  25. )
  26. // MemoryEvent is an interface that V1 memory Cgroup notifications implement. Arg returns the
  27. // file name whose fd should be written to "cgroups.event_control". EventFile returns the name of
  28. // the file that supports the notification api e.g. "memory.usage_in_bytes".
  29. type MemoryEvent interface {
  30. Arg() string
  31. EventFile() string
  32. }
  33. type memoryThresholdEvent struct {
  34. threshold uint64
  35. swap bool
  36. }
  37. // MemoryThresholdEvent returns a new [MemoryEvent] representing the memory threshold set.
  38. // If swap is true, the event will be registered using memory.memsw.usage_in_bytes
  39. func MemoryThresholdEvent(threshold uint64, swap bool) MemoryEvent {
  40. return &memoryThresholdEvent{
  41. threshold,
  42. swap,
  43. }
  44. }
  45. func (m *memoryThresholdEvent) Arg() string {
  46. return strconv.FormatUint(m.threshold, 10)
  47. }
  48. func (m *memoryThresholdEvent) EventFile() string {
  49. if m.swap {
  50. return "memory.memsw.usage_in_bytes"
  51. }
  52. return "memory.usage_in_bytes"
  53. }
  54. type oomEvent struct{}
  55. // OOMEvent returns a new oom event to be used with RegisterMemoryEvent.
  56. func OOMEvent() MemoryEvent {
  57. return &oomEvent{}
  58. }
  59. func (oom *oomEvent) Arg() string {
  60. return ""
  61. }
  62. func (oom *oomEvent) EventFile() string {
  63. return "memory.oom_control"
  64. }
  65. type memoryPressureEvent struct {
  66. pressureLevel MemoryPressureLevel
  67. hierarchy EventNotificationMode
  68. }
  69. // MemoryPressureEvent returns a new [MemoryEvent] representing the memory pressure set.
  70. func MemoryPressureEvent(pressureLevel MemoryPressureLevel, hierarchy EventNotificationMode) MemoryEvent {
  71. return &memoryPressureEvent{
  72. pressureLevel,
  73. hierarchy,
  74. }
  75. }
  76. func (m *memoryPressureEvent) Arg() string {
  77. return string(m.pressureLevel) + "," + string(m.hierarchy)
  78. }
  79. func (m *memoryPressureEvent) EventFile() string {
  80. return "memory.pressure_level"
  81. }
  82. // MemoryPressureLevel corresponds to the memory pressure levels defined
  83. // for memory cgroups.
  84. type MemoryPressureLevel string
  85. // The three memory pressure levels are as follows.
  86. // - The "low" level means that the system is reclaiming memory for new
  87. // allocations. Monitoring this reclaiming activity might be useful for
  88. // maintaining cache level. Upon notification, the program (typically
  89. // "Activity Manager") might analyze vmstat and act in advance (i.e.
  90. // prematurely shutdown unimportant services).
  91. // - The "medium" level means that the system is experiencing medium memory
  92. // pressure, the system might be making swap, paging out active file caches,
  93. // etc. Upon this event applications may decide to further analyze
  94. // vmstat/zoneinfo/memcg or internal memory usage statistics and free any
  95. // resources that can be easily reconstructed or re-read from a disk.
  96. // - The "critical" level means that the system is actively thrashing, it is
  97. // about to out of memory (OOM) or even the in-kernel OOM killer is on its
  98. // way to trigger. Applications should do whatever they can to help the
  99. // system. It might be too late to consult with vmstat or any other
  100. // statistics, so it is advisable to take an immediate action.
  101. // "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
  102. const (
  103. LowPressure MemoryPressureLevel = "low"
  104. MediumPressure MemoryPressureLevel = "medium"
  105. CriticalPressure MemoryPressureLevel = "critical"
  106. )
  107. // EventNotificationMode corresponds to the notification modes
  108. // for the memory cgroups pressure level notifications.
  109. type EventNotificationMode string
  110. // There are three optional modes that specify different propagation behavior:
  111. // - "default": this is the default behavior specified above. This mode is the
  112. // same as omitting the optional mode parameter, preserved by backwards
  113. // compatibility.
  114. // - "hierarchy": events always propagate up to the root, similar to the default
  115. // behavior, except that propagation continues regardless of whether there are
  116. // event listeners at each level, with the "hierarchy" mode. In the above
  117. // example, groups A, B, and C will receive notification of memory pressure.
  118. // - "local": events are pass-through, i.e. they only receive notifications when
  119. // memory pressure is experienced in the memcg for which the notification is
  120. // registered. In the above example, group C will receive notification if
  121. // registered for "local" notification and the group experiences memory
  122. // pressure. However, group B will never receive notification, regardless if
  123. // there is an event listener for group C or not, if group B is registered for
  124. // local notification.
  125. // "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
  126. const (
  127. DefaultMode EventNotificationMode = "default"
  128. LocalMode EventNotificationMode = "local"
  129. HierarchyMode EventNotificationMode = "hierarchy"
  130. )
  131. // NewMemory returns a Memory controller given the root folder of cgroups.
  132. // It may optionally accept other configuration options, such as IgnoreModules(...)
  133. func NewMemory(root string, options ...func(*memoryController)) *memoryController {
  134. mc := &memoryController{
  135. root: filepath.Join(root, string(Memory)),
  136. ignored: map[string]struct{}{},
  137. }
  138. for _, opt := range options {
  139. opt(mc)
  140. }
  141. return mc
  142. }
  143. // IgnoreModules configure the memory controller to not read memory metrics for some
  144. // module names (e.g. passing "memsw" would avoid all the memory.memsw.* entries)
  145. func IgnoreModules(names ...string) func(*memoryController) {
  146. return func(mc *memoryController) {
  147. for _, name := range names {
  148. mc.ignored[name] = struct{}{}
  149. }
  150. }
  151. }
  152. // OptionalSwap allows the memory controller to not fail if cgroups is not accounting
  153. // Swap memory (there are no memory.memsw.* entries)
  154. func OptionalSwap() func(*memoryController) {
  155. return func(mc *memoryController) {
  156. _, err := os.Stat(filepath.Join(mc.root, "memory.memsw.usage_in_bytes"))
  157. if os.IsNotExist(err) {
  158. mc.ignored["memsw"] = struct{}{}
  159. }
  160. }
  161. }
  162. type memoryController struct {
  163. root string
  164. ignored map[string]struct{}
  165. }
  166. func (m *memoryController) Name() Name {
  167. return Memory
  168. }
  169. func (m *memoryController) Path(path string) string {
  170. return filepath.Join(m.root, path)
  171. }
  172. func (m *memoryController) Create(path string, resources *specs.LinuxResources) error {
  173. if err := os.MkdirAll(m.Path(path), defaultDirPerm); err != nil {
  174. return err
  175. }
  176. if resources.Memory == nil {
  177. return nil
  178. }
  179. return m.set(path, getMemorySettings(resources))
  180. }
  181. func (m *memoryController) Update(path string, resources *specs.LinuxResources) error {
  182. if resources.Memory == nil {
  183. return nil
  184. }
  185. g := func(v *int64) bool {
  186. return v != nil && *v > 0
  187. }
  188. settings := getMemorySettings(resources)
  189. if g(resources.Memory.Limit) && g(resources.Memory.Swap) {
  190. // if the updated swap value is larger than the current memory limit set the swap changes first
  191. // then set the memory limit as swap must always be larger than the current limit
  192. current, err := readUint(filepath.Join(m.Path(path), "memory.limit_in_bytes"))
  193. if err != nil {
  194. return err
  195. }
  196. if current < uint64(*resources.Memory.Swap) {
  197. settings[0], settings[1] = settings[1], settings[0]
  198. }
  199. }
  200. return m.set(path, settings)
  201. }
  202. func (m *memoryController) Stat(path string, stats *v1.Metrics) error {
  203. fMemStat, err := os.Open(filepath.Join(m.Path(path), "memory.stat"))
  204. if err != nil {
  205. return err
  206. }
  207. defer fMemStat.Close()
  208. stats.Memory = &v1.MemoryStat{
  209. Usage: &v1.MemoryEntry{},
  210. Swap: &v1.MemoryEntry{},
  211. Kernel: &v1.MemoryEntry{},
  212. KernelTCP: &v1.MemoryEntry{},
  213. }
  214. if err := m.parseStats(fMemStat, stats.Memory); err != nil {
  215. return err
  216. }
  217. fMemOomControl, err := os.Open(filepath.Join(m.Path(path), "memory.oom_control"))
  218. if err != nil {
  219. return err
  220. }
  221. defer fMemOomControl.Close()
  222. stats.MemoryOomControl = &v1.MemoryOomControl{}
  223. if err := m.parseOomControlStats(fMemOomControl, stats.MemoryOomControl); err != nil {
  224. return err
  225. }
  226. for _, t := range []struct {
  227. module string
  228. entry *v1.MemoryEntry
  229. }{
  230. {
  231. module: "",
  232. entry: stats.Memory.Usage,
  233. },
  234. {
  235. module: "memsw",
  236. entry: stats.Memory.Swap,
  237. },
  238. {
  239. module: "kmem",
  240. entry: stats.Memory.Kernel,
  241. },
  242. {
  243. module: "kmem.tcp",
  244. entry: stats.Memory.KernelTCP,
  245. },
  246. } {
  247. if _, ok := m.ignored[t.module]; ok {
  248. continue
  249. }
  250. for _, tt := range []struct {
  251. name string
  252. value *uint64
  253. }{
  254. {
  255. name: "usage_in_bytes",
  256. value: &t.entry.Usage,
  257. },
  258. {
  259. name: "max_usage_in_bytes",
  260. value: &t.entry.Max,
  261. },
  262. {
  263. name: "failcnt",
  264. value: &t.entry.Failcnt,
  265. },
  266. {
  267. name: "limit_in_bytes",
  268. value: &t.entry.Limit,
  269. },
  270. } {
  271. parts := []string{"memory"}
  272. if t.module != "" {
  273. parts = append(parts, t.module)
  274. }
  275. parts = append(parts, tt.name)
  276. v, err := readUint(filepath.Join(m.Path(path), strings.Join(parts, ".")))
  277. if err != nil {
  278. return err
  279. }
  280. *tt.value = v
  281. }
  282. }
  283. return nil
  284. }
  285. func (m *memoryController) parseStats(r io.Reader, stat *v1.MemoryStat) error {
  286. var (
  287. raw = make(map[string]uint64)
  288. sc = bufio.NewScanner(r)
  289. line int
  290. )
  291. for sc.Scan() {
  292. key, v, err := parseKV(sc.Text())
  293. if err != nil {
  294. return fmt.Errorf("%d: %v", line, err)
  295. }
  296. raw[key] = v
  297. line++
  298. }
  299. if err := sc.Err(); err != nil {
  300. return err
  301. }
  302. stat.Cache = raw["cache"]
  303. stat.RSS = raw["rss"]
  304. stat.RSSHuge = raw["rss_huge"]
  305. stat.MappedFile = raw["mapped_file"]
  306. stat.Dirty = raw["dirty"]
  307. stat.Writeback = raw["writeback"]
  308. stat.PgPgIn = raw["pgpgin"]
  309. stat.PgPgOut = raw["pgpgout"]
  310. stat.PgFault = raw["pgfault"]
  311. stat.PgMajFault = raw["pgmajfault"]
  312. stat.InactiveAnon = raw["inactive_anon"]
  313. stat.ActiveAnon = raw["active_anon"]
  314. stat.InactiveFile = raw["inactive_file"]
  315. stat.ActiveFile = raw["active_file"]
  316. stat.Unevictable = raw["unevictable"]
  317. stat.HierarchicalMemoryLimit = raw["hierarchical_memory_limit"]
  318. stat.HierarchicalSwapLimit = raw["hierarchical_memsw_limit"]
  319. stat.TotalCache = raw["total_cache"]
  320. stat.TotalRSS = raw["total_rss"]
  321. stat.TotalRSSHuge = raw["total_rss_huge"]
  322. stat.TotalMappedFile = raw["total_mapped_file"]
  323. stat.TotalDirty = raw["total_dirty"]
  324. stat.TotalWriteback = raw["total_writeback"]
  325. stat.TotalPgPgIn = raw["total_pgpgin"]
  326. stat.TotalPgPgOut = raw["total_pgpgout"]
  327. stat.TotalPgFault = raw["total_pgfault"]
  328. stat.TotalPgMajFault = raw["total_pgmajfault"]
  329. stat.TotalInactiveAnon = raw["total_inactive_anon"]
  330. stat.TotalActiveAnon = raw["total_active_anon"]
  331. stat.TotalInactiveFile = raw["total_inactive_file"]
  332. stat.TotalActiveFile = raw["total_active_file"]
  333. stat.TotalUnevictable = raw["total_unevictable"]
  334. return nil
  335. }
  336. func (m *memoryController) parseOomControlStats(r io.Reader, stat *v1.MemoryOomControl) error {
  337. var (
  338. raw = make(map[string]uint64)
  339. sc = bufio.NewScanner(r)
  340. line int
  341. )
  342. for sc.Scan() {
  343. key, v, err := parseKV(sc.Text())
  344. if err != nil {
  345. return fmt.Errorf("%d: %v", line, err)
  346. }
  347. raw[key] = v
  348. line++
  349. }
  350. if err := sc.Err(); err != nil {
  351. return err
  352. }
  353. stat.OomKillDisable = raw["oom_kill_disable"]
  354. stat.UnderOom = raw["under_oom"]
  355. stat.OomKill = raw["oom_kill"]
  356. return nil
  357. }
  358. func (m *memoryController) set(path string, settings []memorySettings) error {
  359. for _, t := range settings {
  360. if t.value != nil {
  361. if err := os.WriteFile(
  362. filepath.Join(m.Path(path), "memory."+t.name),
  363. []byte(strconv.FormatInt(*t.value, 10)),
  364. defaultFilePerm,
  365. ); err != nil {
  366. return err
  367. }
  368. }
  369. }
  370. return nil
  371. }
  372. type memorySettings struct {
  373. name string
  374. value *int64
  375. }
  376. func getMemorySettings(resources *specs.LinuxResources) []memorySettings {
  377. mem := resources.Memory
  378. var swappiness *int64
  379. if mem.Swappiness != nil {
  380. v := int64(*mem.Swappiness)
  381. swappiness = &v
  382. }
  383. return []memorySettings{
  384. {
  385. name: "limit_in_bytes",
  386. value: mem.Limit,
  387. },
  388. {
  389. name: "soft_limit_in_bytes",
  390. value: mem.Reservation,
  391. },
  392. {
  393. name: "memsw.limit_in_bytes",
  394. value: mem.Swap,
  395. },
  396. {
  397. name: "kmem.limit_in_bytes",
  398. value: mem.Kernel,
  399. },
  400. {
  401. name: "kmem.tcp.limit_in_bytes",
  402. value: mem.KernelTCP,
  403. },
  404. {
  405. name: "oom_control",
  406. value: getOomControlValue(mem),
  407. },
  408. {
  409. name: "swappiness",
  410. value: swappiness,
  411. },
  412. }
  413. }
  414. func getOomControlValue(mem *specs.LinuxMemory) *int64 {
  415. if mem.DisableOOMKiller != nil && *mem.DisableOOMKiller {
  416. i := int64(1)
  417. return &i
  418. }
  419. return nil
  420. }
  421. func (m *memoryController) memoryEvent(path string, event MemoryEvent) (uintptr, error) {
  422. root := m.Path(path)
  423. efd, err := unix.Eventfd(0, unix.EFD_CLOEXEC)
  424. if err != nil {
  425. return 0, err
  426. }
  427. evtFile, err := os.Open(filepath.Join(root, event.EventFile()))
  428. if err != nil {
  429. unix.Close(efd)
  430. return 0, err
  431. }
  432. defer evtFile.Close()
  433. data := fmt.Sprintf("%d %d %s", efd, evtFile.Fd(), event.Arg())
  434. evctlPath := filepath.Join(root, "cgroup.event_control")
  435. if err := os.WriteFile(evctlPath, []byte(data), 0o700); err != nil {
  436. unix.Close(efd)
  437. return 0, err
  438. }
  439. return uintptr(efd), nil
  440. }