summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhaircommander <pehunt@redhat.com>2018-08-16 18:26:24 -0400
committerAtomic Bot <atomic-devel@projectatomic.io>2018-08-23 15:01:17 +0000
commit88df4ea0f97f2892a6fd588bd3abfad1db634629 (patch)
tree9ecea1234ccf25488095c9b2150ad625c21a3716
parent6c253d00554d5f5c17cc7bbb76e9658f277ab129 (diff)
downloadpodman-88df4ea0f97f2892a6fd588bd3abfad1db634629.tar.gz
podman-88df4ea0f97f2892a6fd588bd3abfad1db634629.tar.bz2
podman-88df4ea0f97f2892a6fd588bd3abfad1db634629.zip
Add podman pod top
Using the vendored changes from psgo, incorporate JoinNamespaceAndProcessInfoByPids to get process information for each pid namespace of running containers in the pod. Also added a man page, and tests. Signed-off-by: haircommander <pehunt@redhat.com> Closes: #1298 Approved by: mheon
-rw-r--r--cmd/podman/pod.go1
-rw-r--r--cmd/podman/pod_top.go101
-rw-r--r--docs/podman-pod-top.1.md92
-rw-r--r--libpod/pod_top_linux.go55
-rw-r--r--libpod/pod_top_unsupported.go8
-rw-r--r--test/e2e/pod_top_test.go144
-rw-r--r--vendor.conf2
7 files changed, 402 insertions, 1 deletions
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go
index e691204d8..dde43f77c 100644
--- a/cmd/podman/pod.go
+++ b/cmd/podman/pod.go
@@ -20,6 +20,7 @@ Pods are a group of one or more containers sharing the same network, pid and ipc
podStartCommand,
podStatsCommand,
podStopCommand,
+ podTopCommand,
podUnpauseCommand,
}
podCommand = cli.Command{
diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go
new file mode 100644
index 000000000..703ec0a44
--- /dev/null
+++ b/cmd/podman/pod_top.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var (
+ podTopFlags = []cli.Flag{
+ LatestFlag,
+ cli.BoolFlag{
+ Name: "list-descriptors",
+ Hidden: true,
+ },
+ }
+ podTopDescription = fmt.Sprintf(`Display the running processes containers in a pod. Specify format descriptors
+to alter the output. You may run "podman pod top -l pid pcpu seccomp" to print
+the process ID, the CPU percentage and the seccomp mode of each process of
+the latest pod.
+%s
+`, getDescriptorString())
+
+ podTopCommand = cli.Command{
+ Name: "top",
+ Usage: "Display the running processes of containers in a pod",
+ Description: podTopDescription,
+ Flags: podTopFlags,
+ Action: podTopCmd,
+ ArgsUsage: "POD-NAME [format descriptors]",
+ SkipArgReorder: true,
+ }
+)
+
+func podTopCmd(c *cli.Context) error {
+ var pod *libpod.Pod
+ var err error
+ args := c.Args()
+
+ if c.Bool("list-descriptors") {
+ descriptors, err := libpod.GetContainerPidInformationDescriptors()
+ if err != nil {
+ return err
+ }
+ fmt.Println(strings.Join(descriptors, "\n"))
+ return nil
+ }
+
+ if len(args) < 1 && !c.Bool("latest") {
+ return errors.Errorf("you must provide the name or id of a running pod")
+ }
+ if err := validateFlags(c, podTopFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ var descriptors []string
+ if c.Bool("latest") {
+ descriptors = args
+ pod, err = runtime.GetLatestPod()
+ } else {
+ descriptors = args[1:]
+ pod, err = runtime.LookupPod(args[0])
+ }
+
+ if err != nil {
+ return errors.Wrapf(err, "unable to lookup requested container")
+ }
+
+ podStatus, err := shared.GetPodStatus(pod)
+ if err != nil {
+ return err
+ }
+ if podStatus != "Running" {
+ return errors.Errorf("pod top can only be used on pods with at least one running container")
+ }
+
+ psOutput, err := pod.GetPodPidInformation(descriptors)
+ if err != nil {
+ return err
+ }
+
+ w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
+ for _, proc := range psOutput {
+ fmt.Fprintln(w, proc)
+ }
+ w.Flush()
+ return nil
+}
diff --git a/docs/podman-pod-top.1.md b/docs/podman-pod-top.1.md
new file mode 100644
index 000000000..7a66b4534
--- /dev/null
+++ b/docs/podman-pod-top.1.md
@@ -0,0 +1,92 @@
+% podman-pod-top "1"
+
+## NAME
+podman\-pod\-top - Display the running processes of containers in a pod
+
+## SYNOPSIS
+**podman top** [*options*] *pod* [*format-descriptors*]
+
+## DESCRIPTION
+Display the running process of containers in a pod. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process.
+
+## OPTIONS
+
+**--help, -h**
+
+ Print usage statement
+
+**--latest, -l**
+
+Instead of providing the pod name or ID, use the last created pod.
+
+## FORMAT DESCRIPTORS
+
+The following descriptors are supported in addition to the AIX format descriptors mentioned in ps (1):
+
+**args, capbnd, capeff, capinh, capprm, comm, etime, group, hgroup, hpid, huser, label, nice, pcpu, pgid, pid, ppid, rgroup, ruser, seccomp, state, time, tty, user, vsz**
+
+**capbnd**
+
+ Set of bounding capabilities. See capabilities (7) for more information.
+
+**capeff**
+
+ Set of effective capabilities. See capabilities (7) for more information.
+
+**capinh**
+
+ Set of inheritable capabilities. See capabilities (7) for more information.
+
+**capprm**
+
+ Set of permitted capabilities. See capabilities (7) for more information.
+
+**hgroup**
+
+ The corresponding effective group of a container process on the host.
+
+**hpid**
+
+ The corresponding host PID of a container process.
+
+**huser**
+
+ The corresponding effective user of a container process on the host.
+
+**label**
+
+ Current security attributes of the process.
+
+**seccomp**
+
+ Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp (2) for more information.
+
+**state**
+
+ Process state codes (e.g, **R** for *running*, **S** for *sleeping*). See proc(5) for more information.
+
+## EXAMPLES
+
+By default, `podman-top` prints data similar to `ps -ef`:
+
+```
+ # podman pod top b031293491cc
+USER PID PPID %CPU ELAPSED TTY TIME COMMAND
+root 1 0 0.000 2h5m38.737137571s ? 0s top
+root 8 0 0.000 2h5m15.737228361s ? 0s top
+```
+
+The output can be controlled by specifying format descriptors as arguments after the pod:
+
+```
+ # podman pod top -l pid seccomp args %C
+PID SECCOMP COMMAND %CPU
+1 filter top 0.000
+1 filter /bin/sh 0.000
+```
+
+## SEE ALSO
+podman-pod(1), ps(1), seccomp(2), proc(5), capabilities(7)
+
+## HISTORY
+August 2018, Originally compiled by Peter Hunt <pehunt@redhat.com>
diff --git a/libpod/pod_top_linux.go b/libpod/pod_top_linux.go
new file mode 100644
index 000000000..f49e28c9d
--- /dev/null
+++ b/libpod/pod_top_linux.go
@@ -0,0 +1,55 @@
+// +build linux
+
+package libpod
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/containers/psgo"
+)
+
+// GetPodPidInformation returns process-related data of all processes in
+// the pod. The output data can be controlled via the `descriptors`
+// argument which expects format descriptors and supports all AIXformat
+// descriptors of ps (1) plus some additional ones to for instance inspect the
+// set of effective capabilities. Eeach element in the returned string slice
+// is a tab-separated string.
+//
+// For more details, please refer to github.com/containers/psgo.
+func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ pids := make([]string, 0)
+ ctrsInPod, err := p.allContainers()
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range ctrsInPod {
+ c.lock.Lock()
+
+ if err := c.syncContainer(); err != nil {
+ c.lock.Unlock()
+ return nil, err
+ }
+ if c.state.State == ContainerStateRunning {
+ pid := strconv.Itoa(c.state.PID)
+ pids = append(pids, pid)
+ }
+ c.lock.Unlock()
+ }
+ // TODO: psgo returns a [][]string to give users the ability to apply
+ // filters on the data. We need to change the API here and the
+ // varlink API to return a [][]string if we want to make use of
+ // filtering.
+ output, err := psgo.JoinNamespaceAndProcessInfoByPids(pids, descriptors)
+ if err != nil {
+ return nil, err
+ }
+ res := []string{}
+ for _, out := range output {
+ res = append(res, strings.Join(out, "\t"))
+ }
+ return res, nil
+}
diff --git a/libpod/pod_top_unsupported.go b/libpod/pod_top_unsupported.go
new file mode 100644
index 000000000..d44470523
--- /dev/null
+++ b/libpod/pod_top_unsupported.go
@@ -0,0 +1,8 @@
+// +build !linux
+
+package libpod
+
+// GetPodPidInformation is exclusive to linux
+func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) {
+ return nil, ErrNotImplemented
+}
diff --git a/test/e2e/pod_top_test.go b/test/e2e/pod_top_test.go
new file mode 100644
index 000000000..2b4aa540f
--- /dev/null
+++ b/test/e2e/pod_top_test.go
@@ -0,0 +1,144 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman top", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.CleanupPod()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("podman pod top without pod name or id", func() {
+ result := podmanTest.Podman([]string{"pod", "top"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(125))
+ })
+
+ It("podman pod top on bogus pod", func() {
+ result := podmanTest.Podman([]string{"pod", "top", "1234"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(125))
+ })
+
+ It("podman pod top on non-running pod", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ result := podmanTest.Podman([]string{"top", podid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(125))
+ })
+
+ It("podman pod top on pod", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "top", "-l"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
+ })
+
+ It("podman pod top with options", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "top", podid, "pid", "%C", "args"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
+ })
+
+ It("podman pod top on pod invalid options", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "top", podid, "invalid"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(125))
+ })
+
+ It("podman pod top on pod with containers in same pid namespace", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, "--pid", fmt.Sprintf("container:%s", cid), ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "top", podid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(Equal(4))
+ })
+
+ It("podman pod top on pod with containers in different namespace", func() {
+ session := podmanTest.Podman([]string{"pod", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ podid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "-d", "--pod", podid, ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"pod", "top", podid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(Equal(4))
+ })
+})
diff --git a/vendor.conf b/vendor.conf
index 86278ae05..4a2e82e7a 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -12,7 +12,7 @@ github.com/containernetworking/cni v0.7.0-alpha1
github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1
github.com/containers/image 134f99bed228d6297dc01d152804f6f09f185418
github.com/containers/storage 17c7d1fee5603ccf6dd97edc14162fc1510e7e23
-github.com/containers/psgo master
+github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee
github.com/coreos/go-systemd v14
github.com/cri-o/ocicni master
github.com/cyphar/filepath-securejoin v0.2.1