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 <ido@wizery.com>
This commit is contained in:
parent
d97d5e8b00
commit
55a8d686a9
|
@ -103,6 +103,12 @@ type Container interface {
|
|||
// errors:
|
||||
// Systemerror - System 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
|
||||
|
@ -368,6 +374,10 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
|
|||
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.
|
||||
func addArgsFromEnv(evar string, args *[]string) {
|
||||
if e := os.Getenv(evar); e != "" {
|
||||
|
|
|
@ -12,31 +12,32 @@ import (
|
|||
|
||||
const oomCgroupName = "memory"
|
||||
|
||||
// notifyOnOOM returns channel on which you can expect event about OOM,
|
||||
// if process died without OOM this channel will be closed.
|
||||
// s is current *libcontainer.State for container.
|
||||
func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
|
||||
dir := paths[oomCgroupName]
|
||||
if dir == "" {
|
||||
return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName)
|
||||
}
|
||||
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control"))
|
||||
type PressureLevel uint
|
||||
|
||||
const (
|
||||
LowPressure PressureLevel = iota
|
||||
MediumPressure
|
||||
CriticalPressure
|
||||
)
|
||||
|
||||
func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) {
|
||||
evFile, err := os.Open(filepath.Join(cgDir, evName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
|
||||
if syserr != 0 {
|
||||
oomControl.Close()
|
||||
evFile.Close()
|
||||
return nil, syserr
|
||||
}
|
||||
|
||||
eventfd := os.NewFile(fd, "eventfd")
|
||||
|
||||
eventControlPath := filepath.Join(dir, "cgroup.event_control")
|
||||
data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
|
||||
eventControlPath := filepath.Join(cgDir, "cgroup.event_control")
|
||||
data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg)
|
||||
if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
|
||||
eventfd.Close()
|
||||
oomControl.Close()
|
||||
evFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
ch := make(chan struct{})
|
||||
|
@ -44,7 +45,7 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
|
|||
defer func() {
|
||||
close(ch)
|
||||
eventfd.Close()
|
||||
oomControl.Close()
|
||||
evFile.Close()
|
||||
}()
|
||||
buf := make([]byte, 8)
|
||||
for {
|
||||
|
@ -61,3 +62,28 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
|
|||
}()
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -13,24 +13,25 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func TestNotifyOnOOM(t *testing.T) {
|
||||
memoryPath, err := ioutil.TempDir("", "testnotifyoom-")
|
||||
type notifyFunc func(paths map[string]string) (<-chan struct{}, error)
|
||||
|
||||
func testMemoryNotification(t *testing.T, evName string, notify notifyFunc, targ string) {
|
||||
memoryPath, err := ioutil.TempDir("", "testmemnotification-"+evName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oomPath := filepath.Join(memoryPath, "memory.oom_control")
|
||||
evFile := filepath.Join(memoryPath, evName)
|
||||
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)
|
||||
}
|
||||
if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var eventFd, oomControlFd int
|
||||
paths := map[string]string{
|
||||
"memory": memoryPath,
|
||||
}
|
||||
ooms, err := notifyOnOOM(paths)
|
||||
ch, err := notify(paths)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -63,9 +71,9 @@ func TestNotifyOnOOM(t *testing.T) {
|
|||
}
|
||||
|
||||
select {
|
||||
case <-ooms:
|
||||
case <-ch:
|
||||
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
|
||||
|
@ -79,18 +87,42 @@ func TestNotifyOnOOM(t *testing.T) {
|
|||
|
||||
// give things a moment to shut down
|
||||
select {
|
||||
case _, ok := <-ooms:
|
||||
case _, ok := <-ch:
|
||||
if ok {
|
||||
t.Fatal("expected no oom to be triggered")
|
||||
t.Fatal("expected no notification to be triggered")
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(oomControlFd), syscall.F_GETFD, 0); err != syscall.EBADF {
|
||||
t.Error("expected oom control to be closed")
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(evFd), syscall.F_GETFD, 0); err != syscall.EBADF {
|
||||
t.Error("expected event control to be closed")
|
||||
}
|
||||
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(eventFd), syscall.F_GETFD, 0); err != syscall.EBADF {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue