[chore]: Bump github.com/KimMachineGun/automemlimit from 0.2.4 to 0.2.5 (#1666)

Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.2.4 to 0.2.5.
- [Release notes](https://github.com/KimMachineGun/automemlimit/releases)
- [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.2.4...v0.2.5)

---
updated-dependencies:
- dependency-name: github.com/KimMachineGun/automemlimit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot] 2023-04-03 11:16:17 +02:00 committed by GitHub
commit 57dc742c76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
200 changed files with 16392 additions and 38190 deletions

View file

@ -1,46 +0,0 @@
version = "unstable"
generator = "gogoctrd"
plugins = ["grpc"]
# Control protoc include paths. Below are usually some good defaults, but feel
# free to try it without them if it works for your project.
[includes]
# Include paths that will be added before all others. Typically, you want to
# treat the root of the project as an include, but this may not be necessary.
# before = ["."]
# Paths that should be treated as include roots in relation to the vendor
# directory. These will be calculated with the vendor directory nearest the
# target package.
# vendored = ["github.com/gogo/protobuf"]
packages = ["github.com/gogo/protobuf"]
# Paths that will be added untouched to the end of the includes. We use
# `/usr/local/include` to pickup the common install location of protobuf.
# This is the default.
after = ["/usr/local/include", "/usr/include"]
# This section maps protobuf imports to Go packages. These will become
# `-M` directives in the call to the go protobuf generator.
[packages]
"gogoproto/gogo.proto" = "github.com/gogo/protobuf/gogoproto"
"google/protobuf/any.proto" = "github.com/gogo/protobuf/types"
"google/protobuf/descriptor.proto" = "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
"google/protobuf/field_mask.proto" = "github.com/gogo/protobuf/types"
"google/protobuf/timestamp.proto" = "github.com/gogo/protobuf/types"
# Aggregrate the API descriptors to lock down API changes.
[[descriptors]]
prefix = "github.com/containerd/cgroups/stats/v1"
target = "stats/v1/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
[[descriptors]]
prefix = "github.com/containerd/cgroups/v2/stats"
target = "v2/stats/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]

View file

@ -1,46 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
# Fedora box is used for testing cgroup v2 support
config.vm.box = "fedora/35-cloud-base"
config.vm.provider :virtualbox do |v|
v.memory = 4096
v.cpus = 2
end
config.vm.provider :libvirt do |v|
v.memory = 4096
v.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
set -eux -o pipefail
# configuration
GO_VERSION="1.17.7"
# install gcc and Golang
dnf -y install gcc
curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" | tar Cxz /usr/local
# setup env vars
cat >> /etc/profile.d/sh.local <<EOF
PATH=/usr/local/go/bin:$PATH
GO111MODULE=on
export PATH GO111MODULE
EOF
source /etc/profile.d/sh.local
# enter /root/go/src/github.com/containerd/cgroups
mkdir -p /root/go/src/github.com/containerd
ln -s /vagrant /root/go/src/github.com/containerd/cgroups
cd /root/go/src/github.com/containerd/cgroups
# create /test.sh
cat > /test.sh <<EOF
#!/bin/bash
set -eux -o pipefail
cd /root/go/src/github.com/containerd/cgroups
go test -v ./...
EOF
chmod +x /test.sh
SHELL
end

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -22,3 +22,5 @@ cgutil:
proto:
protobuild --quiet ${PACKAGES}
# Keep them Go-idiomatic and backward-compatible with the gogo/protobuf era.
go-fix-acronym -w -a '(Cpu|Tcp|Rss)' $(shell find cgroup1/stats/ cgroup2/stats/ -name '*.pb.go')

View file

@ -0,0 +1,31 @@
version = "2"
generators = ["go"]
# Control protoc include paths. Below are usually some good defaults, but feel
# free to try it without them if it works for your project.
[includes]
# Include paths that will be added before all others. Typically, you want to
# treat the root of the project as an include, but this may not be necessary.
# before = ["."]
# Paths that will be added untouched to the end of the includes. We use
# `/usr/local/include` to pickup the common install location of protobuf.
# This is the default.
after = ["/usr/local/include", "/usr/include"]
# Aggregrate the API descriptors to lock down API changes.
[[descriptors]]
prefix = "github.com/containerd/cgroups/cgroup1/stats"
target = "cgroup1/stats/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
]
[[descriptors]]
prefix = "github.com/containerd/cgroups/cgroup2/stats"
target = "cgroup2/stats/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
]
[parameters.go]
paths = "source_relative"

View file

@ -9,7 +9,7 @@ Go package for creating, managing, inspecting, and destroying cgroups.
The resources format for settings on the cgroup uses the OCI runtime-spec found
[here](https://github.com/opencontainers/runtime-spec).
## Examples
## Examples (v1)
### Create a new cgroup
@ -25,7 +25,7 @@ uses the v1 implementation of cgroups.
```go
shares := uint64(100)
control, err := cgroups.New(cgroups.V1, cgroups.StaticPath("/test"), &specs.LinuxResources{
control, err := cgroup1.New(cgroup1.StaticPath("/test"), &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Shares: &shares,
},
@ -37,7 +37,7 @@ defer control.Delete()
```go
control, err := cgroups.New(cgroups.Systemd, cgroups.Slice("system.slice", "runc-test"), &specs.LinuxResources{
control, err := cgroup1.New(cgroup1.Systemd, cgroup1.Slice("system.slice", "runc-test"), &specs.LinuxResources{
CPU: &specs.CPU{
Shares: &shares,
},
@ -48,17 +48,17 @@ control, err := cgroups.New(cgroups.Systemd, cgroups.Slice("system.slice", "runc
### Load an existing cgroup
```go
control, err = cgroups.Load(cgroups.V1, cgroups.StaticPath("/test"))
control, err = cgroup1.Load(cgroup1.Default, cgroups.StaticPath("/test"))
```
### Add a process to the cgroup
```go
if err := control.Add(cgroups.Process{Pid:1234}); err != nil {
if err := control.Add(cgroup1.Process{Pid:1234}); err != nil {
}
```
### Update the cgroup
### Update the cgroup
To update the resources applied in the cgroup
@ -84,7 +84,7 @@ if err := control.Thaw(); err != nil {
### List all processes in the cgroup or recursively
```go
processes, err := control.Processes(cgroups.Devices, recursive)
processes, err := control.Processes(cgroup1.Devices, recursive)
```
### Get Stats on the cgroup
@ -95,7 +95,7 @@ stats, err := control.Stat()
By adding `cgroups.IgnoreNotExist` all non-existent files will be ignored, e.g. swap memory stats without swap enabled
```go
stats, err := control.Stat(cgroups.IgnoreNotExist)
stats, err := control.Stat(cgroup1.IgnoreNotExist)
```
### Move process across cgroups
@ -117,22 +117,90 @@ subCgroup, err := control.New("child", resources)
This allows you to get notified by an eventfd for v1 memory cgroups events.
```go
event := cgroups.MemoryThresholdEvent(50 * 1024 * 1024, false)
event := cgroup1.MemoryThresholdEvent(50 * 1024 * 1024, false)
efd, err := control.RegisterMemoryEvent(event)
```
```go
event := cgroups.MemoryPressureEvent(cgroups.MediumPressure, cgroups.DefaultMode)
event := cgroup1.MemoryPressureEvent(cgroup1.MediumPressure, cgroup1.DefaultMode)
efd, err := control.RegisterMemoryEvent(event)
```
```go
efd, err := control.OOMEventFD()
// or by using RegisterMemoryEvent
event := cgroups.OOMEvent()
event := cgroup1.OOMEvent()
efd, err := control.RegisterMemoryEvent(event)
```
## Examples (v2/unified)
### Check that the current system is running cgroups v2
```go
var cgroupV2 bool
if cgroups.Mode() == cgroups.Unified {
cgroupV2 = true
}
```
### Create a new cgroup
This creates a new systemd v2 cgroup slice. Systemd slices consider ["-" a special character](https://www.freedesktop.org/software/systemd/man/systemd.slice.html),
so the resulting slice would be located here on disk:
* /sys/fs/cgroup/my.slice/my-cgroup.slice/my-cgroup-abc.slice
```go
import (
"github.com/containerd/cgroups/v3/cgroup2"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
res := cgroup2.Resources{}
// dummy PID of -1 is used for creating a "general slice" to be used as a parent cgroup.
// see https://github.com/containerd/cgroups/blob/1df78138f1e1e6ee593db155c6b369466f577651/v2/manager.go#L732-L735
m, err := cgroup2.NewSystemd("/", "my-cgroup-abc.slice", -1, &res)
if err != nil {
return err
}
```
### Load an existing cgroup
```go
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
```
### Delete a cgroup
```go
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
err = m.DeleteSystemd()
if err != nil {
return err
}
```
### Kill all processes in a cgroup
```go
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
err = m.Kill()
if err != nil {
return err
}
```
### Attention
All static path should not include `/sys/fs/cgroup/` prefix, it should start with your own cgroups name

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@ -25,7 +25,8 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -71,7 +72,7 @@ func (b *blkioController) Create(path string, resources *specs.LinuxResources) e
}
for _, t := range createBlkioSettings(resources.BlockIO) {
if t.value != nil {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(b.Path(path), "blkio."+t.name),
t.format(t.value),
defaultFilePerm,

View file

@ -14,31 +14,34 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
"github.com/opencontainers/runtime-spec/specs-go"
)
// New returns a new control via the cgroup cgroups interface
func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
func New(path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
config := newInitConfig()
for _, o := range opts {
if err := o(config); err != nil {
return nil, err
}
}
subsystems, err := hierarchy()
subsystems, err := config.hiearchy()
if err != nil {
return nil, err
}
@ -68,7 +71,7 @@ func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources, opts .
// Load will load an existing cgroup and allow it to be controlled
// All static path should not include `/sys/fs/cgroup/` prefix, it should start with your own cgroups name
func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
func Load(path Path, opts ...InitOpts) (Cgroup, error) {
config := newInitConfig()
for _, o := range opts {
if err := o(config); err != nil {
@ -76,7 +79,7 @@ func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
}
}
var activeSubsystems []Subsystem
subsystems, err := hierarchy()
subsystems, err := config.hiearchy()
if err != nil {
return nil, err
}
@ -84,7 +87,7 @@ func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
for _, s := range pathers(subsystems) {
p, err := path(s.Name())
if err != nil {
if errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) {
return nil, ErrCgroupDeleted
}
if err == ErrControllerNotActive {
@ -193,6 +196,31 @@ func (c *cgroup) AddTask(process Process, subsystems ...Name) error {
return c.add(process, cgroupTasks, subsystems...)
}
// writeCgroupsProcs writes to the file, but retries on EINVAL.
func writeCgroupProcs(path string, content []byte, perm fs.FileMode) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, perm)
if err != nil {
return err
}
defer f.Close()
for i := 0; i < 5; i++ {
_, err = f.Write(content)
if err == nil {
return nil
}
// If the process's associated task's state is TASK_NEW, the kernel
// returns EINVAL. The function will retry on the error like runc.
// https://github.com/torvalds/linux/blob/v6.0/kernel/sched/core.c#L10308-L10337
// https://github.com/opencontainers/runc/pull/1950
if !errors.Is(err, syscall.EINVAL) {
return err
}
time.Sleep(30 * time.Millisecond)
}
return err
}
func (c *cgroup) add(process Process, pType procType, subsystems ...Name) error {
if process.Pid <= 0 {
return ErrInvalidPid
@ -207,7 +235,7 @@ func (c *cgroup) add(process Process, pType procType, subsystems ...Name) error
if err != nil {
return err
}
err = retryingWriteFile(
err = writeCgroupProcs(
filepath.Join(s.Path(p), pType),
[]byte(strconv.Itoa(process.Pid)),
defaultFilePerm,
@ -228,6 +256,15 @@ func (c *cgroup) Delete() error {
}
var errs []string
for _, s := range c.subsystems {
// kernel prevents cgroups with running process from being removed, check the tree is empty
procs, err := c.processes(s.Name(), true, cgroupProcs)
if err != nil {
return err
}
if len(procs) > 0 {
errs = append(errs, fmt.Sprintf("%s (contains running processes)", string(s.Name())))
continue
}
if d, ok := s.(deleter); ok {
sp, err := c.path(s.Name())
if err != nil {
@ -247,6 +284,7 @@ func (c *cgroup) Delete() error {
if err := remove(path); err != nil {
errs = append(errs, path)
}
continue
}
}
if len(errs) > 0 {

View file

@ -14,12 +14,12 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@ -22,7 +22,7 @@ import (
"path/filepath"
"strconv"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -82,7 +82,7 @@ func (c *cpuController) Create(path string, resources *specs.LinuxResources) err
value = []byte(strconv.FormatInt(*t.ivalue, 10))
}
if value != nil {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(c.Path(path), "cpu."+t.name),
value,
defaultFilePerm,

View file

@ -14,16 +14,17 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
)
const nanosecondsInSecond = 1000000000
@ -70,7 +71,7 @@ func (c *cpuacctController) Stat(path string, stats *v1.Metrics) error {
func (c *cpuacctController) percpuUsage(path string) ([]uint64, error) {
var usage []uint64
data, err := ioutil.ReadFile(filepath.Join(c.Path(path), "cpuacct.usage_percpu"))
data, err := os.ReadFile(filepath.Join(c.Path(path), "cpuacct.usage_percpu"))
if err != nil {
return nil, err
}
@ -86,36 +87,41 @@ func (c *cpuacctController) percpuUsage(path string) ([]uint64, error) {
func (c *cpuacctController) getUsage(path string) (user uint64, kernel uint64, err error) {
statPath := filepath.Join(c.Path(path), "cpuacct.stat")
data, err := ioutil.ReadFile(statPath)
f, err := os.Open(statPath)
if err != nil {
return 0, 0, err
}
fields := strings.Fields(string(data))
if len(fields) != 4 {
return 0, 0, fmt.Errorf("%q is expected to have 4 fields", statPath)
defer f.Close()
var (
raw = make(map[string]uint64)
sc = bufio.NewScanner(f)
)
for sc.Scan() {
key, v, err := parseKV(sc.Text())
if err != nil {
return 0, 0, err
}
raw[key] = v
}
if err := sc.Err(); err != nil {
return 0, 0, err
}
for _, t := range []struct {
index int
name string
value *uint64
}{
{
index: 0,
name: "user",
value: &user,
},
{
index: 2,
name: "system",
value: &kernel,
},
} {
if fields[t.index] != t.name {
return 0, 0, fmt.Errorf("expected field %q but found %q in %q", t.name, fields[t.index], statPath)
}
v, err := strconv.ParseUint(fields[t.index+1], 10, 64)
if err != nil {
return 0, 0, err
v, ok := raw[t.name]
if !ok {
return 0, 0, fmt.Errorf("expected field %q but not found in %q", t.name, statPath)
}
*t.value = v
}

View file

@ -14,12 +14,11 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@ -69,7 +68,7 @@ func (c *cpusetController) Create(path string, resources *specs.LinuxResources)
},
} {
if t.value != "" {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(c.Path(path), "cpuset."+t.name),
[]byte(t.value),
defaultFilePerm,
@ -87,10 +86,10 @@ func (c *cpusetController) Update(path string, resources *specs.LinuxResources)
}
func (c *cpusetController) getValues(path string) (cpus []byte, mems []byte, err error) {
if cpus, err = ioutil.ReadFile(filepath.Join(path, "cpuset.cpus")); err != nil && !os.IsNotExist(err) {
if cpus, err = os.ReadFile(filepath.Join(path, "cpuset.cpus")); err != nil && !os.IsNotExist(err) {
return
}
if mems, err = ioutil.ReadFile(filepath.Join(path, "cpuset.mems")); err != nil && !os.IsNotExist(err) {
if mems, err = os.ReadFile(filepath.Join(path, "cpuset.mems")); err != nil && !os.IsNotExist(err) {
return
}
return cpus, mems, nil
@ -134,7 +133,7 @@ func (c *cpusetController) copyIfNeeded(current, parent string) error {
return err
}
if isEmpty(currentCpus) {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(current, "cpuset.cpus"),
parentCpus,
defaultFilePerm,
@ -143,7 +142,7 @@ func (c *cpusetController) copyIfNeeded(current, parent string) error {
}
}
if isEmpty(currentMems) {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(current, "cpuset.mems"),
parentMems,
defaultFilePerm,

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"fmt"
@ -60,7 +60,7 @@ func (d *devicesController) Create(path string, resources *specs.LinuxResources)
if device.Type == "" {
device.Type = "a"
}
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(d.Path(path), file),
[]byte(deviceString(device)),
defaultFilePerm,

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"

View file

@ -14,10 +14,10 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
@ -50,7 +50,7 @@ func (f *freezerController) Thaw(path string) error {
}
func (f *freezerController) changeState(path string, state State) error {
return retryingWriteFile(
return os.WriteFile(
filepath.Join(f.root, path, "freezer.state"),
[]byte(strings.ToUpper(string(state))),
defaultFilePerm,
@ -58,7 +58,7 @@ func (f *freezerController) changeState(path string, state State) error {
}
func (f *freezerController) state(path string) (State, error) {
current, err := ioutil.ReadFile(filepath.Join(f.root, path, "freezer.state"))
current, err := os.ReadFile(filepath.Join(f.root, path, "freezer.state"))
if err != nil {
return "", err
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
// Hierarchy enables both unified and split hierarchy for cgroups
type Hierarchy func() ([]Subsystem, error)

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
@ -22,7 +22,7 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -56,7 +56,7 @@ func (h *hugetlbController) Create(path string, resources *specs.LinuxResources)
return err
}
for _, limit := range resources.HugepageLimits {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(h.Path(path), strings.Join([]string{"hugetlb", limit.Pagesize, "limit_in_bytes"}, ".")),
[]byte(strconv.FormatUint(limit.Limit, 10)),
defaultFilePerm,

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@ -25,7 +25,7 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
@ -43,7 +43,7 @@ type memoryThresholdEvent struct {
swap bool
}
// MemoryThresholdEvent returns a new memory threshold event to be used with RegisterMemoryEvent.
// MemoryThresholdEvent returns a new [MemoryEvent] representing the memory threshold set.
// If swap is true, the event will be registered using memory.memsw.usage_in_bytes
func MemoryThresholdEvent(threshold uint64, swap bool) MemoryEvent {
return &memoryThresholdEvent{
@ -83,7 +83,7 @@ type memoryPressureEvent struct {
hierarchy EventNotificationMode
}
// MemoryPressureEvent returns a new memory pressure event to be used with RegisterMemoryEvent.
// MemoryPressureEvent returns a new [MemoryEvent] representing the memory pressure set.
func MemoryPressureEvent(pressureLevel MemoryPressureLevel, hierarchy EventNotificationMode) MemoryEvent {
return &memoryPressureEvent{
pressureLevel,
@ -104,22 +104,22 @@ func (m *memoryPressureEvent) EventFile() string {
type MemoryPressureLevel string
// The three memory pressure levels are as follows.
// - The "low" level means that the system is reclaiming memory for new
// allocations. Monitoring this reclaiming activity might be useful for
// maintaining cache level. Upon notification, the program (typically
// "Activity Manager") might analyze vmstat and act in advance (i.e.
// prematurely shutdown unimportant services).
// - The "medium" level means that the system is experiencing medium memory
// pressure, the system might be making swap, paging out active file caches,
// etc. Upon this event applications may decide to further analyze
// vmstat/zoneinfo/memcg or internal memory usage statistics and free any
// resources that can be easily reconstructed or re-read from a disk.
// - The "critical" level means that the system is actively thrashing, it is
// about to out of memory (OOM) or even the in-kernel OOM killer is on its
// way to trigger. Applications should do whatever they can to help the
// system. It might be too late to consult with vmstat or any other
// statistics, so it is advisable to take an immediate action.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
// - The "low" level means that the system is reclaiming memory for new
// allocations. Monitoring this reclaiming activity might be useful for
// maintaining cache level. Upon notification, the program (typically
// "Activity Manager") might analyze vmstat and act in advance (i.e.
// prematurely shutdown unimportant services).
// - The "medium" level means that the system is experiencing medium memory
// pressure, the system might be making swap, paging out active file caches,
// etc. Upon this event applications may decide to further analyze
// vmstat/zoneinfo/memcg or internal memory usage statistics and free any
// resources that can be easily reconstructed or re-read from a disk.
// - The "critical" level means that the system is actively thrashing, it is
// about to out of memory (OOM) or even the in-kernel OOM killer is on its
// way to trigger. Applications should do whatever they can to help the
// system. It might be too late to consult with vmstat or any other
// statistics, so it is advisable to take an immediate action.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
const (
LowPressure MemoryPressureLevel = "low"
MediumPressure MemoryPressureLevel = "medium"
@ -131,21 +131,21 @@ const (
type EventNotificationMode string
// There are three optional modes that specify different propagation behavior:
// - "default": this is the default behavior specified above. This mode is the
// same as omitting the optional mode parameter, preserved by backwards
// compatibility.
// - "hierarchy": events always propagate up to the root, similar to the default
// behavior, except that propagation continues regardless of whether there are
// event listeners at each level, with the "hierarchy" mode. In the above
// example, groups A, B, and C will receive notification of memory pressure.
// - "local": events are pass-through, i.e. they only receive notifications when
// memory pressure is experienced in the memcg for which the notification is
// registered. In the above example, group C will receive notification if
// registered for "local" notification and the group experiences memory
// pressure. However, group B will never receive notification, regardless if
// there is an event listener for group C or not, if group B is registered for
// local notification.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
// - "default": this is the default behavior specified above. This mode is the
// same as omitting the optional mode parameter, preserved by backwards
// compatibility.
// - "hierarchy": events always propagate up to the root, similar to the default
// behavior, except that propagation continues regardless of whether there are
// event listeners at each level, with the "hierarchy" mode. In the above
// example, groups A, B, and C will receive notification of memory pressure.
// - "local": events are pass-through, i.e. they only receive notifications when
// memory pressure is experienced in the memcg for which the notification is
// registered. In the above example, group C will receive notification if
// registered for "local" notification and the group experiences memory
// pressure. However, group B will never receive notification, regardless if
// there is an event listener for group C or not, if group B is registered for
// local notification.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
const (
DefaultMode EventNotificationMode = "default"
LocalMode EventNotificationMode = "local"
@ -394,7 +394,7 @@ func (m *memoryController) parseOomControlStats(r io.Reader, stat *v1.MemoryOomC
func (m *memoryController) set(path string, settings []memorySettings) error {
for _, t := range settings {
if t.value != nil {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(m.Path(path), "memory."+t.name),
[]byte(strconv.FormatInt(*t.value, 10)),
defaultFilePerm,
@ -472,7 +472,7 @@ func (m *memoryController) memoryEvent(path string, event MemoryEvent) (uintptr,
defer evtFile.Close()
data := fmt.Sprintf("%d %d %s", efd, evtFile.Fd(), event.Arg())
evctlPath := filepath.Join(root, "cgroup.event_control")
if err := retryingWriteFile(evctlPath, []byte(data), 0700); err != nil {
if err := os.WriteFile(evctlPath, []byte(data), 0700); err != nil {
unix.Close(efd)
return 0, err
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import "path/filepath"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
@ -47,7 +47,7 @@ func (n *netclsController) Create(path string, resources *specs.LinuxResources)
return err
}
if resources.Network != nil && resources.Network.ClassID != nil && *resources.Network.ClassID > 0 {
return retryingWriteFile(
return os.WriteFile(
filepath.Join(n.Path(path), "net_cls.classid"),
[]byte(strconv.FormatUint(uint64(*resources.Network.ClassID), 10)),
defaultFilePerm,

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"fmt"
@ -48,7 +48,7 @@ func (n *netprioController) Create(path string, resources *specs.LinuxResources)
}
if resources.Network != nil {
for _, prio := range resources.Network.Priorities {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(n.Path(path), "net_prio.ifpriomap"),
formatPrio(prio.Name, prio.Priority),
defaultFilePerm,

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"
@ -36,11 +36,13 @@ type InitOpts func(*InitConfig) error
type InitConfig struct {
// InitCheck can be used to check initialization errors from the subsystem
InitCheck InitCheck
hiearchy Hierarchy
}
func newInitConfig() *InitConfig {
return &InitConfig{
InitCheck: RequireDevices,
hiearchy: Default,
}
}
@ -59,3 +61,12 @@ func RequireDevices(s Subsystem, _ Path, _ error) error {
}
return ErrIgnoreSubsystem
}
// WithHiearchy sets a list of cgroup subsystems.
// The default list is coming from /proc/self/mountinfo.
func WithHiearchy(h Hierarchy) InitOpts {
return func(c *InitConfig) error {
c.hiearchy = h
return nil
}
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import "path/filepath"

View file

@ -14,16 +14,15 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -50,7 +49,7 @@ func (p *pidsController) Create(path string, resources *specs.LinuxResources) er
return err
}
if resources.Pids != nil && resources.Pids.Limit > 0 {
return retryingWriteFile(
return os.WriteFile(
filepath.Join(p.Path(path), "pids.max"),
[]byte(strconv.FormatInt(resources.Pids.Limit, 10)),
defaultFilePerm,
@ -69,7 +68,7 @@ func (p *pidsController) Stat(path string, stats *v1.Metrics) error {
return err
}
var max uint64
maxData, err := ioutil.ReadFile(filepath.Join(p.Path(path), "pids.max"))
maxData, err := os.ReadFile(filepath.Join(p.Path(path), "pids.max"))
if err != nil {
return err
}

View file

@ -14,17 +14,16 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"io/ioutil"
"math"
"os"
"path/filepath"
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -68,7 +67,7 @@ func (p *rdmaController) Create(path string, resources *specs.LinuxResources) er
for device, limit := range resources.Rdma {
if device != "" && (limit.HcaHandles != nil || limit.HcaObjects != nil) {
limit := limit
return retryingWriteFile(
return os.WriteFile(
filepath.Join(p.Path(path), "rdma.max"),
[]byte(createCmdString(device, &limit)),
defaultFilePerm,
@ -126,13 +125,13 @@ func toRdmaEntry(strEntries []string) []*v1.RdmaEntry {
func (p *rdmaController) Stat(path string, stats *v1.Metrics) error {
currentData, err := ioutil.ReadFile(filepath.Join(p.Path(path), "rdma.current"))
currentData, err := os.ReadFile(filepath.Join(p.Path(path), "rdma.current"))
if err != nil {
return err
}
currentPerDevices := strings.Split(string(currentData), "\n")
maxData, err := ioutil.ReadFile(filepath.Join(p.Path(path), "rdma.max"))
maxData, err := os.ReadFile(filepath.Join(p.Path(path), "rdma.max"))
if err != nil {
return err
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
// State is a type that represents the state of the current cgroup
type State string

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
file {
name: "github.com/containerd/cgroups/stats/v1/metrics.proto"
name: "github.com/containerd/cgroups/cgroup1/stats/metrics.proto"
package: "io.containerd.cgroups.v1"
dependency: "gogoproto/gogo.proto"
message_type {
name: "Metrics"
field {
@ -26,9 +25,6 @@ file {
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.CPUStat"
options {
65004: "CPU"
}
json_name: "cpu"
}
field {
@ -175,9 +171,6 @@ file {
number: 4
label: LABEL_REPEATED
type: TYPE_UINT64
options {
65004: "PerCPU"
}
json_name: "perCpu"
}
}
@ -219,9 +212,6 @@ file {
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
options {
65004: "RSS"
}
json_name: "rss"
}
field {
@ -229,9 +219,6 @@ file {
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
options {
65004: "RSSHuge"
}
json_name: "rssHuge"
}
field {
@ -344,9 +331,6 @@ file {
number: 19
label: LABEL_OPTIONAL
type: TYPE_UINT64
options {
65004: "TotalRSS"
}
json_name: "totalRss"
}
field {
@ -354,9 +338,6 @@ file {
number: 20
label: LABEL_OPTIONAL
type: TYPE_UINT64
options {
65004: "TotalRSSHuge"
}
json_name: "totalRssHuge"
}
field {
@ -473,9 +454,6 @@ file {
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryEntry"
options {
65004: "KernelTCP"
}
json_name: "kernelTcp"
}
}
@ -786,5 +764,8 @@ file {
json_name: "nrIoWait"
}
}
options {
go_package: "github.com/containerd/cgroups/cgroup1/stats"
}
syntax: "proto3"
}

View file

@ -2,12 +2,12 @@ syntax = "proto3";
package io.containerd.cgroups.v1;
import "gogoproto/gogo.proto";
option go_package = "github.com/containerd/cgroups/cgroup1/stats";
message Metrics {
repeated HugetlbStat hugetlb = 1;
PidsStat pids = 2;
CPUStat cpu = 3 [(gogoproto.customname) = "CPU"];
CPUStat cpu = 3;
MemoryStat memory = 4;
BlkIOStat blkio = 5;
RdmaStat rdma = 6;
@ -38,7 +38,7 @@ message CPUUsage {
uint64 total = 1;
uint64 kernel = 2;
uint64 user = 3;
repeated uint64 per_cpu = 4 [(gogoproto.customname) = "PerCPU"];
repeated uint64 per_cpu = 4;
}
@ -50,8 +50,8 @@ message Throttle {
message MemoryStat {
uint64 cache = 1;
uint64 rss = 2 [(gogoproto.customname) = "RSS"];
uint64 rss_huge = 3 [(gogoproto.customname) = "RSSHuge"];
uint64 rss = 2;
uint64 rss_huge = 3;
uint64 mapped_file = 4;
uint64 dirty = 5;
uint64 writeback = 6;
@ -67,8 +67,8 @@ message MemoryStat {
uint64 hierarchical_memory_limit = 16;
uint64 hierarchical_swap_limit = 17;
uint64 total_cache = 18;
uint64 total_rss = 19 [(gogoproto.customname) = "TotalRSS"];
uint64 total_rss_huge = 20 [(gogoproto.customname) = "TotalRSSHuge"];
uint64 total_rss = 19;
uint64 total_rss_huge = 20;
uint64 total_mapped_file = 21;
uint64 total_dirty = 22;
uint64 total_writeback = 23;
@ -84,7 +84,7 @@ message MemoryStat {
MemoryEntry usage = 33;
MemoryEntry swap = 34;
MemoryEntry kernel = 35;
MemoryEntry kernel_tcp = 36 [(gogoproto.customname) = "KernelTCP"];
MemoryEntry kernel_tcp = 36;
}

View file

@ -14,13 +14,14 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"fmt"
"os"
v1 "github.com/containerd/cgroups/stats/v1"
"github.com/containerd/cgroups/v3"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -59,7 +60,7 @@ func Subsystems() []Name {
Blkio,
Rdma,
}
if !RunningInUserNS() {
if !cgroups.RunningInUserNS() {
n = append(n, Devices)
}
if _, err := os.Stat("/sys/kernel/mm/hugepages"); err == nil {

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"context"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
func getClockTicks() uint64 {
// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and

View file

@ -14,107 +14,22 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/containerd/cgroups/v3"
units "github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
var (
nsOnce sync.Once
inUserNS bool
checkMode sync.Once
cgMode CGMode
)
const unifiedMountpoint = "/sys/fs/cgroup"
// CGMode is the cgroups mode of the host system
type CGMode int
const (
// Unavailable cgroup mountpoint
Unavailable CGMode = iota
// Legacy cgroups v1
Legacy
// Hybrid with cgroups v1 and v2 controllers mounted
Hybrid
// Unified with only cgroups v2 mounted
Unified
)
// Mode returns the cgroups mode running on the host
func Mode() CGMode {
checkMode.Do(func() {
var st unix.Statfs_t
if err := unix.Statfs(unifiedMountpoint, &st); err != nil {
cgMode = Unavailable
return
}
switch st.Type {
case unix.CGROUP2_SUPER_MAGIC:
cgMode = Unified
default:
cgMode = Legacy
if err := unix.Statfs(filepath.Join(unifiedMountpoint, "unified"), &st); err != nil {
return
}
if st.Type == unix.CGROUP2_SUPER_MAGIC {
cgMode = Hybrid
}
}
})
return cgMode
}
// RunningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}
// defaults returns all known groups
func defaults(root string) ([]Subsystem, error) {
h, err := NewHugetlb(root)
@ -137,7 +52,7 @@ func defaults(root string) ([]Subsystem, error) {
}
// only add the devices cgroup if we are not in a user namespace
// because modifications are not allowed
if !RunningInUserNS() {
if !cgroups.RunningInUserNS() {
s = append(s, NewDevices(root))
}
// add the hugetlb cgroup if error wasn't due to missing hugetlb
@ -200,7 +115,7 @@ func hugePageSizes() ([]string, error) {
pageSizes []string
sizeList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
)
files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
files, err := os.ReadDir("/sys/kernel/mm/hugepages")
if err != nil {
return nil, err
}
@ -216,7 +131,7 @@ func hugePageSizes() ([]string, error) {
}
func readUint(path string) (uint64, error) {
v, err := ioutil.ReadFile(path)
v, err := os.ReadFile(path)
if err != nil {
return 0, err
}
@ -257,12 +172,14 @@ func parseKV(raw string) (string, uint64, error) {
// ParseCgroupFile parses the given cgroup file, typically /proc/self/cgroup
// or /proc/<pid>/cgroup, into a map of subsystems to cgroup paths, e.g.
// "cpu": "/user.slice/user-1000.slice"
// "pids": "/user.slice/user-1000.slice"
//
// "cpu": "/user.slice/user-1000.slice"
// "pids": "/user.slice/user-1000.slice"
//
// etc.
//
// The resulting map does not have an element for cgroup v2 unified hierarchy.
// Use ParseCgroupFileUnified to get the unified path.
// Use [cgroups.ParseCgroupFileUnified] to get the unified path.
func ParseCgroupFile(path string) (map[string]string, error) {
x, _, err := ParseCgroupFileUnified(path)
return x, err
@ -270,41 +187,10 @@ func ParseCgroupFile(path string) (map[string]string, error) {
// ParseCgroupFileUnified returns legacy subsystem paths as the first value,
// and returns the unified path as the second value.
//
// Deprecated: use [cgroups.ParseCgroupFileUnified] instead .
func ParseCgroupFileUnified(path string) (map[string]string, string, error) {
f, err := os.Open(path)
if err != nil {
return nil, "", err
}
defer f.Close()
return parseCgroupFromReaderUnified(f)
}
func parseCgroupFromReaderUnified(r io.Reader) (map[string]string, string, error) {
var (
cgroups = make(map[string]string)
unified = ""
s = bufio.NewScanner(r)
)
for s.Scan() {
var (
text = s.Text()
parts = strings.SplitN(text, ":", 3)
)
if len(parts) < 3 {
return nil, unified, fmt.Errorf("invalid cgroup entry: %q", text)
}
for _, subs := range strings.Split(parts[1], ",") {
if subs == "" {
unified = parts[2]
} else {
cgroups[subs] = parts[2]
}
}
}
if err := s.Err(); err != nil {
return nil, unified, err
}
return cgroups, unified, nil
return cgroups.ParseCgroupFileUnified(path)
}
func getCgroupDestination(subsystem string) (string, error) {
@ -377,16 +263,3 @@ func cleanPath(path string) string {
}
return path
}
func retryingWriteFile(path string, data []byte, mode os.FileMode) error {
// Retry writes on EINTR; see:
// https://github.com/golang/go/issues/38033
for {
err := ioutil.WriteFile(path, data, mode)
if err == nil {
return nil
} else if !errors.Is(err, syscall.EINTR) {
return err
}
}
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@ -24,8 +24,8 @@ import (
"strings"
)
// V1 returns all the groups in the default cgroups mountpoint in a single hierarchy
func V1() ([]Subsystem, error) {
// Default returns all the groups in the default cgroups mountpoint in a single hierarchy
func Default() ([]Subsystem, error) {
root, err := v1MountPoint()
if err != nil {
return nil, err

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"math"

View file

@ -24,7 +24,7 @@
// This particular Go implementation based on runc version
// https://github.com/opencontainers/runc/blob/master/libcontainer/cgroups/ebpf/devicefilter/devicefilter.go
package v2
package cgroup2
import (
"errors"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"fmt"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"errors"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import "strings"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import "fmt"

View file

@ -14,14 +14,14 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"bufio"
"context"
"errors"
"fmt"
"io/ioutil"
"io"
"math"
"os"
"path/filepath"
@ -30,7 +30,7 @@ import (
"syscall"
"time"
"github.com/containerd/cgroups/v2/stats"
"github.com/containerd/cgroups/v3/cgroup2/stats"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
@ -42,6 +42,7 @@ import (
const (
subtreeControl = "cgroup.subtree_control"
controllersFile = "cgroup.controllers"
killFile = "cgroup.kill"
defaultCgroup2Path = "/sys/fs/cgroup"
defaultSlice = "system.slice"
)
@ -144,20 +145,11 @@ func (c *Value) write(path string, perm os.FileMode) error {
return ErrInvalidFormat
}
// Retry writes on EINTR; see:
// https://github.com/golang/go/issues/38033
for {
err := ioutil.WriteFile(
filepath.Join(path, c.filename),
data,
perm,
)
if err == nil {
return nil
} else if !errors.Is(err, syscall.EINTR) {
return err
}
}
return os.WriteFile(
filepath.Join(path, c.filename),
data,
perm,
)
}
func writeValues(path string, values []Value) error {
@ -196,13 +188,35 @@ func NewManager(mountpoint string, group string, resources *Resources) (*Manager
return &m, nil
}
func LoadManager(mountpoint string, group string) (*Manager, error) {
type InitConfig struct {
mountpoint string
}
type InitOpts func(c *InitConfig) error
// WithMountpoint sets the unified mountpoint. The default path is /sys/fs/cgroup.
func WithMountpoint(path string) InitOpts {
return func(c *InitConfig) error {
c.mountpoint = path
return nil
}
}
// Load a cgroup.
func Load(group string, opts ...InitOpts) (*Manager, error) {
c := InitConfig{mountpoint: defaultCgroup2Path}
for _, opt := range opts {
if err := opt(&c); err != nil {
return nil, err
}
}
if err := VerifyGroupPath(group); err != nil {
return nil, err
}
path := filepath.Join(mountpoint, group)
path := filepath.Join(c.mountpoint, group)
return &Manager{
unifiedMountpoint: mountpoint,
unifiedMountpoint: c.mountpoint,
path: path,
}, nil
}
@ -225,7 +239,7 @@ func setResources(path string, resources *Resources) error {
}
func (c *Manager) RootControllers() ([]string, error) {
b, err := ioutil.ReadFile(filepath.Join(c.unifiedMountpoint, controllersFile))
b, err := os.ReadFile(filepath.Join(c.unifiedMountpoint, controllersFile))
if err != nil {
return nil, err
}
@ -233,7 +247,7 @@ func (c *Manager) RootControllers() ([]string, error) {
}
func (c *Manager) Controllers() ([]string, error) {
b, err := ioutil.ReadFile(filepath.Join(c.path, controllersFile))
b, err := os.ReadFile(filepath.Join(c.path, controllersFile))
if err != nil {
return nil, err
}
@ -336,7 +350,103 @@ func (c *Manager) AddProc(pid uint64) error {
return writeValues(c.path, []Value{v})
}
func (c *Manager) AddThread(tid uint64) error {
v := Value{
filename: cgroupThreads,
value: tid,
}
return writeValues(c.path, []Value{v})
}
// Kill will try to forcibly exit all of the processes in the cgroup. This is
// equivalent to sending a SIGKILL to every process. On kernels 5.14 and greater
// this will use the cgroup.kill file, on anything that doesn't have the cgroup.kill
// file, a manual process of freezing -> sending a SIGKILL to every process -> thawing
// will be used.
func (c *Manager) Kill() error {
v := Value{
filename: killFile,
value: "1",
}
err := writeValues(c.path, []Value{v})
if err == nil {
return nil
}
logrus.Warnf("falling back to slower kill implementation: %s", err)
// Fallback to slow method.
return c.fallbackKill()
}
// fallbackKill is a slower fallback to the more modern (kernels 5.14+)
// approach of writing to the cgroup.kill file. This is heavily pulled
// from runc's same approach (in signalAllProcesses), with the only differences
// being this is just tailored to the API exposed in this library, and we don't
// need to care about signals other than SIGKILL.
//
// https://github.com/opencontainers/runc/blob/8da0a0b5675764feaaaaad466f6567a9983fcd08/libcontainer/init_linux.go#L523-L529
func (c *Manager) fallbackKill() error {
if err := c.Freeze(); err != nil {
logrus.Warn(err)
}
pids, err := c.Procs(true)
if err != nil {
if err := c.Thaw(); err != nil {
logrus.Warn(err)
}
return err
}
var procs []*os.Process
for _, pid := range pids {
p, err := os.FindProcess(int(pid))
if err != nil {
logrus.Warn(err)
continue
}
procs = append(procs, p)
if err := p.Signal(unix.SIGKILL); err != nil {
logrus.Warn(err)
}
}
if err := c.Thaw(); err != nil {
logrus.Warn(err)
}
subreaper, err := getSubreaper()
if err != nil {
// The error here means that PR_GET_CHILD_SUBREAPER is not
// supported because this code might run on a kernel older
// than 3.4. We don't want to throw an error in that case,
// and we simplify things, considering there is no subreaper
// set.
subreaper = 0
}
for _, p := range procs {
// In case a subreaper has been setup, this code must not
// wait for the process. Otherwise, we cannot be sure the
// current process will be reaped by the subreaper, while
// the subreaper might be waiting for this process in order
// to retrieve its exit code.
if subreaper == 0 {
if _, err := p.Wait(); err != nil {
if !errors.Is(err, unix.ECHILD) {
logrus.Warnf("wait on pid %d failed: %s", p.Pid, err)
}
}
}
}
return nil
}
func (c *Manager) Delete() error {
// kernel prevents cgroups with running process from being removed, check the tree is empty
processes, err := c.Procs(true)
if err != nil {
return err
}
if len(processes) > 0 {
return fmt.Errorf("cgroups: unable to remove path %q: still contains running processes", c.path)
}
return remove(c.path)
}
@ -366,6 +476,22 @@ func (c *Manager) Procs(recursive bool) ([]uint64, error) {
return processes, err
}
func (c *Manager) MoveTo(destination *Manager) error {
processes, err := c.Procs(true)
if err != nil {
return err
}
for _, p := range processes {
if err := destination.AddProc(p); err != nil {
if strings.Contains(err.Error(), "no such process") {
continue
}
return err
}
}
return nil
}
var singleValueFiles = []string{
"pids.current",
"pids.max",
@ -506,7 +632,7 @@ func readSingleFile(path string, file string, out map[string]interface{}) error
return err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
data, err := io.ReadAll(f)
if err != nil {
return err
}
@ -709,7 +835,8 @@ func setDevices(path string, devices []specs.LinuxDeviceCgroup) error {
// the reason this is necessary is because the "-" character has a special meaning in
// systemd slice. For example, when creating a slice called "my-group-112233.slice",
// systemd will create a hierarchy like this:
// /sys/fs/cgroup/my.slice/my-group.slice/my-group-112233.slice
//
// /sys/fs/cgroup/my.slice/my-group.slice/my-group-112233.slice
func getSystemdFullPath(slice, group string) string {
return filepath.Join(defaultCgroup2Path, dashesToPath(slice), dashesToPath(group))
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
type Memory struct {
Swap *int64

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"fmt"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import "strconv"

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"fmt"

View file

@ -14,10 +14,10 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
)
@ -50,7 +50,7 @@ func (s State) Values() []Value {
}
func fetchState(path string) (State, error) {
current, err := ioutil.ReadFile(filepath.Join(path, cgroupFreeze))
current, err := os.ReadFile(filepath.Join(path, cgroupFreeze))
if err != nil {
return Unknown, err
}

View file

@ -14,4 +14,4 @@
limitations under the License.
*/
package v1
package stats

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
file {
name: "github.com/containerd/cgroups/v2/stats/metrics.proto"
name: "github.com/containerd/cgroups/cgroup2/stats/metrics.proto"
package: "io.containerd.cgroups.v2"
dependency: "gogoproto/gogo.proto"
message_type {
name: "Metrics"
field {
@ -18,9 +17,6 @@ file {
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v2.CPUStat"
options {
65004: "CPU"
}
json_name: "cpu"
}
field {
@ -535,5 +531,8 @@ file {
json_name: "pagesize"
}
}
options {
go_package: "github.com/containerd/cgroups/cgroup2/stats"
}
syntax: "proto3"
}

View file

@ -2,11 +2,11 @@ syntax = "proto3";
package io.containerd.cgroups.v2;
import "gogoproto/gogo.proto";
option go_package = "github.com/containerd/cgroups/cgroup2/stats";
message Metrics {
PidsStat pids = 1;
CPUStat cpu = 2 [(gogoproto.customname) = "CPU"];
CPUStat cpu = 2;
MemoryStat memory = 4;
RdmaStat rdma = 5;
IOStat io = 6;

View file

@ -14,29 +14,31 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unsafe"
"github.com/containerd/cgroups/v2/stats"
"github.com/containerd/cgroups/v3/cgroup2/stats"
"github.com/godbus/dbus/v5"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const (
cgroupProcs = "cgroup.procs"
cgroupThreads = "cgroup.threads"
defaultDirPerm = 0755
)
@ -242,7 +244,7 @@ func ToResources(spec *specs.LinuxResources) *Resources {
// Gets uint64 parsed content of single value cgroup stat file
func getStatFileContentUint64(filePath string) uint64 {
contents, err := ioutil.ReadFile(filePath)
contents, err := os.ReadFile(filePath)
if err != nil {
return 0
}
@ -264,7 +266,7 @@ func readIoStats(path string) []*stats.IOEntry {
// more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
var usage []*stats.IOEntry
fpath := filepath.Join(path, "io.stat")
currentData, err := ioutil.ReadFile(fpath)
currentData, err := os.ReadFile(fpath)
if err != nil {
return usage
}
@ -318,7 +320,7 @@ func readIoStats(path string) []*stats.IOEntry {
}
func rdmaStats(filepath string) []*stats.RdmaEntry {
currentData, err := ioutil.ReadFile(filepath)
currentData, err := os.ReadFile(filepath)
if err != nil {
return []*stats.RdmaEntry{}
}
@ -406,7 +408,7 @@ func readHugeTlbStats(path string) []*stats.HugeTlbStat {
hugeTlb = &stats.HugeTlbStat{}
}
hugeTlb.Pagesize = pageSize
out, err := ioutil.ReadFile(filepath.Join(path, file.Name()))
out, err := os.ReadFile(filepath.Join(path, file.Name()))
if err != nil {
continue
}
@ -434,3 +436,11 @@ func readHugeTlbStats(path string) []*stats.HugeTlbStat {
}
return usage
}
func getSubreaper() (int, error) {
var i uintptr
if err := unix.Prctl(unix.PR_GET_CHILD_SUBREAPER, uintptr(unsafe.Pointer(&i)), 0, 0, 0); err != nil {
return -1, err
}
return int(i), nil
}

150
vendor/github.com/containerd/cgroups/v3/utils.go generated vendored Normal file
View file

@ -0,0 +1,150 @@
/*
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.
*/
package cgroups
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"golang.org/x/sys/unix"
)
var (
nsOnce sync.Once
inUserNS bool
checkMode sync.Once
cgMode CGMode
)
const unifiedMountpoint = "/sys/fs/cgroup"
// CGMode is the cgroups mode of the host system
type CGMode int
const (
// Unavailable cgroup mountpoint
Unavailable CGMode = iota
// Legacy cgroups v1
Legacy
// Hybrid with cgroups v1 and v2 controllers mounted
Hybrid
// Unified with only cgroups v2 mounted
Unified
)
// Mode returns the cgroups mode running on the host
func Mode() CGMode {
checkMode.Do(func() {
var st unix.Statfs_t
if err := unix.Statfs(unifiedMountpoint, &st); err != nil {
cgMode = Unavailable
return
}
switch st.Type {
case unix.CGROUP2_SUPER_MAGIC:
cgMode = Unified
default:
cgMode = Legacy
if err := unix.Statfs(filepath.Join(unifiedMountpoint, "unified"), &st); err != nil {
return
}
if st.Type == unix.CGROUP2_SUPER_MAGIC {
cgMode = Hybrid
}
}
})
return cgMode
}
// RunningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}
// ParseCgroupFileUnified returns legacy subsystem paths as the first value,
// and returns the unified path as the second value.
func ParseCgroupFileUnified(path string) (map[string]string, string, error) {
f, err := os.Open(path)
if err != nil {
return nil, "", err
}
defer f.Close()
return ParseCgroupFromReaderUnified(f)
}
// ParseCgroupFromReaderUnified returns legacy subsystem paths as the first value,
// and returns the unified path as the second value.
func ParseCgroupFromReaderUnified(r io.Reader) (map[string]string, string, error) {
var (
cgroups = make(map[string]string)
unified = ""
s = bufio.NewScanner(r)
)
for s.Scan() {
var (
text = s.Text()
parts = strings.SplitN(text, ":", 3)
)
if len(parts) < 3 {
return nil, unified, fmt.Errorf("invalid cgroup entry: %q", text)
}
for _, subs := range strings.Split(parts[1], ",") {
if subs == "" {
unified = parts[2]
} else {
cgroups[subs] = parts[2]
}
}
}
if err := s.Err(); err != nil {
return nil, unified, err
}
return cgroups, unified, nil
}