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:
|
// 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 != "" {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue