Merge pull request #426 from gitido/pressure_level
libcontainer: Add support for memcg pressure notifications
This commit is contained in:
commit
6259f09e97
|
@ -104,6 +104,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
|
||||
|
@ -357,6 +363,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