summaryrefslogtreecommitdiffhomepage
path: root/runsc
diff options
context:
space:
mode:
Diffstat (limited to 'runsc')
-rw-r--r--runsc/boot/controller.go8
-rw-r--r--runsc/cmd/BUILD1
-rw-r--r--runsc/cmd/usage.go93
-rw-r--r--runsc/container/container.go18
-rw-r--r--runsc/container/container_test.go124
-rw-r--r--runsc/sandbox/sandbox.go53
6 files changed, 297 insertions, 0 deletions
diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go
index 60b532798..b0282604a 100644
--- a/runsc/boot/controller.go
+++ b/runsc/boot/controller.go
@@ -114,6 +114,13 @@ const (
FsCat = "Fs.Cat"
)
+// Usage related commands (see usage.go for more details).
+const (
+ UsageCollect = "Usage.Collect"
+ UsageUsageFD = "Usage.UsageFD"
+ UsageReduce = "Usage.Reduce"
+)
+
// ControlSocketAddr generates an abstract unix socket name for the given ID.
func ControlSocketAddr(id string) string {
return fmt.Sprintf("\x00runsc-sandbox.%s", id)
@@ -157,6 +164,7 @@ func newController(fd int, l *Loader) (*controller, error) {
ctrl.srv.Register(&control.Logging{})
ctrl.srv.Register(&control.Lifecycle{l.k})
ctrl.srv.Register(&control.Fs{l.k})
+ ctrl.srv.Register(&control.Usage{l.k})
if l.root.conf.ProfileEnable {
ctrl.srv.Register(control.NewProfile(l.k))
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index 031ddd57e..c5e32807d 100644
--- a/runsc/cmd/BUILD
+++ b/runsc/cmd/BUILD
@@ -36,6 +36,7 @@ go_library(
"statefile.go",
"symbolize.go",
"syscalls.go",
+ "usage.go",
"verity_prepare.go",
"wait.go",
],
diff --git a/runsc/cmd/usage.go b/runsc/cmd/usage.go
new file mode 100644
index 000000000..d2aeafa28
--- /dev/null
+++ b/runsc/cmd/usage.go
@@ -0,0 +1,93 @@
+// Copyright 2021 The gVisor 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 cmd
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/google/subcommands"
+ "gvisor.dev/gvisor/runsc/config"
+ "gvisor.dev/gvisor/runsc/container"
+ "gvisor.dev/gvisor/runsc/flag"
+)
+
+// Usage implements subcommands.Command for the "usage" command.
+type Usage struct {
+ full bool
+ fd bool
+}
+
+// Name implements subcommands.Command.Name.
+func (*Usage) Name() string {
+ return "usage"
+}
+
+// Synopsis implements subcommands.Command.Synopsis.
+func (*Usage) Synopsis() string {
+ return "Usage shows application memory usage across various categories in bytes."
+}
+
+// Usage implements subcommands.Command.Usage.
+func (*Usage) Usage() string {
+ return `usage [flags] <container id> - print memory usages to standard output.`
+}
+
+// SetFlags implements subcommands.Command.SetFlags.
+func (u *Usage) SetFlags(f *flag.FlagSet) {
+ f.BoolVar(&u.full, "full", false, "enumerate all usage by categories")
+ f.BoolVar(&u.fd, "fd", false, "retrieves a subset of usage through the established usage FD")
+}
+
+// Execute implements subcommands.Command.Execute.
+func (u *Usage) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+ if f.NArg() < 1 {
+ f.Usage()
+ return subcommands.ExitUsageError
+ }
+
+ id := f.Arg(0)
+ conf := args[0].(*config.Config)
+
+ cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
+ if err != nil {
+ Fatalf("loading container: %v", err)
+ }
+
+ if !u.fd {
+ m, err := cont.Usage(u.full)
+ if err != nil {
+ Fatalf("usage failed: %v", err)
+ }
+ if err := json.NewEncoder(os.Stdout).Encode(m); err != nil {
+ Fatalf("Encode MemoryUsage failed: %v", err)
+ }
+ } else {
+ m, err := cont.UsageFD()
+ if err != nil {
+ Fatalf("usagefd failed: %v", err)
+ }
+
+ mapped, unknown, total, err := m.Fetch()
+ if err != nil {
+ Fatalf("Fetch memory usage failed: %v", err)
+ }
+
+ fmt.Printf("Mapped %v, Unknown %v, Total %v\n", mapped, unknown, total)
+ }
+ return subcommands.ExitSuccess
+}
diff --git a/runsc/container/container.go b/runsc/container/container.go
index d1f979eb2..381f57574 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -652,6 +652,24 @@ func (c *Container) Cat(files []string, out *os.File) error {
return c.Sandbox.Cat(c.ID, files, out)
}
+// Usage displays memory used by the application.
+func (c *Container) Usage(full bool) (control.MemoryUsage, error) {
+ log.Debugf("Usage in container, cid: %s, full: %v", c.ID, full)
+ return c.Sandbox.Usage(c.ID, full)
+}
+
+// UsageFD shows application memory usage using two donated FDs.
+func (c *Container) UsageFD() (*control.MemoryUsageRecord, error) {
+ log.Debugf("UsageFD in container, cid: %s", c.ID)
+ return c.Sandbox.UsageFD(c.ID)
+}
+
+// Reduce requests that the sentry attempt to reduce its memory usage.
+func (c *Container) Reduce(wait bool) error {
+ log.Debugf("Reduce in container, cid: %s", c.ID)
+ return c.Sandbox.Reduce(c.ID, wait)
+}
+
// State returns the metadata of the container.
func (c *Container) State() specs.State {
return specs.State{
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 960c36946..8f612db8f 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -2655,3 +2655,127 @@ func TestCat(t *testing.T) {
t.Errorf("out got %s, want include %s", buf, want)
}
}
+
+// TestUsage checks that usage generates the expected memory usage.
+func TestUsage(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ for _, full := range []bool{false, true} {
+ m, err := cont.Usage(full)
+ if err != nil {
+ t.Fatalf("error usage from container: %v", err)
+ }
+ if m.Mapped == 0 {
+ t.Errorf("Usage mapped got zero")
+ }
+ if m.Total == 0 {
+ t.Errorf("Usage total got zero")
+ }
+ if full {
+ if m.System == 0 {
+ t.Errorf("Usage system got zero")
+ }
+ if m.Anonymous == 0 {
+ t.Errorf("Usage anonymous got zero")
+ }
+ }
+ }
+}
+
+// TestUsageFD checks that usagefd generates the expected memory usage.
+func TestUsageFD(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ m, err := cont.UsageFD()
+ if err != nil {
+ t.Fatalf("error usageFD from container: %v", err)
+ }
+
+ mapped, unknown, total, err := m.Fetch()
+ if err != nil {
+ t.Fatalf("error Fetch memory usage: %v", err)
+ }
+
+ if mapped == 0 {
+ t.Errorf("UsageFD Mapped got zero")
+ }
+ if unknown == 0 {
+ t.Errorf("UsageFD unknown got zero")
+ }
+ if total == 0 {
+ t.Errorf("UsageFD total got zero")
+ }
+}
+
+// TestReduce checks that reduce call succeeds.
+func TestReduce(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ if err := cont.Reduce(false); err != nil {
+ t.Fatalf("error reduce from container: %v", err)
+ }
+}
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index b15572a98..c8c6886c7 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -1020,6 +1020,59 @@ func (s *Sandbox) Cat(cid string, files []string, out *os.File) error {
return nil
}
+// Usage sends the collect call for a container in the sandbox.
+func (s *Sandbox) Usage(cid string, Full bool) (control.MemoryUsage, error) {
+ log.Debugf("Usage sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return control.MemoryUsage{}, err
+ }
+ defer conn.Close()
+
+ var m control.MemoryUsage
+ err = conn.Call(boot.UsageCollect, &control.MemoryUsageOpts{
+ Full: Full,
+ }, &m)
+ return m, err
+}
+
+// UsageFD sends the usagefd call for a container in the sandbox.
+func (s *Sandbox) UsageFD(cid string) (*control.MemoryUsageRecord, error) {
+ log.Debugf("Usage sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ var m control.MemoryUsageFile
+ if err := conn.Call(boot.UsageUsageFD, &control.MemoryUsageFileOpts{
+ Version: 1,
+ }, &m); err != nil {
+ return nil, fmt.Errorf("UsageFD failed: %v", err)
+ }
+
+ if len(m.FilePayload.Files) != 2 {
+ return nil, fmt.Errorf("wants exactly two fds")
+ }
+
+ return control.NewMemoryUsageRecord(*m.FilePayload.Files[0], *m.FilePayload.Files[1])
+}
+
+// Reduce sends the reduce call for a container in the sandbox.
+func (s *Sandbox) Reduce(cid string, wait bool) error {
+ log.Debugf("Reduce sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return conn.Call(boot.UsageReduce, &control.UsageReduceOpts{
+ Wait: wait,
+ }, nil)
+}
+
// IsRunning returns true if the sandbox or gofer process is running.
func (s *Sandbox) IsRunning() bool {
if s.Pid != 0 {