From fa1869381301797295dece0aec12435fc902295b Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 19 Jun 2019 11:09:54 +0200 Subject: pkg: new package cgroups provide a package for managing cgroups. This is not supposed to be a complete implementation with all the features supported by cgroups, but it is a minimal implementation designed around what libpod needs and it is currently using. For example, it is currently possible to Apply only the pids limit, as it is used by libpod for stopping containers, any other Apply will just fail. The main goal here is to have a minimal library where we have full control, so we can start playing with cgroup v2. When the need arises, we can add more features. Signed-off-by: Giuseppe Scrivano --- pkg/cgroups/blkio.go | 100 ++++++++++ pkg/cgroups/cgroups.go | 367 +++++++++++++++++++++++++++++++++++++ pkg/cgroups/cgroups_supported.go | 27 +++ pkg/cgroups/cgroups_unsupported.go | 8 + pkg/cgroups/cpu.go | 97 ++++++++++ pkg/cgroups/cpuset.go | 80 ++++++++ pkg/cgroups/memory.go | 60 ++++++ pkg/cgroups/pids.go | 62 +++++++ pkg/cgroups/systemd.go | 92 ++++++++++ 9 files changed, 893 insertions(+) create mode 100644 pkg/cgroups/blkio.go create mode 100644 pkg/cgroups/cgroups.go create mode 100644 pkg/cgroups/cgroups_supported.go create mode 100644 pkg/cgroups/cgroups_unsupported.go create mode 100644 pkg/cgroups/cpu.go create mode 100644 pkg/cgroups/cpuset.go create mode 100644 pkg/cgroups/memory.go create mode 100644 pkg/cgroups/pids.go create mode 100644 pkg/cgroups/systemd.go (limited to 'pkg/cgroups') diff --git a/pkg/cgroups/blkio.go b/pkg/cgroups/blkio.go new file mode 100644 index 000000000..8434703fd --- /dev/null +++ b/pkg/cgroups/blkio.go @@ -0,0 +1,100 @@ +package cgroups + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +type blkioHandler struct { +} + +func getBlkioHandler() *blkioHandler { + return &blkioHandler{} +} + +// Apply set the specified constraints +func (c *blkioHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { + if res.BlockIO == nil { + return nil + } + return fmt.Errorf("blkio apply function not implemented yet") +} + +// Create the cgroup +func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) { + if ctr.cgroup2 { + return false, fmt.Errorf("function not implemented yet") + } + return ctr.createCgroupDirectory(Blkio) +} + +// Destroy the cgroup +func (c *blkioHandler) Destroy(ctr *CgroupControl) error { + return os.Remove(ctr.getCgroupv1Path(Blkio)) +} + +// Stat fills a metrics structure with usage stats for the controller +func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error { + var ioServiceBytesRecursive []BlkIOEntry + + if ctr.cgroup2 { + return fmt.Errorf("function not implemented yet") + } + + BlkioRoot := ctr.getCgroupv1Path(Blkio) + + p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive") + f, err := os.Open(p) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer f.Close() + + var ioServiceBytesRecursive []BlkIOEntry + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(line) + if len(parts) < 3 { + continue + } + d := strings.Split(parts[0], ":") + if len(d) != 2 { + continue + } + minor, err := strconv.ParseUint(d[0], 10, 0) + if err != nil { + return err + } + major, err := strconv.ParseUint(d[1], 10, 0) + if err != nil { + return err + } + + op := parts[1] + + value, err := strconv.ParseUint(parts[2], 10, 0) + if err != nil { + return err + } + entry := BlkIOEntry{ + Op: op, + Major: major, + Minor: minor, + Value: value, + } + ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry) + } + m.Blkio = BlkioMetrics{IoServiceBytesRecursive: ioServiceBytesRecursive} + return nil +} diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go new file mode 100644 index 000000000..2a88c9db6 --- /dev/null +++ b/pkg/cgroups/cgroups.go @@ -0,0 +1,367 @@ +package cgroups + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + // ErrCgroupDeleted means the cgroup was deleted + ErrCgroupDeleted = errors.New("cgroups: cgroup deleted") +) + +// CgroupControl controls a cgroup hierarchy +type CgroupControl struct { + cgroup2 bool + path string + systemd bool + // List of additional cgroup subsystems joined that + // do not have a custom handler. + additionalControllers []controller +} + +// CPUUsage keeps stats for the CPU usage +type CPUUsage struct { + Kernel uint64 + Total uint64 + PerCPU []uint64 +} + +// MemoryUsage keeps stats for the memory usage +type MemoryUsage struct { + Usage uint64 + Limit uint64 +} + +// CPUMetrics keeps stats for the CPU usage +type CPUMetrics struct { + Usage CPUUsage +} + +// BlkIOEntry describes an entry in the blkio stats +type BlkIOEntry struct { + Op string + Major uint64 + Minor uint64 + Value uint64 +} + +// BlkioMetrics keeps usage stats for the blkio cgroup controller +type BlkioMetrics struct { + IoServiceBytesRecursive []BlkIOEntry +} + +// MemoryMetrics keeps usage stats for the memory cgroup controller +type MemoryMetrics struct { + Usage MemoryUsage +} + +// PidsMetrics keeps usage stats for the pids cgroup controller +type PidsMetrics struct { + Current uint64 +} + +// Metrics keeps usage stats for the cgroup controllers +type Metrics struct { + CPU CPUMetrics + Blkio BlkioMetrics + Memory MemoryMetrics + Pids PidsMetrics +} + +type controller struct { + name string + symlink bool +} + +type controllerHandler interface { + Create(*CgroupControl) (bool, error) + Apply(*CgroupControl, *spec.LinuxResources) error + Destroy(*CgroupControl) error + Stat(*CgroupControl, *Metrics) error +} + +const ( + cgroupRoot = "/sys/fs/cgroup" + _cgroup2SuperMagic = 0x63677270 + // CPU is the cpu controller + CPU = "cpu" + // CPUAcct is the cpuacct controller + CPUAcct = "cpuacct" + // CPUset is the cpuset controller + CPUset = "cpuset" + // Memory is the memory controller + Memory = "memory" + // Pids is the pids controller + Pids = "pids" + // Blkio is the blkio controller + Blkio = "blkio" +) + +var handlers map[string]controllerHandler + +func init() { + handlers = make(map[string]controllerHandler) + handlers[CPU] = getCPUHandler() + handlers[CPUset] = getCpusetHandler() + handlers[Memory] = getMemoryHandler() + handlers[Pids] = getPidsHandler() + handlers[Blkio] = getBlkioHandler() +} + +// getAvailableControllers get the available controllers +func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) { + if cgroup2 { + return nil, fmt.Errorf("function not implemented yet") + } + + infos, err := ioutil.ReadDir(cgroupRoot) + if err != nil { + return nil, err + } + var controllers []controller + for _, i := range infos { + name := i.Name() + if _, found := exclude[name]; found { + continue + } + c := controller{ + name: name, + symlink: !i.IsDir(), + } + controllers = append(controllers, c) + } + return controllers, nil +} + +// getCgroupv1Path is a helper function to get the cgroup v1 path +func (c *CgroupControl) getCgroupv1Path(name string) string { + return filepath.Join(cgroupRoot, name, c.path) +} + +// initialize initializes the specified hierarchy +func (c *CgroupControl) initialize() (err error) { + createdSoFar := map[string]controllerHandler{} + defer func() { + if err != nil { + for name, ctr := range createdSoFar { + if err := ctr.Destroy(c); err != nil { + logrus.Warningf("error cleaning up controller %s for %s", name, c.path) + } + } + } + }() + for name, handler := range handlers { + created, err := handler.Create(c) + if err != nil { + return err + } + if created { + createdSoFar[name] = handler + } + } + + if !c.cgroup2 { + // We won't need to do this for cgroup v2 + for _, ctr := range c.additionalControllers { + if ctr.symlink { + continue + } + path := c.getCgroupv1Path(ctr.name) + if err := os.MkdirAll(path, 0755); err != nil { + return errors.Wrapf(err, "error creating cgroup path %s for %s", path, ctr.name) + } + } + } + + return nil +} + +func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) { + cPath := c.getCgroupv1Path(controller) + _, err := os.Stat(cPath) + if err == nil { + return false, nil + } + + if !os.IsNotExist(err) { + return false, err + } + + if err := os.MkdirAll(cPath, 0755); err != nil { + return false, errors.Wrapf(err, "error creating cgroup for %s", controller) + } + return true, nil +} + +func readFileAsUint64(path string) (uint64, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return 0, err + } + return strconv.ParseUint(cleanString(string(data)), 10, 0) +} + +func (c *CgroupControl) writePidToTasks(pid int, name string) error { + path := filepath.Join(c.getCgroupv1Path(name), "tasks") + payload := []byte(fmt.Sprintf("%d", pid)) + return ioutil.WriteFile(path, payload, 0644) +} + +// New creates a new cgroup control +func New(path string, resources *spec.LinuxResources) (*CgroupControl, error) { + cgroup2, err := IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + control := &CgroupControl{ + cgroup2: cgroup2, + path: path, + } + + if !cgroup2 { + controllers, err := getAvailableControllers(handlers, false) + if err != nil { + return nil, err + } + control.additionalControllers = controllers + } + + if err := control.initialize(); err != nil { + return nil, err + } + + return control, nil +} + +// NewSystemd creates a new cgroup control +func NewSystemd(path string) (*CgroupControl, error) { + cgroup2, err := IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + control := &CgroupControl{ + cgroup2: cgroup2, + path: path, + systemd: true, + } + return control, nil +} + +// Load loads an existing cgroup control +func Load(path string) (*CgroupControl, error) { + cgroup2, err := IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + control := &CgroupControl{ + cgroup2: cgroup2, + path: path, + systemd: false, + } + if !cgroup2 { + for name := range handlers { + p := control.getCgroupv1Path(name) + if _, err := os.Stat(p); err != nil { + if os.IsNotExist(err) { + // compatible with the error code + // used by containerd/cgroups + return nil, ErrCgroupDeleted + } + } + } + } + return control, nil +} + +// CreateSystemdUnit creates the systemd cgroup +func (c *CgroupControl) CreateSystemdUnit(path string) error { + if !c.systemd { + return fmt.Errorf("the cgroup controller is not using systemd") + } + return systemdCreate(path) +} + +// Delete cleans a cgroup +func (c *CgroupControl) Delete() error { + return c.DeleteByPath(c.path) +} + +// DeleteByPath deletes the specified cgroup path +func (c *CgroupControl) DeleteByPath(path string) error { + if c.systemd { + return systemdDestroy(path) + } + var lastError error + for _, h := range handlers { + if err := h.Destroy(c); err != nil { + lastError = err + } + } + + for _, ctr := range c.additionalControllers { + if err := os.Remove(c.getCgroupv1Path(ctr.name)); err != nil { + lastError = err + } + } + return lastError +} + +// Update updates the cgroups +func (c *CgroupControl) Update(resources *spec.LinuxResources) error { + for _, h := range handlers { + if err := h.Apply(c, resources); err != nil { + return err + } + } + return nil +} + +// AddPid moves the specified pid to the cgroup +func (c *CgroupControl) AddPid(pid int) error { + if c.cgroup2 { + return fmt.Errorf("function not implemented yet") + } + pidString := []byte(fmt.Sprintf("%d\n", pid)) + + var names []string + for n := range handlers { + names = append(names, n) + } + + for _, c := range c.additionalControllers { + if !c.symlink { + names = append(names, c.name) + } + } + + for _, n := range names { + p := filepath.Join(c.getCgroupv1Path(n), "tasks") + if err := ioutil.WriteFile(p, pidString, 0644); err != nil { + return err + } + } + return nil +} + +// Stat returns usage statistics for the cgroup +func (c *CgroupControl) Stat() (*Metrics, error) { + if c.cgroup2 { + return nil, fmt.Errorf("function not implemented yet") + } + m := Metrics{} + for _, h := range handlers { + if err := h.Stat(c, &m); err != nil { + return nil, err + } + } + return &m, nil +} diff --git a/pkg/cgroups/cgroups_supported.go b/pkg/cgroups/cgroups_supported.go new file mode 100644 index 000000000..fcd44dfc8 --- /dev/null +++ b/pkg/cgroups/cgroups_supported.go @@ -0,0 +1,27 @@ +// +build linux + +package cgroups + +import ( + "sync" + "syscall" +) + +var ( + isUnifiedOnce sync.Once + isUnified bool + isUnifiedErr error +) + +// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. +func IsCgroup2UnifiedMode() (bool, error) { + isUnifiedOnce.Do(func() { + var st syscall.Statfs_t + if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil { + isUnified, isUnifiedErr = false, err + } else { + isUnified, isUnifiedErr = st.Type == _cgroup2SuperMagic, nil + } + }) + return isUnified, isUnifiedErr +} diff --git a/pkg/cgroups/cgroups_unsupported.go b/pkg/cgroups/cgroups_unsupported.go new file mode 100644 index 000000000..9dc196e42 --- /dev/null +++ b/pkg/cgroups/cgroups_unsupported.go @@ -0,0 +1,8 @@ +// +build !linux + +package cgroups + +// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. +func IsCgroup2UnifiedMode() (bool, error) { + return false, nil +} diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go new file mode 100644 index 000000000..d27f1257f --- /dev/null +++ b/pkg/cgroups/cpu.go @@ -0,0 +1,97 @@ +package cgroups + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +type cpuHandler struct { +} + +func getCPUHandler() *cpuHandler { + return &cpuHandler{} +} + +func cleanString(s string) string { + return strings.Trim(s, "\n") +} + +func readAcct(ctr *CgroupControl, name string) (uint64, error) { + p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name) + return readFileAsUint64(p) +} + +func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) { + var r []uint64 + + p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name) + data, err := ioutil.ReadFile(p) + if err != nil { + return nil, errors.Wrapf(err, "reading %s", p) + } + for _, s := range strings.Split(string(data), " ") { + s = cleanString(s) + if s == "" { + break + } + v, err := strconv.ParseUint(s, 10, 0) + if err != nil { + return nil, errors.Wrapf(err, "parsing %s", s) + } + r = append(r, v) + } + return r, nil +} + +// Apply set the specified constraints +func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { + if res.CPU == nil { + return nil + } + return fmt.Errorf("function not implemented yet") +} + +// Create the cgroup +func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) { + if ctr.cgroup2 { + return false, fmt.Errorf("function not implemented yet") + } + return ctr.createCgroupDirectory(CPU) +} + +// Destroy the cgroup +func (c *cpuHandler) Destroy(ctr *CgroupControl) error { + return os.Remove(ctr.getCgroupv1Path(CPU)) +} + +// Stat fills a metrics structure with usage stats for the controller +func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error { + if ctr.cgroup2 { + return fmt.Errorf("function not implemented yet") + } + + var err error + usage := CPUUsage{} + + usage.Total, err = readAcct(ctr, "cpuacct.usage") + if err != nil { + return err + } + usage.Kernel, err = readAcct(ctr, "cpuacct.usage_sys") + if err != nil { + return err + } + usage.PerCPU, err = readAcctList(ctr, "cpuacct.usage_percpu") + if err != nil { + return err + } + m.CPU = CPUMetrics{Usage: usage} + return nil +} diff --git a/pkg/cgroups/cpuset.go b/pkg/cgroups/cpuset.go new file mode 100644 index 000000000..15c649e46 --- /dev/null +++ b/pkg/cgroups/cpuset.go @@ -0,0 +1,80 @@ +package cgroups + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +type cpusetHandler struct { +} + +func cpusetCopyFileFromParent(dir, file string) ([]byte, error) { + if dir == cgroupRoot { + return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file) + } + path := filepath.Join(dir, file) + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + if len(strings.Trim(string(data), "\n")) != 0 { + return data, nil + } + data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file) + if err != nil { + return nil, err + } + if err := ioutil.WriteFile(path, data, 0644); err != nil { + return nil, err + } + return data, nil +} + +func cpusetCopyFromParent(path string) error { + for _, file := range []string{"cpuset.cpus", "cpuset.mems"} { + if _, err := cpusetCopyFileFromParent(path, file); err != nil { + return err + } + } + return nil +} + +func getCpusetHandler() *cpusetHandler { + return &cpusetHandler{} +} + +// Apply set the specified constraints +func (c *cpusetHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { + if res.CPU == nil { + return nil + } + return fmt.Errorf("function not implemented yet") +} + +// Create the cgroup +func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) { + if ctr.cgroup2 { + return false, fmt.Errorf("function not implemented yet") + } + + created, err := ctr.createCgroupDirectory(CPUset) + if !created || err != nil { + return created, err + } + return true, cpusetCopyFromParent(ctr.getCgroupv1Path(CPUset)) +} + +// Destroy the cgroup +func (c *cpusetHandler) Destroy(ctr *CgroupControl) error { + return os.Remove(ctr.getCgroupv1Path(CPUset)) +} + +// Stat fills a metrics structure with usage stats for the controller +func (c *cpusetHandler) Stat(ctr *CgroupControl, m *Metrics) error { + return nil +} diff --git a/pkg/cgroups/memory.go b/pkg/cgroups/memory.go new file mode 100644 index 000000000..2224e4f1e --- /dev/null +++ b/pkg/cgroups/memory.go @@ -0,0 +1,60 @@ +package cgroups + +import ( + "fmt" + "os" + "path/filepath" + + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +type memHandler struct { +} + +func getMemoryHandler() *memHandler { + return &memHandler{} +} + +// Apply set the specified constraints +func (c *memHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { + if res.Memory == nil { + return nil + } + return fmt.Errorf("function not implemented yet") +} + +// Create the cgroup +func (c *memHandler) Create(ctr *CgroupControl) (bool, error) { + if ctr.cgroup2 { + return false, fmt.Errorf("function not implemented yet") + } + return ctr.createCgroupDirectory(Memory) +} + +// Destroy the cgroup +func (c *memHandler) Destroy(ctr *CgroupControl) error { + return os.Remove(ctr.getCgroupv1Path(Memory)) +} + +// Stat fills a metrics structure with usage stats for the controller +func (c *memHandler) Stat(ctr *CgroupControl, m *Metrics) error { + if ctr.cgroup2 { + return fmt.Errorf("function not implemented yet") + } + usage := MemoryUsage{} + + memoryRoot := ctr.getCgroupv1Path(Memory) + + var err error + usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes")) + if err != nil { + return err + } + usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.limit_in_bytes")) + if err != nil { + return err + } + + m.Memory = MemoryMetrics{Usage: usage} + return nil +} diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go new file mode 100644 index 000000000..f37ac7611 --- /dev/null +++ b/pkg/cgroups/pids.go @@ -0,0 +1,62 @@ +package cgroups + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +type pidHandler struct { +} + +func getPidsHandler() *pidHandler { + return &pidHandler{} +} + +// Apply set the specified constraints +func (c *pidHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { + if res.Pids == nil { + return nil + } + if ctr.cgroup2 { + return fmt.Errorf("function not implemented yet") + } + + p := filepath.Join(ctr.getCgroupv1Path(Pids), "pids.max") + return ioutil.WriteFile(p, []byte(fmt.Sprintf("%d\n", res.Pids.Limit)), 0644) +} + +// Create the cgroup +func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) { + if ctr.cgroup2 { + return false, fmt.Errorf("function not implemented yet") + } + return ctr.createCgroupDirectory(Pids) +} + +// Destroy the cgroup +func (c *pidHandler) Destroy(ctr *CgroupControl) error { + return os.Remove(ctr.getCgroupv1Path(Pids)) +} + +// Stat fills a metrics structure with usage stats for the controller +func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error { + var PIDRoot string + + if ctr.cgroup2 { + return fmt.Errorf("function not implemented yet") + } + + PIDRoot := ctr.getCgroupv1Path(Pids) + + current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current")) + if err != nil { + return err + } + + m.Pids = PidsMetrics{Current: current} + return nil +} diff --git a/pkg/cgroups/systemd.go b/pkg/cgroups/systemd.go new file mode 100644 index 000000000..e72e456bc --- /dev/null +++ b/pkg/cgroups/systemd.go @@ -0,0 +1,92 @@ +package cgroups + +import ( + "fmt" + "path/filepath" + "strings" + + systemdDbus "github.com/coreos/go-systemd/dbus" + "github.com/godbus/dbus" +) + +func systemdCreate(path string) error { + c, err := systemdDbus.New() + if err != nil { + return err + } + defer c.Close() + + slice, name := filepath.Split(path) + slice = strings.TrimSuffix(slice, "/") + + var lastError error + for i := 0; i < 2; i++ { + properties := []systemdDbus.Property{ + systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)), + systemdDbus.PropWants(slice), + } + pMap := map[string]bool{ + "DefaultDependencies": false, + "MemoryAccounting": true, + "CPUAccounting": true, + "BlockIOAccounting": true, + } + if i == 0 { + pMap["Delegate"] = true + } + for k, v := range pMap { + p := systemdDbus.Property{ + Name: k, + Value: dbus.MakeVariant(v), + } + properties = append(properties, p) + } + + ch := make(chan string) + _, err = c.StartTransientUnit(name, "replace", properties, ch) + if err != nil { + lastError = err + continue + } + <-ch + return nil + } + return lastError +} + +/* + systemdDestroy is copied from containerd/cgroups/systemd.go file, that + has the following license: + + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +func systemdDestroy(path string) error { + c, err := systemdDbus.New() + if err != nil { + return err + } + defer c.Close() + + name := filepath.Base(path) + + ch := make(chan string) + _, err = c.StopUnit(name, "replace", ch) + if err != nil { + return err + } + <-ch + return nil +} -- cgit v1.2.3-54-g00ecf From 72cf0c81e86435c1c8daed9bff183b20d6bc400c Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 19 Jun 2019 11:13:18 +0200 Subject: libpod: use pkg/cgroups instead of containerd/cgroups use the new implementation for dealing with cgroups. Signed-off-by: Giuseppe Scrivano --- libpod/container_internal.go | 7 ------- libpod/oci_linux.go | 6 +++--- libpod/runtime_pod_linux.go | 10 ++++------ libpod/stats.go | 7 +++---- libpod/util_linux.go | 31 +++---------------------------- pkg/cgroups/blkio.go | 5 +++-- pkg/cgroups/cgroups.go | 1 - pkg/cgroups/pids.go | 2 +- 8 files changed, 17 insertions(+), 52 deletions(-) (limited to 'pkg/cgroups') diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 2687bcdd1..fcd6a990a 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1461,13 +1461,6 @@ func (c *Container) unmount(force bool) error { return nil } -// getExcludedCGroups returns a string slice of cgroups we want to exclude -// because runc or other components are unaware of them. -func getExcludedCGroups() (excludes []string) { - excludes = []string{"rdma"} - return -} - // this should be from chrootarchive. func (c *Container) copyWithTarFromImage(src, dest string) error { mountpoint, err := c.mount() diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 07bc4e5f3..7d9f47ae2 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -15,8 +15,8 @@ import ( "syscall" "time" - "github.com/containerd/cgroups" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" @@ -49,13 +49,13 @@ func (r *OCIRuntime) moveConmonToCgroup(ctr *Container, cgroupParent string, cmd } } else { cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") - control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(cgroupPath), &spec.LinuxResources{}) + control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) if err != nil { logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } else { // we need to remove this defer and delete the cgroup once conmon exits // maybe need a conmon monitor? - if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil { + if err := control.AddPid(cmd.Process.Pid); err != nil { logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } } diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index d1d303ad5..11dc8cd44 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -9,9 +9,9 @@ import ( "path/filepath" "strings" - "github.com/containerd/cgroups" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/cgroups" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -183,9 +183,8 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) // would prevent removing the CGroups. if p.runtime.config.CgroupManager == CgroupfsCgroupsManager { // Get the conmon CGroup - v1CGroups := GetV1CGroups(getExcludedCGroups()) conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon") - conmonCgroup, err := cgroups.Load(v1CGroups, cgroups.StaticPath(conmonCgroupPath)) + conmonCgroup, err := cgroups.Load(conmonCgroupPath) if err != nil && err != cgroups.ErrCgroupDeleted { if removalErr == nil { removalErr = errors.Wrapf(err, "error retrieving pod %s conmon cgroup %s", p.ID(), conmonCgroupPath) @@ -250,9 +249,8 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) // Make sure the conmon cgroup is deleted first // Since the pod is almost gone, don't bother failing // hard - instead, just log errors. - v1CGroups := GetV1CGroups(getExcludedCGroups()) conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon") - conmonCgroup, err := cgroups.Load(v1CGroups, cgroups.StaticPath(conmonCgroupPath)) + conmonCgroup, err := cgroups.Load(conmonCgroupPath) if err != nil && err != cgroups.ErrCgroupDeleted { if removalErr == nil { removalErr = errors.Wrapf(err, "error retrieving pod %s conmon cgroup", p.ID()) @@ -269,7 +267,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) } } } - cgroup, err := cgroups.Load(v1CGroups, cgroups.StaticPath(p.state.CgroupPath)) + cgroup, err := cgroups.Load(p.state.CgroupPath) if err != nil && err != cgroups.ErrCgroupDeleted { if removalErr == nil { removalErr = errors.Wrapf(err, "error retrieving pod %s cgroup", p.ID()) diff --git a/libpod/stats.go b/libpod/stats.go index 926a1a511..e003f145b 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -7,8 +7,8 @@ import ( "syscall" "time" - "github.com/containerd/cgroups" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" "github.com/pkg/errors" ) @@ -34,14 +34,13 @@ func (c *Container) GetContainerStats(previousStats *ContainerStats) (*Container if err != nil { return nil, err } - v1CGroups := GetV1CGroups(getExcludedCGroups()) - cgroup, err := cgroups.Load(v1CGroups, cgroups.StaticPath(cgroupPath)) + cgroup, err := cgroups.Load(cgroupPath) if err != nil { return stats, errors.Wrapf(err, "unable to load cgroup at %s", cgroupPath) } // Ubuntu does not have swap memory in cgroups because swap is often not enabled. - cgroupStats, err := cgroup.Stat(cgroups.IgnoreNotExist) + cgroupStats, err := cgroup.Stat() if err != nil { return stats, errors.Wrapf(err, "unable to obtain cgroup stats") } diff --git a/libpod/util_linux.go b/libpod/util_linux.go index 8752eba20..77dcf86f6 100644 --- a/libpod/util_linux.go +++ b/libpod/util_linux.go @@ -6,10 +6,8 @@ import ( "fmt" "strings" - "github.com/containerd/cgroups" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/util" - spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/containers/libpod/pkg/cgroups" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -42,7 +40,7 @@ func makeSystemdCgroup(path string) error { return err } - return controller.Create(path, &spec.LinuxResources{}) + return controller.CreateSystemdUnit(path) } // deleteSystemdCgroup deletes the systemd cgroup at the given location @@ -52,7 +50,7 @@ func deleteSystemdCgroup(path string) error { return err } - return controller.Delete(path) + return controller.DeleteByPath(path) } // assembleSystemdCgroupName creates a systemd cgroup path given a base and @@ -71,29 +69,6 @@ func assembleSystemdCgroupName(baseSlice, newSlice string) (string, error) { return final, nil } -// GetV1CGroups gets the V1 cgroup subsystems and then "filters" -// out any subsystems that are provided by the caller. Passing nil -// for excludes will return the subsystems unfiltered. -//func GetV1CGroups(excludes []string) ([]cgroups.Subsystem, error) { -func GetV1CGroups(excludes []string) cgroups.Hierarchy { - return func() ([]cgroups.Subsystem, error) { - var filtered []cgroups.Subsystem - - subSystem, err := cgroups.V1() - if err != nil { - return nil, err - } - for _, s := range subSystem { - // If the name of the subsystem is not in the list of excludes, then - // add it as a keeper. - if !util.StringInSlice(string(s.Name()), excludes) { - filtered = append(filtered, s) - } - } - return filtered, nil - } -} - // LabelVolumePath takes a mount path for a volume and gives it an // selinux label of either shared or not func LabelVolumePath(path string, shared bool) error { diff --git a/pkg/cgroups/blkio.go b/pkg/cgroups/blkio.go index 8434703fd..8eb54abec 100644 --- a/pkg/cgroups/blkio.go +++ b/pkg/cgroups/blkio.go @@ -59,8 +59,6 @@ func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error { } defer f.Close() - var ioServiceBytesRecursive []BlkIOEntry - scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() @@ -95,6 +93,9 @@ func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error { } ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry) } + if err := scanner.Err(); err != nil { + return err + } m.Blkio = BlkioMetrics{IoServiceBytesRecursive: ioServiceBytesRecursive} return nil } diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 2a88c9db6..24dce71bd 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" "strconv" - "strings" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go index f37ac7611..342b25d85 100644 --- a/pkg/cgroups/pids.go +++ b/pkg/cgroups/pids.go @@ -50,7 +50,7 @@ func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error { return fmt.Errorf("function not implemented yet") } - PIDRoot := ctr.getCgroupv1Path(Pids) + PIDRoot = ctr.getCgroupv1Path(Pids) current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current")) if err != nil { -- cgit v1.2.3-54-g00ecf From 1778bfa5fef337158537a77344300e1a755a7ffe Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 19 Jun 2019 13:07:23 +0200 Subject: pkg, cgroups: add initial support for cgroup v2 This is an initial implementation of cgroup v2 support for pkg/cgroups. It currently works with crun, with this patch: https://github.com/giuseppe/crun/pull/49). It adds the pieces for: - set PID limit to 1 - retrieve stats so that "podman stats" work. the only missing part is the support for reading per CPU stats (that is cpuacct.usage_percpu on cgroup v1), so for now it always returns an empty result. Signed-off-by: Giuseppe Scrivano --- pkg/cgroups/blkio.go | 134 +++++++++++++++++++++++++++++++++---------------- pkg/cgroups/cgroups.go | 64 ++++++++++++++++++----- pkg/cgroups/cpu.go | 51 ++++++++++++------- pkg/cgroups/cpuset.go | 9 ++-- pkg/cgroups/memory.go | 25 +++++---- pkg/cgroups/pids.go | 16 +++--- 6 files changed, 207 insertions(+), 92 deletions(-) (limited to 'pkg/cgroups') diff --git a/pkg/cgroups/blkio.go b/pkg/cgroups/blkio.go index 8eb54abec..ca9107d97 100644 --- a/pkg/cgroups/blkio.go +++ b/pkg/cgroups/blkio.go @@ -9,6 +9,7 @@ import ( "strings" spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) type blkioHandler struct { @@ -29,7 +30,7 @@ func (c *blkioHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error // Create the cgroup func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) { if ctr.cgroup2 { - return false, fmt.Errorf("function not implemented yet") + return false, fmt.Errorf("io create not implemented for cgroup v2") } return ctr.createCgroupDirectory(Blkio) } @@ -44,57 +45,104 @@ func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error { var ioServiceBytesRecursive []BlkIOEntry if ctr.cgroup2 { - return fmt.Errorf("function not implemented yet") - } - - BlkioRoot := ctr.getCgroupv1Path(Blkio) - - p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive") - f, err := os.Open(p) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - parts := strings.Fields(line) - if len(parts) < 3 { - continue - } - d := strings.Split(parts[0], ":") - if len(d) != 2 { - continue - } - minor, err := strconv.ParseUint(d[0], 10, 0) + // more details on the io.stat file format:X https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html + values, err := readCgroup2MapFile(ctr, "io.stat") if err != nil { return err } - major, err := strconv.ParseUint(d[1], 10, 0) + for k, v := range values { + d := strings.Split(k, ":") + if len(d) != 2 { + continue + } + minor, err := strconv.ParseUint(d[0], 10, 0) + if err != nil { + return err + } + major, err := strconv.ParseUint(d[1], 10, 0) + if err != nil { + return err + } + + for _, item := range v { + d := strings.Split(item, "=") + if len(d) != 2 { + continue + } + op := d[0] + + // Accommodate the cgroup v1 naming + switch op { + case "rbytes": + op = "read" + case "wbytes": + op = "write" + } + + value, err := strconv.ParseUint(d[1], 10, 0) + if err != nil { + return err + } + + entry := BlkIOEntry{ + Op: op, + Major: major, + Minor: minor, + Value: value, + } + ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry) + } + } + } else { + BlkioRoot := ctr.getCgroupv1Path(Blkio) + + p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive") + f, err := os.Open(p) if err != nil { - return err + if os.IsNotExist(err) { + return nil + } + return errors.Wrapf(err, "open %s", p) } + defer f.Close() - op := parts[1] + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(line) + if len(parts) < 3 { + continue + } + d := strings.Split(parts[0], ":") + if len(d) != 2 { + continue + } + minor, err := strconv.ParseUint(d[0], 10, 0) + if err != nil { + return err + } + major, err := strconv.ParseUint(d[1], 10, 0) + if err != nil { + return err + } - value, err := strconv.ParseUint(parts[2], 10, 0) - if err != nil { - return err + op := parts[1] + + value, err := strconv.ParseUint(parts[2], 10, 0) + if err != nil { + return err + } + entry := BlkIOEntry{ + Op: op, + Major: major, + Minor: minor, + Value: value, + } + ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry) } - entry := BlkIOEntry{ - Op: op, - Major: major, - Minor: minor, - Value: value, + if err := scanner.Err(); err != nil { + return errors.Wrapf(err, "parse %s", p) } - ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry) - } - if err := scanner.Err(); err != nil { - return err } m.Blkio = BlkioMetrics{IoServiceBytesRecursive: ioServiceBytesRecursive} return nil diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 24dce71bd..426bda559 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -1,11 +1,14 @@ package cgroups import ( + "bufio" "fmt" "io/ioutil" + "math" "os" "path/filepath" "strconv" + "strings" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -119,12 +122,12 @@ func init() { // getAvailableControllers get the available controllers func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) { if cgroup2 { - return nil, fmt.Errorf("function not implemented yet") + return nil, fmt.Errorf("getAvailableControllers not implemented yet for cgroup v2") } infos, err := ioutil.ReadDir(cgroupRoot) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "read directory %s", cgroupRoot) } var controllers []controller for _, i := range infos { @@ -204,9 +207,17 @@ func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) { func readFileAsUint64(path string) (uint64, error) { data, err := ioutil.ReadFile(path) if err != nil { - return 0, err + return 0, errors.Wrapf(err, "open %s", path) + } + v := cleanString(string(data)) + if v == "max" { + return math.MaxUint64, nil + } + ret, err := strconv.ParseUint(v, 10, 0) + if err != nil { + return ret, errors.Wrapf(err, "parse %s from %s", v, path) } - return strconv.ParseUint(cleanString(string(data)), 10, 0) + return ret, nil } func (c *CgroupControl) writePidToTasks(pid int, name string) error { @@ -307,8 +318,9 @@ func (c *CgroupControl) DeleteByPath(path string) error { } for _, ctr := range c.additionalControllers { - if err := os.Remove(c.getCgroupv1Path(ctr.name)); err != nil { - lastError = err + p := c.getCgroupv1Path(ctr.name) + if err := os.Remove(p); err != nil { + lastError = errors.Wrapf(err, "remove %s", p) } } return lastError @@ -326,10 +338,15 @@ func (c *CgroupControl) Update(resources *spec.LinuxResources) error { // AddPid moves the specified pid to the cgroup func (c *CgroupControl) AddPid(pid int) error { + pidString := []byte(fmt.Sprintf("%d\n", pid)) + if c.cgroup2 { - return fmt.Errorf("function not implemented yet") + p := filepath.Join(cgroupRoot, c.path, "tasks") + if err := ioutil.WriteFile(p, pidString, 0644); err != nil { + return errors.Wrapf(err, "write %s", p) + } + return nil } - pidString := []byte(fmt.Sprintf("%d\n", pid)) var names []string for n := range handlers { @@ -345,7 +362,7 @@ func (c *CgroupControl) AddPid(pid int) error { for _, n := range names { p := filepath.Join(c.getCgroupv1Path(n), "tasks") if err := ioutil.WriteFile(p, pidString, 0644); err != nil { - return err + return errors.Wrapf(err, "write %s", p) } } return nil @@ -353,9 +370,6 @@ func (c *CgroupControl) AddPid(pid int) error { // Stat returns usage statistics for the cgroup func (c *CgroupControl) Stat() (*Metrics, error) { - if c.cgroup2 { - return nil, fmt.Errorf("function not implemented yet") - } m := Metrics{} for _, h := range handlers { if err := h.Stat(c, &m); err != nil { @@ -364,3 +378,29 @@ func (c *CgroupControl) Stat() (*Metrics, error) { } return &m, nil } + +func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) { + ret := map[string][]string{} + p := filepath.Join(cgroupRoot, ctr.path, name) + f, err := os.Open(p) + if err != nil { + if os.IsNotExist(err) { + return ret, nil + } + return nil, errors.Wrapf(err, "open file %s", p) + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(line) + if len(parts) < 2 { + continue + } + ret[parts[0]] = parts[1:] + } + if err := scanner.Err(); err != nil { + return nil, errors.Wrapf(err, "parsing file %s", p) + } + return ret, nil +} diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go index d27f1257f..3f969fd3c 100644 --- a/pkg/cgroups/cpu.go +++ b/pkg/cgroups/cpu.go @@ -55,13 +55,13 @@ func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { if res.CPU == nil { return nil } - return fmt.Errorf("function not implemented yet") + return fmt.Errorf("cpu apply not implemented yet") } // Create the cgroup func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) { if ctr.cgroup2 { - return false, fmt.Errorf("function not implemented yet") + return false, fmt.Errorf("cpu create not implemented for cgroup v2") } return ctr.createCgroupDirectory(CPU) } @@ -73,24 +73,39 @@ func (c *cpuHandler) Destroy(ctr *CgroupControl) error { // Stat fills a metrics structure with usage stats for the controller func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error { - if ctr.cgroup2 { - return fmt.Errorf("function not implemented yet") - } - var err error usage := CPUUsage{} - - usage.Total, err = readAcct(ctr, "cpuacct.usage") - if err != nil { - return err - } - usage.Kernel, err = readAcct(ctr, "cpuacct.usage_sys") - if err != nil { - return err - } - usage.PerCPU, err = readAcctList(ctr, "cpuacct.usage_percpu") - if err != nil { - return err + if ctr.cgroup2 { + values, err := readCgroup2MapFile(ctr, "cpu.stat") + if err != nil { + return err + } + if val, found := values["usage_usec"]; found { + usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 0) + if err != nil { + return err + } + } + if val, found := values["system_usec"]; found { + usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 0) + if err != nil { + return err + } + } + // FIXME: How to read usage.PerCPU? + } else { + usage.Total, err = readAcct(ctr, "cpuacct.usage") + if err != nil { + return err + } + usage.Kernel, err = readAcct(ctr, "cpuacct.usage_sys") + if err != nil { + return err + } + usage.PerCPU, err = readAcctList(ctr, "cpuacct.usage_percpu") + if err != nil { + return err + } } m.CPU = CPUMetrics{Usage: usage} return nil diff --git a/pkg/cgroups/cpuset.go b/pkg/cgroups/cpuset.go index 15c649e46..9aef493c9 100644 --- a/pkg/cgroups/cpuset.go +++ b/pkg/cgroups/cpuset.go @@ -8,6 +8,7 @@ import ( "strings" spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) type cpusetHandler struct { @@ -20,7 +21,7 @@ func cpusetCopyFileFromParent(dir, file string) ([]byte, error) { path := filepath.Join(dir, file) data, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "open %s", path) } if len(strings.Trim(string(data), "\n")) != 0 { return data, nil @@ -30,7 +31,7 @@ func cpusetCopyFileFromParent(dir, file string) ([]byte, error) { return nil, err } if err := ioutil.WriteFile(path, data, 0644); err != nil { - return nil, err + return nil, errors.Wrapf(err, "write %s", path) } return data, nil } @@ -53,13 +54,13 @@ func (c *cpusetHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) erro if res.CPU == nil { return nil } - return fmt.Errorf("function not implemented yet") + return fmt.Errorf("cpuset apply not implemented yet") } // Create the cgroup func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) { if ctr.cgroup2 { - return false, fmt.Errorf("function not implemented yet") + return false, fmt.Errorf("cpuset create not implemented for cgroup v2") } created, err := ctr.createCgroupDirectory(CPUset) diff --git a/pkg/cgroups/memory.go b/pkg/cgroups/memory.go index 2224e4f1e..0505eac40 100644 --- a/pkg/cgroups/memory.go +++ b/pkg/cgroups/memory.go @@ -20,13 +20,13 @@ func (c *memHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { if res.Memory == nil { return nil } - return fmt.Errorf("function not implemented yet") + return fmt.Errorf("memory apply not implemented yet") } // Create the cgroup func (c *memHandler) Create(ctr *CgroupControl) (bool, error) { if ctr.cgroup2 { - return false, fmt.Errorf("function not implemented yet") + return false, fmt.Errorf("memory create not implemented for cgroup v2") } return ctr.createCgroupDirectory(Memory) } @@ -38,19 +38,26 @@ func (c *memHandler) Destroy(ctr *CgroupControl) error { // Stat fills a metrics structure with usage stats for the controller func (c *memHandler) Stat(ctr *CgroupControl, m *Metrics) error { - if ctr.cgroup2 { - return fmt.Errorf("function not implemented yet") - } + var err error usage := MemoryUsage{} - memoryRoot := ctr.getCgroupv1Path(Memory) + var memoryRoot string + filenames := map[string]string{} - var err error - usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes")) + if ctr.cgroup2 { + memoryRoot = filepath.Join(cgroupRoot, ctr.path) + filenames["usage"] = "memory.current" + filenames["limit"] = "memory.max" + } else { + memoryRoot = ctr.getCgroupv1Path(Memory) + filenames["usage"] = "memory.usage_in_bytes" + filenames["limit"] = "memory.limit_in_bytes" + } + usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, filenames["usage"])) if err != nil { return err } - usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.limit_in_bytes")) + usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, filenames["limit"])) if err != nil { return err } diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go index 342b25d85..c90dc1c02 100644 --- a/pkg/cgroups/pids.go +++ b/pkg/cgroups/pids.go @@ -21,18 +21,22 @@ func (c *pidHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error { if res.Pids == nil { return nil } + var PIDRoot string + if ctr.cgroup2 { - return fmt.Errorf("function not implemented yet") + PIDRoot = filepath.Join(cgroupRoot, ctr.path) + } else { + PIDRoot = ctr.getCgroupv1Path(Pids) } - p := filepath.Join(ctr.getCgroupv1Path(Pids), "pids.max") + p := filepath.Join(PIDRoot, "pids.max") return ioutil.WriteFile(p, []byte(fmt.Sprintf("%d\n", res.Pids.Limit)), 0644) } // Create the cgroup func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) { if ctr.cgroup2 { - return false, fmt.Errorf("function not implemented yet") + return false, fmt.Errorf("pid create not implemented for cgroup v2") } return ctr.createCgroupDirectory(Pids) } @@ -47,11 +51,11 @@ func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error { var PIDRoot string if ctr.cgroup2 { - return fmt.Errorf("function not implemented yet") + PIDRoot = filepath.Join(cgroupRoot, ctr.path) + } else { + PIDRoot = ctr.getCgroupv1Path(Pids) } - PIDRoot = ctr.getCgroupv1Path(Pids) - current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current")) if err != nil { return err -- cgit v1.2.3-54-g00ecf