diff options
Diffstat (limited to 'runsc/cgroup')
-rw-r--r-- | runsc/cgroup/cgroup.go | 63 | ||||
-rw-r--r-- | runsc/cgroup/cgroup_test.go | 127 |
2 files changed, 166 insertions, 24 deletions
diff --git a/runsc/cgroup/cgroup.go b/runsc/cgroup/cgroup.go index 13c6a16a0..797c1c2bc 100644 --- a/runsc/cgroup/cgroup.go +++ b/runsc/cgroup/cgroup.go @@ -203,6 +203,19 @@ func LoadPaths(pid string) (map[string]string, error) { } func loadPathsHelper(cgroup io.Reader) (map[string]string, error) { + // For nested containers, in /proc/self/cgroup we see paths from host, + // which don't exist in container, so recover the container paths here by + // double-checking with /proc/pid/mountinfo + mountinfo, err := os.Open("/proc/self/mountinfo") + if err != nil { + return nil, err + } + defer mountinfo.Close() + + return loadPathsHelperWithMountinfo(cgroup, mountinfo) +} + +func loadPathsHelperWithMountinfo(cgroup, mountinfo io.Reader) (map[string]string, error) { paths := make(map[string]string) scanner := bufio.NewScanner(cgroup) @@ -225,6 +238,31 @@ func loadPathsHelper(cgroup io.Reader) (map[string]string, error) { if err := scanner.Err(); err != nil { return nil, err } + + mfScanner := bufio.NewScanner(mountinfo) + for mfScanner.Scan() { + txt := mfScanner.Text() + fields := strings.Fields(txt) + if len(fields) < 9 || fields[len(fields)-3] != "cgroup" { + continue + } + for _, opt := range strings.Split(fields[len(fields)-1], ",") { + // Remove prefix for cgroups with no controller, eg. systemd. + opt = strings.TrimPrefix(opt, "name=") + if cgroupPath, ok := paths[opt]; ok { + root := fields[3] + relCgroupPath, err := filepath.Rel(root, cgroupPath) + if err != nil { + return nil, err + } + paths[opt] = relCgroupPath + } + } + } + if err := mfScanner.Err(); err != nil { + return nil, err + } + return paths, nil } @@ -243,8 +281,13 @@ func New(spec *specs.Spec) (*Cgroup, error) { if spec.Linux == nil || spec.Linux.CgroupsPath == "" { return nil, nil } + return NewFromPath(spec.Linux.CgroupsPath) +} + +// NewFromPath creates a new Cgroup instance. +func NewFromPath(cgroupsPath string) (*Cgroup, error) { var parents map[string]string - if !filepath.IsAbs(spec.Linux.CgroupsPath) { + if !filepath.IsAbs(cgroupsPath) { var err error parents, err = LoadPaths("self") if err != nil { @@ -253,7 +296,7 @@ func New(spec *specs.Spec) (*Cgroup, error) { } own := make(map[string]bool) return &Cgroup{ - Name: spec.Linux.CgroupsPath, + Name: cgroupsPath, Parents: parents, Own: own, }, nil @@ -351,6 +394,9 @@ func (c *Cgroup) Join() (func(), error) { undo = func() { for _, path := range undoPaths { log.Debugf("Restoring cgroup %q", path) + // Writing the value 0 to a cgroup.procs file causes + // the writing process to be moved to the corresponding + // cgroup. - cgroups(7). if err := setValue(path, "cgroup.procs", "0"); err != nil { log.Warningf("Error restoring cgroup %q: %v", path, err) } @@ -361,6 +407,9 @@ func (c *Cgroup) Join() (func(), error) { for key, cfg := range controllers { path := c.makePath(key) log.Debugf("Joining cgroup %q", path) + // Writing the value 0 to a cgroup.procs file causes the + // writing process to be moved to the corresponding cgroup. + // - cgroups(7). if err := setValue(path, "cgroup.procs", "0"); err != nil { if cfg.optional && os.IsNotExist(err) { continue @@ -388,6 +437,16 @@ func (c *Cgroup) CPUQuota() (float64, error) { return float64(quota) / float64(period), nil } +// CPUUsage returns the total CPU usage of the cgroup. +func (c *Cgroup) CPUUsage() (uint64, error) { + path := c.makePath("cpuacct") + usage, err := getValue(path, "cpuacct.usage") + if err != nil { + return 0, err + } + return strconv.ParseUint(strings.TrimSpace(usage), 10, 64) +} + // NumCPU returns the number of CPUs configured in 'cpuset/cpuset.cpus'. func (c *Cgroup) NumCPU() (int, error) { path := c.makePath("cpuset") diff --git a/runsc/cgroup/cgroup_test.go b/runsc/cgroup/cgroup_test.go index 931144cf9..48d71cfa6 100644 --- a/runsc/cgroup/cgroup_test.go +++ b/runsc/cgroup/cgroup_test.go @@ -25,6 +25,39 @@ import ( "gvisor.dev/gvisor/pkg/test/testutil" ) +var debianMountinfo = ` +35 24 0:30 / /sys/fs/cgroup ro shared:9 - tmpfs tmpfs ro +36 35 0:31 / /sys/fs/cgroup/unified rw shared:10 - cgroup2 cgroup2 rw +37 35 0:32 / /sys/fs/cgroup/systemd rw - cgroup cgroup rw,name=systemd +41 35 0:36 / /sys/fs/cgroup/cpu,cpuacct rw shared:16 - cgroup cgroup rw,cpu,cpuacct +42 35 0:37 / /sys/fs/cgroup/freezer rw shared:17 - cgroup cgroup rw,freezer +43 35 0:38 / /sys/fs/cgroup/hugetlb rw shared:18 - cgroup cgroup rw,hugetlb +44 35 0:39 / /sys/fs/cgroup/cpuset rw shared:19 - cgroup cgroup rw,cpuset +45 35 0:40 / /sys/fs/cgroup/net_cls,net_prio rw shared:20 - cgroup cgroup rw,net_cls,net_prio +46 35 0:41 / /sys/fs/cgroup/pids rw shared:21 - cgroup cgroup rw,pids +47 35 0:42 / /sys/fs/cgroup/perf_event rw shared:22 - cgroup cgroup rw,perf_event +48 35 0:43 / /sys/fs/cgroup/memory rw shared:23 - cgroup cgroup rw,memory +49 35 0:44 / /sys/fs/cgroup/blkio rw shared:24 - cgroup cgroup rw,blkio +50 35 0:45 / /sys/fs/cgroup/devices rw shared:25 - cgroup cgroup rw,devices +51 35 0:46 / /sys/fs/cgroup/rdma rw shared:26 - cgroup cgroup rw,rdma +` + +var dindMountinfo = ` +1305 1304 0:64 / /sys/fs/cgroup rw - tmpfs tmpfs rw,mode=755 +1306 1305 0:32 /docker/136 /sys/fs/cgroup/systemd ro master:11 - cgroup cgroup rw,xattr,name=systemd +1307 1305 0:36 /docker/136 /sys/fs/cgroup/cpu,cpuacct ro master:16 - cgroup cgroup rw,cpu,cpuacct +1308 1305 0:37 /docker/136 /sys/fs/cgroup/freezer ro master:17 - cgroup cgroup rw,freezer +1309 1305 0:38 /docker/136 /sys/fs/cgroup/hugetlb ro master:18 - cgroup cgroup rw,hugetlb +1310 1305 0:39 /docker/136 /sys/fs/cgroup/cpuset ro master:19 - cgroup cgroup rw,cpuset +1311 1305 0:40 /docker/136 /sys/fs/cgroup/net_cls,net_prio ro master:20 - cgroup cgroup rw,net_cls,net_prio +1312 1305 0:41 /docker/136 /sys/fs/cgroup/pids ro master:21 - cgroup cgroup rw,pids +1313 1305 0:42 /docker/136 /sys/fs/cgroup/perf_event ro master:22 - cgroup cgroup rw,perf_event +1314 1305 0:43 /docker/136 /sys/fs/cgroup/memory ro master:23 - cgroup cgroup rw,memory +1316 1305 0:44 /docker/136 /sys/fs/cgroup/blkio ro master:24 - cgroup cgroup rw,blkio +1317 1305 0:45 /docker/136 /sys/fs/cgroup/devices ro master:25 - cgroup cgroup rw,devices +1318 1305 0:46 / /sys/fs/cgroup/rdma ro master:26 - cgroup cgroup rw,rdma +` + func TestUninstallEnoent(t *testing.T) { c := Cgroup{ // set a non-existent name @@ -653,60 +686,110 @@ func TestPids(t *testing.T) { func TestLoadPaths(t *testing.T) { for _, tc := range []struct { - name string - cgroups string - want map[string]string - err string + name string + cgroups string + mountinfo string + want map[string]string + err string }{ { - name: "abs-path", - cgroups: "0:ctr:/path", - want: map[string]string{"ctr": "/path"}, + name: "abs-path-unknown-controller", + cgroups: "0:ctr:/path", + mountinfo: debianMountinfo, + want: map[string]string{"ctr": "/path"}, }, { - name: "rel-path", - cgroups: "0:ctr:rel-path", - want: map[string]string{"ctr": "rel-path"}, + name: "rel-path", + cgroups: "0:ctr:rel-path", + mountinfo: debianMountinfo, + want: map[string]string{"ctr": "rel-path"}, }, { - name: "non-controller", - cgroups: "0:name=systemd:/path", - want: map[string]string{"systemd": "/path"}, + name: "non-controller", + cgroups: "0:name=systemd:/path", + mountinfo: debianMountinfo, + want: map[string]string{"systemd": "path"}, }, { - name: "empty", + name: "empty", + mountinfo: debianMountinfo, }, { name: "multiple", cgroups: "0:ctr0:/path0\n" + "1:ctr1:/path1\n" + "2::/empty\n", + mountinfo: debianMountinfo, want: map[string]string{ "ctr0": "/path0", "ctr1": "/path1", }, }, { - name: "missing-field", - cgroups: "0:nopath\n", - err: "invalid cgroups file", + name: "missing-field", + cgroups: "0:nopath\n", + mountinfo: debianMountinfo, + err: "invalid cgroups file", }, { - name: "too-many-fields", - cgroups: "0:ctr:/path:extra\n", - err: "invalid cgroups file", + name: "too-many-fields", + cgroups: "0:ctr:/path:extra\n", + mountinfo: debianMountinfo, + err: "invalid cgroups file", }, { name: "multiple-malformed", cgroups: "0:ctr0:/path0\n" + "1:ctr1:/path1\n" + "2:\n", - err: "invalid cgroups file", + mountinfo: debianMountinfo, + err: "invalid cgroups file", + }, + { + name: "nested-cgroup", + cgroups: `9:memory:/docker/136 +2:cpu,cpuacct:/docker/136 +1:name=systemd:/docker/136 +0::/system.slice/containerd.service`, + mountinfo: dindMountinfo, + // we want relative path to /sys/fs/cgroup inside the nested container. + // Subcroup inside the container will be created at /sys/fs/cgroup/cpu + // This will be /sys/fs/cgroup/cpu/docker/136/CGROUP_NAME + // outside the container + want: map[string]string{ + "memory": ".", + "cpu": ".", + "cpuacct": ".", + "systemd": ".", + }, + }, + { + name: "nested-cgroup-submount", + cgroups: "9:memory:/docker/136/test", + mountinfo: dindMountinfo, + want: map[string]string{ + "memory": "test", + }, + }, + { + name: "invalid-mount-info", + cgroups: "0:memory:/path", + mountinfo: "41 35 0:36 / /sys/fs/cgroup/memory rw shared:16 - invalid", + want: map[string]string{ + "memory": "/path", + }, + }, + { + name: "invalid-rel-path-in-proc-cgroup", + cgroups: "9:memory:./invalid", + mountinfo: dindMountinfo, + err: "can't make ./invalid relative to /docker/136", }, } { t.Run(tc.name, func(t *testing.T) { r := strings.NewReader(tc.cgroups) - got, err := loadPathsHelper(r) + mountinfo := strings.NewReader(tc.mountinfo) + got, err := loadPathsHelperWithMountinfo(r, mountinfo) if len(tc.err) == 0 { if err != nil { t.Fatalf("Unexpected error: %v", err) |