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
This commit is contained in:
haircommander
2018-08-16 18:26:24 -04:00
committed by Atomic Bot
parent 6c253d0055
commit 88df4ea0f9
7 changed files with 402 additions and 1 deletions

View File

@ -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{

101
cmd/podman/pod_top.go Normal file
View File

@ -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
}

92
docs/podman-pod-top.1.md Normal file
View File

@ -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>

55
libpod/pod_top_linux.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,8 @@
// +build !linux
package libpod
// GetPodPidInformation is exclusive to linux
func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) {
return nil, ErrNotImplemented
}

144
test/e2e/pod_top_test.go Normal file
View File

@ -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))
})
})

View File

@ -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