Skip to content

Commit 6259f09

Browse files
author
Mrunal Patel
committed
Merge pull request #426 from gitido/pressure_level
libcontainer: Add support for memcg pressure notifications
2 parents 8962f37 + 55a8d68 commit 6259f09

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
@@ -104,6 +104,12 @@ type Container interface {
104104
// errors:
105105
// Systemerror - System error.
106106
NotifyOOM() (<-chan struct{}, error)
107+
108+
// NotifyMemoryPressure returns a read-only channel signaling when the container reaches a given pressure level
109+
//
110+
// errors:
111+
// Systemerror - System error.
112+
NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error)
107113
}
108114

109115
// ID returns the container's unique ID
@@ -357,6 +363,10 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
357363
return notifyOnOOM(c.cgroupManager.GetPaths())
358364
}
359365

366+
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
367+
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
368+
}
369+
360370
// XXX debug support, remove when debugging done.
361371
func addArgsFromEnv(evar string, args *[]string) {
362372
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)