diff --git a/v2/manager.go b/v2/manager.go index a7137190..a9510867 100644 --- a/v2/manager.go +++ b/v2/manager.go @@ -25,8 +25,11 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" + "golang.org/x/sys/unix" + "github.com/containerd/cgroups/v2/stats" "github.com/pkg/errors" ) @@ -40,6 +43,14 @@ type cgValuer interface { Values() []Value } +type Event struct { + Low uint64 + High uint64 + Max uint64 + OOM uint64 + OOMKill uint64 +} + // Resources for a cgroups v2 unified hierarchy type Resources struct { CPU *CPU @@ -411,3 +422,54 @@ func (c *Manager) freeze(path string, state State) error { time.Sleep(1 * time.Millisecond) } } + +func (c *Manager) MemoryEventFD() (uintptr, error) { + fpath := filepath.Join(c.path, "memory.events") + fd, err := syscall.InotifyInit() + if err != nil { + return 0, errors.Errorf("Failed to create inotify fd") + } + defer syscall.Close(fd) + wd, err := syscall.InotifyAddWatch(fd, fpath, unix.IN_MODIFY) + if wd < 0 { + return 0, errors.Errorf("Failed to add inotify watch for %q", fpath) + } + defer syscall.InotifyRmWatch(fd, uint32(wd)) + + return uintptr(fd), nil +} + +func (c *Manager) EventChan() (<-chan Event, <-chan error) { + ec := make(chan Event) + errCh := make(chan error) + go c.waitForEvents(ec, errCh) + + return ec, nil +} + +func (c *Manager) waitForEvents(ec chan<- Event, errCh chan<- error) { + fd, err := c.MemoryEventFD() + if err != nil { + errCh <- errors.Errorf("Failed to create memory event fd") + } + for { + buffer := make([]byte, syscall.SizeofInotifyEvent*10) + bytesRead, err := syscall.Read(int(fd), buffer) + if err != nil { + errCh <- err + } + var out map[string]interface{} + if bytesRead >= syscall.SizeofInotifyEvent { + if err := readStatsFile(c.path, "memory.events", out); err != nil { + e := Event{ + High: out["high"].(uint64), + Low: out["low"].(uint64), + Max: out["max"].(uint64), + OOM: out["oom"].(uint64), + OOMKill: out["oom_kill"].(uint64), + } + ec <- e + } + } + } +}