Skip to content

Commit 55a8d68

Browse files
author
Ido Yariv
committed
libcontainer: Add support for memcg pressure notifications
It may be desirable to receive memory pressure levels notifications before the container depletes all memory. This may be useful for handling cases where the system thrashes when reaching the container's memory limits. Signed-off-by: Ido Yariv <[email protected]>
1 parent d97d5e8 commit 55a8d68

File tree

3 files changed

+95
-27
lines changed

3 files changed

+95
-27
lines changed

libcontainer/container_linux.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ type Container interface {
103103
// errors:
104104
// Systemerror - System error.
105105
NotifyOOM() (<-chan struct{}, error)
106+
107+
// NotifyMemoryPressure returns a read-only channel signaling when the container reaches a given pressure level
108+
//
109+
// errors:
110+
// Systemerror - System error.
111+
NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error)
106112
}
107113

108114
// ID returns the container's unique ID
@@ -368,6 +374,10 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
368374
return notifyOnOOM(c.cgroupManager.GetPaths())
369375
}
370376

377+
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
378+
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
379+
}
380+
371381
// XXX debug support, remove when debugging done.
372382
func addArgsFromEnv(evar string, args *[]string) {
373383
if e := os.Getenv(evar); e != "" {

libcontainer/notify_linux.go

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,40 @@ import (
1212

1313
const oomCgroupName = "memory"
1414

15-
// notifyOnOOM returns channel on which you can expect event about OOM,
16-
// if process died without OOM this channel will be closed.
17-
// s is current *libcontainer.State for container.
18-
func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
19-
dir := paths[oomCgroupName]
20-
if dir == "" {
21-
return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName)
22-
}
23-
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control"))
15+
type PressureLevel uint
16+
17+
const (
18+
LowPressure PressureLevel = iota
19+
MediumPressure
20+
CriticalPressure
21+
)
22+
23+
func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) {
24+
evFile, err := os.Open(filepath.Join(cgDir, evName))
2425
if err != nil {
2526
return nil, err
2627
}
2728
fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
2829
if syserr != 0 {
29-
oomControl.Close()
30+
evFile.Close()
3031
return nil, syserr
3132
}
3233

3334
eventfd := os.NewFile(fd, "eventfd")
3435

35-
eventControlPath := filepath.Join(dir, "cgroup.event_control")
36-
data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
36+
eventControlPath := filepath.Join(cgDir, "cgroup.event_control")
37+
data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg)
3738
if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
3839
eventfd.Close()
39-
oomControl.Close()
40+
evFile.Close()
4041
return nil, err
4142
}
4243
ch := make(chan struct{})
4344
go func() {
4445
defer func() {
4546
close(ch)
4647
eventfd.Close()
47-
oomControl.Close()
48+
evFile.Close()
4849
}()
4950
buf := make([]byte, 8)
5051
for {
@@ -61,3 +62,28 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
6162
}()
6263
return ch, nil
6364
}
65+
66+
// notifyOnOOM returns channel on which you can expect event about OOM,
67+
// if process died without OOM this channel will be closed.
68+
func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
69+
dir := paths[oomCgroupName]
70+
if dir == "" {
71+
return nil, fmt.Errorf("path %q missing", oomCgroupName)
72+
}
73+
74+
return registerMemoryEvent(dir, "memory.oom_control", "")
75+
}
76+
77+
func notifyMemoryPressure(paths map[string]string, level PressureLevel) (<-chan struct{}, error) {
78+
dir := paths[oomCgroupName]
79+
if dir == "" {
80+
return nil, fmt.Errorf("path %q missing", oomCgroupName)
81+
}
82+
83+
if level > CriticalPressure {
84+
return nil, fmt.Errorf("invalid pressure level %d", level)
85+
}
86+
87+
levelStr := []string{"low", "medium", "critical"}[level]
88+
return registerMemoryEvent(dir, "memory.pressure_level", levelStr)
89+
}

libcontainer/notify_linux_test.go

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,25 @@ import (
1313
"time"
1414
)
1515

