mirror of
https://github.com/containers/podman.git
synced 2025-07-04 10:10:32 +08:00
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:

committed by
Atomic Bot

parent
6c253d0055
commit
88df4ea0f9
@ -20,6 +20,7 @@ Pods are a group of one or more containers sharing the same network, pid and ipc
|
|||||||
podStartCommand,
|
podStartCommand,
|
||||||
podStatsCommand,
|
podStatsCommand,
|
||||||
podStopCommand,
|
podStopCommand,
|
||||||
|
podTopCommand,
|
||||||
podUnpauseCommand,
|
podUnpauseCommand,
|
||||||
}
|
}
|
||||||
podCommand = cli.Command{
|
podCommand = cli.Command{
|
||||||
|
101
cmd/podman/pod_top.go
Normal file
101
cmd/podman/pod_top.go
Normal 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
92
docs/podman-pod-top.1.md
Normal 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
55
libpod/pod_top_linux.go
Normal 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
|
||||||
|
}
|
8
libpod/pod_top_unsupported.go
Normal file
8
libpod/pod_top_unsupported.go
Normal 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
144
test/e2e/pod_top_test.go
Normal 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))
|
||||||
|
})
|
||||||
|
})
|
@ -12,7 +12,7 @@ github.com/containernetworking/cni v0.7.0-alpha1
|
|||||||
github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1
|
github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1
|
||||||
github.com/containers/image 134f99bed228d6297dc01d152804f6f09f185418
|
github.com/containers/image 134f99bed228d6297dc01d152804f6f09f185418
|
||||||
github.com/containers/storage 17c7d1fee5603ccf6dd97edc14162fc1510e7e23
|
github.com/containers/storage 17c7d1fee5603ccf6dd97edc14162fc1510e7e23
|
||||||
github.com/containers/psgo master
|
github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee
|
||||||
github.com/coreos/go-systemd v14
|
github.com/coreos/go-systemd v14
|
||||||
github.com/cri-o/ocicni master
|
github.com/cri-o/ocicni master
|
||||||
github.com/cyphar/filepath-securejoin v0.2.1
|
github.com/cyphar/filepath-securejoin v0.2.1
|
||||||
|
Reference in New Issue
Block a user