From 7bf30d7bf13852d1f2b493817489d37170d2b8a4 Mon Sep 17 00:00:00 2001 From: Michael Crosby <crosbymichael@gmail.com> Date: Wed, 6 Nov 2019 11:39:36 -0500 Subject: [PATCH] Rename playground to cgctl Signed-off-by: Michael Crosby <crosbymichael@gmail.com> --- .gitignore | 2 +- cmd/cgctl/main.go | 120 +++++++++++++++++++++++++++++++++ cmd/cgroups-playground/main.go | 54 --------------- v2/errors.go | 3 +- v2/manager.go | 76 ++++++++++++++++++--- v2/paths.go | 14 ---- 6 files changed, 189 insertions(+), 80 deletions(-) create mode 100644 cmd/cgctl/main.go delete mode 100644 cmd/cgroups-playground/main.go diff --git a/.gitignore b/.gitignore index 23495f5f..54f3c5db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ example/example -cmd/cgroups-playground/cgroups-playground +cmd/cgctl diff --git a/cmd/cgctl/main.go b/cmd/cgctl/main.go new file mode 100644 index 00000000..27ceb01c --- /dev/null +++ b/cmd/cgctl/main.go @@ -0,0 +1,120 @@ +/* + 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 main + +import ( + "fmt" + "os" + + v2 "github.com/containerd/cgroups/v2" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "cgctl" + app.Version = "1" + app.Usage = "cgroup v2 management tool" + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + Usage: "enable debug output in the logs", + }, + cli.StringFlag{ + Name: "mountpoint", + Usage: "cgroup mountpoint", + Value: "/sys/fs/cgroup", + }, + } + app.Commands = []cli.Command{ + newCommand, + delCommand, + listCommand, + } + app.Before = func(clix *cli.Context) error { + if clix.GlobalBool("debug") { + logrus.SetLevel(logrus.DebugLevel) + } + return nil + } + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +var newCommand = cli.Command{ + Name: "new", + Usage: "create a new cgroup", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "enable", + Usage: "enable the controllers for the group", + }, + }, + Action: func(clix *cli.Context) error { + path := clix.Args().First() + c, err := v2.NewManager(clix.GlobalString("mountpoint"), path, nil) + if err != nil { + return err + } + if clix.Bool("enable") { + controllers, err := c.ListControllers() + if err != nil { + return err + } + if err := c.ToggleControllers(controllers, v2.Enable); err != nil { + return err + } + } + return nil + }, +} + +var delCommand = cli.Command{ + Name: "del", + Usage: "delete a cgroup", + Action: func(clix *cli.Context) error { + path := clix.Args().First() + c, err := v2.LoadManager(clix.GlobalString("mountpoint"), path) + if err != nil { + return err + } + return c.Delete() + }, +} + +var listCommand = cli.Command{ + Name: "list", + Usage: "list processes in a cgroup", + Action: func(clix *cli.Context) error { + path := clix.Args().First() + c, err := v2.LoadManager(clix.GlobalString("mountpoint"), path) + if err != nil { + return err + } + procs, err := c.Procs(true) + if err != nil { + return err + } + for _, p := range procs { + fmt.Println(p) + } + return nil + }, +} diff --git a/cmd/cgroups-playground/main.go b/cmd/cgroups-playground/main.go deleted file mode 100644 index 641b541d..00000000 --- a/cmd/cgroups-playground/main.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - 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 main - -import ( - "os" - - v2 "github.com/containerd/cgroups/v2" - "github.com/sirupsen/logrus" -) - -func main() { - if err := xmain(); err != nil { - logrus.Fatalf("%+v", err) - } -} - -func xmain() error { - pid := os.Getpid() - g, err := v2.PidGroupPath(pid) - if err != nil { - return err - } - unifiedMountpoint := "/sys/fs/cgroup" - logrus.Infof("Loading V2 for %q (PID %d), mountpoint=%q", g, pid, unifiedMountpoint) - cg, err := v2.LoadManager(unifiedMountpoint, g) - if err != nil { - return err - } - processes, err := cg.Procs(true) - if err != nil { - return err - } - logrus.Infof("Has %d processes (recursively)", len(processes)) - for i, s := range processes { - logrus.Infof("Process %d: %d", i, s) - } - - return nil -} diff --git a/v2/errors.go b/v2/errors.go index 60af72dd..46d2d9c2 100644 --- a/v2/errors.go +++ b/v2/errors.go @@ -31,8 +31,7 @@ var ( ErrCPUNotSupported = errors.New("cgroups: cpu cgroup (v2) not supported on this system") ErrCgroupDeleted = errors.New("cgroups: cgroup deleted") ErrNoCgroupMountDestination = errors.New("cgroups: cannot find cgroup mount destination") - - ErrInvalidGroupPath = errors.New("cgroups: group path format must be compatible with /proc/PID/cgroup") + ErrInvalidGroupPath = errors.New("cgroups: invalid group path") ) // ErrorHandler is a function that handles and acts on errors diff --git a/v2/manager.go b/v2/manager.go index cc8397dc..5b6f2d62 100644 --- a/v2/manager.go +++ b/v2/manager.go @@ -17,6 +17,7 @@ package v2 import ( + "bufio" "io/ioutil" "os" "path/filepath" @@ -27,6 +28,10 @@ import ( "github.com/pkg/errors" ) +const ( + subtreeControl = "cgroup.subtree_control" +) + type cgValuer interface { Values() []Value } @@ -93,18 +98,19 @@ func writeValues(path string, values []Value) error { } func NewManager(mountpoint string, group string, resources *Resources) (*Manager, error) { - if err := VerifyGroupPath(group); err != nil { - return nil, err + if group == "" { + return nil, ErrInvalidGroupPath } - path := filepath.Join(mountpoint, group) if err := os.MkdirAll(path, defaultDirPerm); err != nil { return nil, err } - if err := writeValues(path, resources.Values()); err != nil { - // clean up cgroup dir on failure - os.Remove(path) - return nil, err + if resources != nil { + if err := writeValues(path, resources.Values()); err != nil { + // clean up cgroup dir on failure + os.Remove(path) + return nil, err + } } return &Manager{ unifiedMountpoint: mountpoint, @@ -113,8 +119,8 @@ func NewManager(mountpoint string, group string, resources *Resources) (*Manager } func LoadManager(mountpoint string, group string) (*Manager, error) { - if err := VerifyGroupPath(group); err != nil { - return nil, err + if group == "" { + return nil, ErrInvalidGroupPath } path := filepath.Join(mountpoint, group) return &Manager{ @@ -128,6 +134,58 @@ type Manager struct { path string } +func (c *Manager) ListControllers() ([]string, error) { + f, err := os.Open(filepath.Join(c.path, subtreeControl)) + if err != nil { + return nil, err + } + defer f.Close() + + var ( + out []string + s = bufio.NewScanner(f) + ) + s.Split(bufio.ScanWords) + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + out = append(out, s.Text()) + } + return out, nil +} + +type ControllerToggle int + +const ( + Enable ControllerToggle = iota + 1 + Disable +) + +func toggleFunc(controllers []string, prefix string) []string { + out := make([]string, len(controllers)) + for i, c := range controllers { + out[i] = prefix + c + } + return out +} + +func (c *Manager) ToggleControllers(controllers []string, t ControllerToggle) error { + f, err := os.OpenFile(filepath.Join(c.path, subtreeControl), os.O_WRONLY, 0) + if err != nil { + return err + } + defer f.Close() + switch t { + case Enable: + controllers = toggleFunc(controllers, "+") + case Disable: + controllers = toggleFunc(controllers, "-") + } + _, err = f.WriteString(strings.Join(controllers, " ")) + return err +} + func (c *Manager) NewChild(name string, resources *Resources) (*Manager, error) { if strings.HasPrefix(name, "/") { return nil, errors.New("name must be relative") diff --git a/v2/paths.go b/v2/paths.go index 75d450f2..171e45bd 100644 --- a/v2/paths.go +++ b/v2/paths.go @@ -19,7 +19,6 @@ package v2 import ( "fmt" "path/filepath" - "strings" ) // NestedGroupPath will nest the cgroups based on the calling processes cgroup @@ -38,16 +37,3 @@ func PidGroupPath(pid int) (string, error) { p := fmt.Sprintf("/proc/%d/cgroup", pid) return parseCgroupFile(p) } - -// VerifyGroupPath verifies the format of g. -// VerifyGroupPath doesn't verify whether g actually exists on the system. -func VerifyGroupPath(g string) error { - s := string(g) - if !strings.HasPrefix(s, "/") { - return ErrInvalidGroupPath - } - if strings.HasPrefix(s, "/sys/fs/cgroup") { - return ErrInvalidGroupPath - } - return nil -}