16-
func TestNotifyOnOOM(t *testing.T) {
17-
memoryPath, err := ioutil.TempDir("", "testnotifyoom-")
16+
type notifyFunc func(paths map[string]string) (<-chan struct{}, error)
17+
18+
func testMemoryNotification(t *testing.T, evName string, notify notifyFunc, targ string) {
19+
memoryPath, err := ioutil.TempDir("", "testmemnotification-"+evName)
1820
if err != nil {
1921
t.Fatal(err)
2022
}
21-
oomPath := filepath.Join(memoryPath, "memory.oom_control")
23+
evFile := filepath.Join(memoryPath, evName)
2224
eventPath := filepath.Join(memoryPath, "cgroup.event_control")
23-
if err := ioutil.WriteFile(oomPath, []byte{}, 0700); err != nil {
25+
if err := ioutil.WriteFile(evFile, []byte{}, 0700); err != nil {
2426
t.Fatal(err)
2527
}
2628
if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil {
2729
t.Fatal(err)
2830
}
29-
var eventFd, oomControlFd int
3031
paths := map[string]string{
3132
"memory": memoryPath,
3233
}
33-
ooms, err := notifyOnOOM(paths)
34+
ch, err := notify(paths)
3435
if err != nil {
3536
t.Fatal("expected no error, got:", err)
3637
}
@@ -40,7 +41,14 @@ func TestNotifyOnOOM(t *testing.T) {
4041
t.Fatal("couldn't read event control file:", err)
4142
}
4243

43-
if _, err := fmt.Sscanf(string(data), "%d %d", &eventFd, &oomControlFd); err != nil {
44+
var eventFd, evFd int
45+
var arg string
46+
if targ != "" {
47+
_, err = fmt.Sscanf(string(data), "%d %d %s", &eventFd, &evFd, &arg)
48+
} else {
49+
_, err = fmt.Sscanf(string(data), "%d %d", &eventFd, &evFd)
50+
}
51+
if err != nil || arg != targ {
4452
t.Fatalf("invalid control data %q: %s", data, err)
4553
}
4654

@@ -63,9 +71,9 @@ func TestNotifyOnOOM(t *testing.T) {
6371
}
6472

6573
select {
66-
case <-ooms:
74+
case <-ch:
6775
case <-time.After(100 * time.Millisecond):
68-
t.Fatal("no notification on oom channel after 100ms")
76+
t.Fatal("no notification on channel after 100ms")
6977
}
7078

7179
// simulate what happens when a cgroup is destroyed by cleaning up and then
@@ -79,18 +87,42 @@ func TestNotifyOnOOM(t *testing.T) {
7987

8088
// give things a moment to shut down
8189
select {
82-
case _, ok := <-ooms:
90+
case _, ok := <-ch:
8391
if ok {
84-
t.Fatal("expected no oom to be triggered")
92+
t.Fatal("expected no notification to be triggered")
8593
}
8694
case <-time.After(100 * time.Millisecond):
8795
}
8896

89-
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(oomControlFd), syscall.F_GETFD, 0); err != syscall.EBADF {
90-
t.Error("expected oom control to be closed")
97+
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(evFd), syscall.F_GETFD, 0); err != syscall.EBADF {
98+
t.Error("expected event control to be closed")
9199
}
92100

93101
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(eventFd), syscall.F_GETFD, 0); err != syscall.EBADF {
94102
t.Error("expected event fd to be closed")
95103
}
96104
}
105+
106+
func TestNotifyOnOOM(t *testing.T) {
107+
f := func(paths map[string]string) (<-chan struct{}, error) {
108+
return notifyOnOOM(paths)
109+
}
110+
111+
testMemoryNotification(t, "memory.oom_control", f, "")
112+
}
113+
114+
func TestNotifyMemoryPressure(t *testing.T) {
115+
tests := map[PressureLevel]string{
116+
LowPressure: "low",
117+
MediumPressure: "medium",
118+
CriticalPressure: "critical",
119+
}
120+
121+
for level, arg := range tests {
122+
f := func(paths map[string]string) (<-chan struct{}, error) {
123+
return notifyMemoryPressure(paths, level)
124+
}
125+
126+
testMemoryNotification(t, "memory.pressure_level", f, arg)
127+
}
128+
}

0 commit comments

Comments
 (0)