Merge pull request #426 from gitido/pressure_level

libcontainer: Add support for memcg pressure notifications
This commit is contained in:
Mrunal Patel 2016-01-14 16:23:07 -08:00
commit 6259f09e97
3 changed files with 95 additions and 27 deletions

View File

@ -104,6 +104,12 @@ type Container interface {
// errors: // errors:
// Systemerror - System error. // Systemerror - System error.
NotifyOOM() (<-chan struct{}, error) NotifyOOM() (<-chan struct{}, error)
// NotifyMemoryPressure returns a read-only channel signaling when the container reaches a given pressure level
//
// errors:
// Systemerror - System error.
NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error)
} }
// ID returns the container's unique ID // ID returns the container's unique ID
@ -357,6 +363,10 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
return notifyOnOOM(c.cgroupManager.GetPaths()) return notifyOnOOM(c.cgroupManager.GetPaths())
} }
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
}
// XXX debug support, remove when debugging done. // XXX debug support, remove when debugging done.
func addArgsFromEnv(evar string, args *[]string) { func addArgsFromEnv(evar string, args *[]string) {
if e := os.Getenv(evar); e != "" { if e := os.Getenv(evar); e != "" {

View File

@ -12,31 +12,32 @@ import (
const oomCgroupName = "memory" const oomCgroupName = "memory"
// notifyOnOOM returns channel on which you can expect event about OOM, type PressureLevel uint
// if process died without OOM this channel will be closed.
// s is current *libcontainer.State for container. const (
func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { LowPressure PressureLevel = iota
dir := paths[oomCgroupName] MediumPressure
if dir == "" { CriticalPressure
return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName) )
}
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) {
evFile, err := os.Open(filepath.Join(cgDir, evName))
if err != nil { if err != nil {
return nil, err return nil, err
} }
fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0) fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
if syserr != 0 { if syserr != 0 {
oomControl.Close() evFile.Close()
return nil, syserr return nil, syserr
} }
eventfd := os.NewFile(fd, "eventfd") eventfd := os.NewFile(fd, "eventfd")
eventControlPath := filepath.Join(dir, "cgroup.event_control") eventControlPath := filepath.Join(cgDir, "cgroup.event_control")
data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd()) data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg)
if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
eventfd.Close() eventfd.Close()
oomControl.Close() evFile.Close()
return nil, err return nil, err
} }
ch := make(chan struct{}) ch := make(chan struct{})
@ -44,7 +45,7 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
defer func() { defer func() {
close(ch) close(ch)
eventfd.Close() eventfd.Close()
oomControl.Close() evFile.Close()
}() }()
buf := make([]byte, 8) buf := make([]byte, 8)
for { for {
@ -61,3 +62,28 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
}() }()
return ch, nil return ch, nil
} }
// notifyOnOOM returns channel on which you can expect event about OOM,
// if process died without OOM this channel will be closed.
func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
dir := paths[oomCgroupName]
if dir == "" {
return nil, fmt.Errorf("path %q missing", oomCgroupName)
}
return registerMemoryEvent(dir, "memory.oom_control", "")
}
func notifyMemoryPressure(paths map[string]string, level PressureLevel) (<-chan struct{}, error) {
dir := paths[oomCgroupName]
if dir == "" {
return nil, fmt.Errorf("path %q missing", oomCgroupName)
}
if level > CriticalPressure {
return nil, fmt.Errorf("invalid pressure level %d", level)
}
levelStr := []string{"low", "medium", "critical"}[level]
return registerMemoryEvent(dir, "memory.pressure_level", levelStr)
}

View File

@ -13,24 +13,25 @@ import (
"time" "time"
) )
func TestNotifyOnOOM(t *testing.T) { type notifyFunc func(paths map[string]string) (<-chan struct{}, error)
memoryPath, err := ioutil.TempDir("", "testnotifyoom-")
func testMemoryNotification(t *testing.T, evName string, notify notifyFunc, targ string) {
memoryPath, err := ioutil.TempDir("", "testmemnotification-"+evName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
oomPath := filepath.Join(memoryPath, "memory.oom_control") evFile := filepath.Join(memoryPath, evName)
eventPath := filepath.Join(memoryPath, "cgroup.event_control") eventPath := filepath.Join(memoryPath, "cgroup.event_control")
if err := ioutil.WriteFile(oomPath, []byte{}, 0700); err != nil { if err := ioutil.WriteFile(evFile, []byte{}, 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil { if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
var eventFd, oomControlFd int
paths := map[string]string{ paths := map[string]string{
"memory": memoryPath, "memory": memoryPath,
} }
ooms, err := notifyOnOOM(paths) ch, err := notify(paths)
if err != nil { if err != nil {
t.Fatal("expected no error, got:", err) t.Fatal("expected no error, got:", err)
} }
@ -40,7 +41,14 @@ func TestNotifyOnOOM(t *testing.T) {
t.Fatal("couldn't read event control file:", err) t.Fatal("couldn't read event control file:", err)
} }
if _, err := fmt.Sscanf(string(data), "%d %d", &eventFd, &oomControlFd); err != nil { var eventFd, evFd int
var arg string
if targ != "" {
_, err = fmt.Sscanf(string(data), "%d %d %s", &eventFd, &evFd, &arg)
} else {
_, err = fmt.Sscanf(string(data), "%d %d", &eventFd, &evFd)
}
if err != nil || arg != targ {
t.Fatalf("invalid control data %q: %s", data, err) t.Fatalf("invalid control data %q: %s", data, err)
} }
@ -63,9 +71,9 @@ func TestNotifyOnOOM(t *testing.T) {
} }
select { select {
case <-ooms: case <-ch:
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
t.Fatal("no notification on oom channel after 100ms") t.Fatal("no notification on channel after 100ms")
} }
// simulate what happens when a cgroup is destroyed by cleaning up and then // simulate what happens when a cgroup is destroyed by cleaning up and then
@ -79,18 +87,42 @@ func TestNotifyOnOOM(t *testing.T) {
// give things a moment to shut down // give things a moment to shut down
select { select {
case _, ok := <-ooms: case _, ok := <-ch:
if ok { if ok {
t.Fatal("expected no oom to be triggered") t.Fatal("expected no notification to be triggered")
} }
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
} }
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(oomControlFd), syscall.F_GETFD, 0); err != syscall.EBADF { if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(evFd), syscall.F_GETFD, 0); err != syscall.EBADF {
t.Error("expected oom control to be closed") t.Error("expected event control to be closed")
} }
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(eventFd), syscall.F_GETFD, 0); err != syscall.EBADF { if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(eventFd), syscall.F_GETFD, 0); err != syscall.EBADF {
t.Error("expected event fd to be closed") t.Error("expected event fd to be closed")
} }
} }
func TestNotifyOnOOM(t *testing.T) {
f := func(paths map[string]string) (<-chan struct{}, error) {
return notifyOnOOM(paths)
}
testMemoryNotification(t, "memory.oom_control", f, "")
}
func TestNotifyMemoryPressure(t *testing.T) {
tests := map[PressureLevel]string{
LowPressure: "low",
MediumPressure: "medium",
CriticalPressure: "critical",
}
for level, arg := range tests {
f := func(paths map[string]string) (<-chan struct{}, error) {
return notifyMemoryPressure(paths, level)
}
testMemoryNotification(t, "memory.pressure_level", f, arg)
}
}