mirror of
https://github.com/containers/podman.git
synced 2025-10-13 01:06:10 +08:00
kpod top
Display information about processes in a running container. Signed-off-by: baude <bbaude@redhat.com> Closes: #121 Approved by: rhatdan
This commit is contained in:
@ -61,6 +61,7 @@ libpod is currently in active development.
|
|||||||
| [kpod-stats(1)](/docs/kpod-stats.1.md) | Display a live stream of one or more containers' resource usage statistics||
|
| [kpod-stats(1)](/docs/kpod-stats.1.md) | Display a live stream of one or more containers' resource usage statistics||
|
||||||
| [kpod-stop(1)](/docs/kpod-stop.1.md) | Stops one or more running containers ||
|
| [kpod-stop(1)](/docs/kpod-stop.1.md) | Stops one or more running containers ||
|
||||||
| [kpod-tag(1)](/docs/kpod-tag.1.md) | Add an additional name to a local image |[](https://asciinema.org/a/133803)|
|
| [kpod-tag(1)](/docs/kpod-tag.1.md) | Add an additional name to a local image |[](https://asciinema.org/a/133803)|
|
||||||
|
| [kpod-top(1)](/docs/kpod-top.1.md) | Display the running processes of a container
|
||||||
| [kpod-umount(1)](/docs/kpod-umount.1.md) | Unmount a working container's root filesystem ||
|
| [kpod-umount(1)](/docs/kpod-umount.1.md) | Unmount a working container's root filesystem ||
|
||||||
| [kpod-unpause(1)](/docs/kpod-unpause.1.md) | Unpause one or more running containers |[](https://asciinema.org/a/141292)|
|
| [kpod-unpause(1)](/docs/kpod-unpause.1.md) | Unpause one or more running containers |[](https://asciinema.org/a/141292)|
|
||||||
| [kpod-version(1)](/docs/kpod-version.1.md) | Display the version information |[](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)|
|
| [kpod-version(1)](/docs/kpod-version.1.md) | Display the version information |[](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)|
|
||||||
|
@ -155,8 +155,13 @@ func genImagesFormat(format string, quiet, noHeading, digests bool) string {
|
|||||||
func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) {
|
func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) {
|
||||||
if len(templParams) > 0 {
|
if len(templParams) > 0 {
|
||||||
for _, v := range templParams {
|
for _, v := range templParams {
|
||||||
|
fmt.Println(v)
|
||||||
|
fmt.Printf("%T\n", v)
|
||||||
genericParams = append(genericParams, interface{}(v))
|
genericParams = append(genericParams, interface{}(v))
|
||||||
}
|
}
|
||||||
|
fmt.Println("###")
|
||||||
|
fmt.Println(genericParams)
|
||||||
|
fmt.Println("###")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, v := range JSONParams {
|
for _, v := range JSONParams {
|
||||||
@ -178,6 +183,9 @@ func (i *imagesTemplateParams) headerMap() map[string]string {
|
|||||||
}
|
}
|
||||||
values[key] = strings.ToUpper(splitCamelCase(value))
|
values[key] = strings.ToUpper(splitCamelCase(value))
|
||||||
}
|
}
|
||||||
|
fmt.Println("!!!")
|
||||||
|
fmt.Printf("%+v\n", values)
|
||||||
|
fmt.Println("!!!")
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ func main() {
|
|||||||
statsCommand,
|
statsCommand,
|
||||||
stopCommand,
|
stopCommand,
|
||||||
tagCommand,
|
tagCommand,
|
||||||
|
topCommand,
|
||||||
umountCommand,
|
umountCommand,
|
||||||
unpauseCommand,
|
unpauseCommand,
|
||||||
versionCommand,
|
versionCommand,
|
||||||
|
258
cmd/kpod/top.go
Normal file
258
cmd/kpod/top.go
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/projectatomic/libpod/cmd/kpod/formats"
|
||||||
|
"github.com/projectatomic/libpod/libpod"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
topFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "format",
|
||||||
|
Usage: "Change the output to JSON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
topDescription = `
|
||||||
|
kpod top
|
||||||
|
|
||||||
|
Display the running processes of the container.
|
||||||
|
`
|
||||||
|
|
||||||
|
topCommand = cli.Command{
|
||||||
|
Name: "top",
|
||||||
|
Usage: "Display the running processes of a container",
|
||||||
|
Description: topDescription,
|
||||||
|
Flags: topFlags,
|
||||||
|
Action: topCmd,
|
||||||
|
ArgsUsage: "CONTAINER-NAME",
|
||||||
|
SkipArgReorder: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func topCmd(c *cli.Context) error {
|
||||||
|
doJSON := false
|
||||||
|
if c.IsSet("format") {
|
||||||
|
if strings.ToUpper(c.String("format")) == "JSON" {
|
||||||
|
doJSON = true
|
||||||
|
} else {
|
||||||
|
return errors.Errorf("only 'json' is supported for a format option")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args := c.Args()
|
||||||
|
var psArgs []string
|
||||||
|
psOpts := []string{"-o", "uid,pid,ppid,c,stime,tname,time,cmd"}
|
||||||
|
if len(args) < 1 {
|
||||||
|
return errors.Errorf("you must provide the name or id of a running container")
|
||||||
|
}
|
||||||
|
if err := validateFlags(c, topFlags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime, err := getRuntime(c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating libpod runtime")
|
||||||
|
}
|
||||||
|
defer runtime.Shutdown(false)
|
||||||
|
if len(args) > 1 {
|
||||||
|
psOpts = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := runtime.LookupContainer(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to lookup %s", args[0])
|
||||||
|
}
|
||||||
|
conStat, err := container.State()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to look up state for %s", args[0])
|
||||||
|
}
|
||||||
|
if conStat != libpod.ContainerStateRunning {
|
||||||
|
return errors.Errorf("top can only be used on running containers")
|
||||||
|
}
|
||||||
|
|
||||||
|
psArgs = append(psArgs, psOpts...)
|
||||||
|
|
||||||
|
results, err := container.GetContainerPidInformation(psArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
headers := getHeaders(results[0])
|
||||||
|
format := genTopFormat(headers)
|
||||||
|
var out formats.Writer
|
||||||
|
psParams, err := psDataToPSParams(results[1:], headers)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to convert ps data to proper structure")
|
||||||
|
}
|
||||||
|
if doJSON {
|
||||||
|
out = formats.JSONStructArray{Output: topToGeneric(psParams)}
|
||||||
|
} else {
|
||||||
|
out = formats.StdoutTemplateArray{Output: topToGeneric(psParams), Template: format, Fields: createTopHeaderMap(headers)}
|
||||||
|
}
|
||||||
|
formats.Writer(out).Out()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeaders(s string) []string {
|
||||||
|
var headers []string
|
||||||
|
tmpHeaders := strings.Fields(s)
|
||||||
|
for _, header := range tmpHeaders {
|
||||||
|
headers = append(headers, strings.Replace(header, "%", "", -1))
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTopFormat(headers []string) string {
|
||||||
|
format := "table "
|
||||||
|
for _, header := range headers {
|
||||||
|
format = fmt.Sprintf("%s{{.%s}}\t", format, header)
|
||||||
|
}
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// imagesToGeneric creates an empty array of interfaces for output
|
||||||
|
func topToGeneric(templParams []PSParams) (genericParams []interface{}) {
|
||||||
|
for _, v := range templParams {
|
||||||
|
genericParams = append(genericParams, interface{}(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the header based on the template provided
|
||||||
|
func createTopHeaderMap(v []string) map[string]string {
|
||||||
|
values := make(map[string]string)
|
||||||
|
for _, key := range v {
|
||||||
|
value := key
|
||||||
|
if value == "CPU" {
|
||||||
|
value = "%CPU"
|
||||||
|
} else if value == "MEM" {
|
||||||
|
value = "%MEM"
|
||||||
|
}
|
||||||
|
values[key] = strings.ToUpper(splitCamelCase(value))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSDataToParams converts a string array of data and its headers to an
|
||||||
|
// arra if PSParams
|
||||||
|
func psDataToPSParams(data []string, headers []string) ([]PSParams, error) {
|
||||||
|
var params []PSParams
|
||||||
|
for _, line := range data {
|
||||||
|
tmpMap := make(map[string]string)
|
||||||
|
tmpArray := strings.Fields(line)
|
||||||
|
if len(tmpArray) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for index, v := range tmpArray {
|
||||||
|
header := headers[index]
|
||||||
|
tmpMap[header] = v
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(tmpMap)
|
||||||
|
var r PSParams
|
||||||
|
err := json.Unmarshal(jsonData, &r)
|
||||||
|
if err != nil {
|
||||||
|
return []PSParams{}, err
|
||||||
|
}
|
||||||
|
params = append(params, r)
|
||||||
|
}
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//PSParams is a list of options that the command line ps recognizes
|
||||||
|
type PSParams struct {
|
||||||
|
CPU string `json: "%CPU"`
|
||||||
|
MEM string `json: "%MEM"`
|
||||||
|
COMMAND string
|
||||||
|
BLOCKED string
|
||||||
|
START string
|
||||||
|
TIME string
|
||||||
|
C string
|
||||||
|
CAUGHT string
|
||||||
|
CGROUP string
|
||||||
|
CLSCLS string
|
||||||
|
CLS string
|
||||||
|
CMD string
|
||||||
|
CP string
|
||||||
|
DRS string
|
||||||
|
EGID string
|
||||||
|
EGROUP string
|
||||||
|
EIP string
|
||||||
|
ESP string
|
||||||
|
ELAPSED string
|
||||||
|
EUIDE string
|
||||||
|
USER string
|
||||||
|
F string
|
||||||
|
FGID string
|
||||||
|
FGROUP string
|
||||||
|
FUID string
|
||||||
|
FUSER string
|
||||||
|
GID string
|
||||||
|
GROUP string
|
||||||
|
IGNORED string
|
||||||
|
IPCNS string
|
||||||
|
LABEL string
|
||||||
|
STARTED string
|
||||||
|
SESSION string
|
||||||
|
LWP string
|
||||||
|
MACHINE string
|
||||||
|
MAJFLT string
|
||||||
|
MINFLT string
|
||||||
|
MNTNS string
|
||||||
|
NETNS string
|
||||||
|
NI string
|
||||||
|
NLWP string
|
||||||
|
OWNER string
|
||||||
|
PENDING string
|
||||||
|
PGID string
|
||||||
|
PGRP string
|
||||||
|
PID string
|
||||||
|
PIDNS string
|
||||||
|
POL string
|
||||||
|
PPID string
|
||||||
|
PRI string
|
||||||
|
PSR string
|
||||||
|
RGID string
|
||||||
|
RGROUP string
|
||||||
|
RSS string
|
||||||
|
RSZ string
|
||||||
|
RTPRIO string
|
||||||
|
RUID string
|
||||||
|
RUSER string
|
||||||
|
S string
|
||||||
|
SCH string
|
||||||
|
SEAT string
|
||||||
|
SESS string
|
||||||
|
P string
|
||||||
|
SGID string
|
||||||
|
SGROUP string
|
||||||
|
SID string
|
||||||
|
SIZE string
|
||||||
|
SLICE string
|
||||||
|
SPID string
|
||||||
|
STACKP string
|
||||||
|
STIME string
|
||||||
|
SUID string
|
||||||
|
SUPGID string
|
||||||
|
SUPGRP string
|
||||||
|
SUSER string
|
||||||
|
SVGID string
|
||||||
|
SZ string
|
||||||
|
TGID string
|
||||||
|
THCNT string
|
||||||
|
TID string
|
||||||
|
TTY string
|
||||||
|
TPGID string
|
||||||
|
TRS string
|
||||||
|
TT string
|
||||||
|
UID string
|
||||||
|
UNIT string
|
||||||
|
USERNS string
|
||||||
|
UTSNS string
|
||||||
|
UUNIT string
|
||||||
|
VSZ string
|
||||||
|
WCHAN string
|
||||||
|
}
|
@ -1288,6 +1288,14 @@ kpod_tag() {
|
|||||||
_complete_ "$options_with_args" "$boolean_options"
|
_complete_ "$options_with_args" "$boolean_options"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kpod_top() {
|
||||||
|
local options_with_args="
|
||||||
|
"
|
||||||
|
local boolean_options="
|
||||||
|
"
|
||||||
|
_complete_ "$options_with_args" "$boolean_options"
|
||||||
|
}
|
||||||
|
|
||||||
_kpod_version() {
|
_kpod_version() {
|
||||||
local options_with_args="
|
local options_with_args="
|
||||||
"
|
"
|
||||||
|
59
docs/kpod-top.1.md
Normal file
59
docs/kpod-top.1.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
% kpod(1) kpod-top - display the running processes of a container
|
||||||
|
% Brent Baude
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
kpod top - Display the running processes of a container
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
**kpod top**
|
||||||
|
[**--help**|**-h**]
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
Display the running process of the container. ps-OPTION can be any of the options you would pass to a Linux ps command
|
||||||
|
|
||||||
|
**kpod [GLOBAL OPTIONS] top [OPTIONS]**
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
**--help, -h**
|
||||||
|
Print usage statement
|
||||||
|
|
||||||
|
**--format**
|
||||||
|
Display the output in an alternate format. The only supported format is **JSON**.
|
||||||
|
|
||||||
|
## EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
# kpod top f5a62a71b07
|
||||||
|
UID PID PPID %CPU STIME TT TIME CMD
|
||||||
|
0 18715 18705 0.0 10:35 pts/0 00:00:00 /bin/bash
|
||||||
|
0 18741 18715 0.0 10:35 pts/0 00:00:00 vi
|
||||||
|
#
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
#kpod --log-level=debug top f5a62a71b07 -o fuser,f,comm,label
|
||||||
|
FUSER F COMMAND LABEL
|
||||||
|
root 4 bash system_u:system_r:container_t:s0:c429,c1016
|
||||||
|
root 0 vi system_u:system_r:container_t:s0:c429,c1016
|
||||||
|
#
|
||||||
|
```
|
||||||
|
```
|
||||||
|
# kpod top --format=json f5a62a71b07b -o %cpu,%mem,command,blocked
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"CPU": "0.0",
|
||||||
|
"MEM": "0.0",
|
||||||
|
"COMMAND": "vi",
|
||||||
|
"BLOCKED": "0000000000000000",
|
||||||
|
"START": "",
|
||||||
|
"TIME": "",
|
||||||
|
"C": "",
|
||||||
|
"CAUGHT": "",
|
||||||
|
...
|
||||||
|
```
|
||||||
|
## SEE ALSO
|
||||||
|
kpod(1), ps(1)
|
||||||
|
|
||||||
|
## HISTORY
|
||||||
|
December 2017, Originally compiled by Brent Baude<bbaude@redhat.com>
|
@ -136,6 +136,9 @@ Stops one or more running containers.
|
|||||||
### tag
|
### tag
|
||||||
Add an additional name to a local image
|
Add an additional name to a local image
|
||||||
|
|
||||||
|
### top
|
||||||
|
Display the running processes of a container
|
||||||
|
|
||||||
### umount
|
### umount
|
||||||
Unmount a working container's root file system
|
Unmount a working container's root file system
|
||||||
|
|
||||||
|
57
libpod/container_top.go
Normal file
57
libpod/container_top.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package libpod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/projectatomic/libpod/utils"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetContainerPids reads sysfs to obtain the pids associated with the container's cgroup
|
||||||
|
// and uses locking
|
||||||
|
func (c *Container) GetContainerPids() ([]string, error) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
if err := c.syncContainer(); err != nil {
|
||||||
|
return []string{}, errors.Wrapf(err, "error updating container %s state", c.ID())
|
||||||
|
}
|
||||||
|
return c.getContainerPids()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the pids for a container without locking. should only be called from a func where
|
||||||
|
// locking has already been established.
|
||||||
|
func (c *Container) getContainerPids() ([]string, error) {
|
||||||
|
taskFile := filepath.Join("/sys/fs/cgroup/pids", CGroupParent, fmt.Sprintf("libpod-conmon-%s", c.ID()), c.ID(), "tasks")
|
||||||
|
logrus.Debug("reading pids from ", taskFile)
|
||||||
|
content, err := ioutil.ReadFile(taskFile)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, errors.Wrapf(err, "unable to read pids from %s", taskFile)
|
||||||
|
}
|
||||||
|
return strings.Fields(string(content)), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContainerPidInformation calls ps with the appropriate options and returns
|
||||||
|
// the results as a string
|
||||||
|
func (c *Container) GetContainerPidInformation(args []string) ([]string, error) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
if err := c.syncContainer(); err != nil {
|
||||||
|
return []string{}, errors.Wrapf(err, "error updating container %s state", c.ID())
|
||||||
|
}
|
||||||
|
pids, err := c.getContainerPids()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, errors.Wrapf(err, "unable to obtain pids for ", c.ID())
|
||||||
|
}
|
||||||
|
args = append(args, "-p", strings.Join(pids, ","))
|
||||||
|
logrus.Debug("Executing: ", strings.Join(args, " "))
|
||||||
|
results, err := utils.ExecCmd("ps", args...)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, errors.Wrapf(err, "unable to obtain information about pids")
|
||||||
|
}
|
||||||
|
return strings.Split(results, "\n"), nil
|
||||||
|
}
|
51
test/kpod_top.bats
Normal file
51
test/kpod_top.bats
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
cleanup_test
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
copy_images
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "top without container name or id" {
|
||||||
|
run ${KPOD_BINARY} ${KPOD_OPTIONS} top
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "top a bogus container" {
|
||||||
|
run ${KPOD_BINARY} ${KPOD_OPTIONS} top foobar
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "top non-running container by id with defaults" {
|
||||||
|
run ${KPOD_BINARY} ${KPOD_OPTIONS} create -d ${ALPINE} sleep 60
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ctr_id="$output"
|
||||||
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} top $ctr_id"
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "top running container by id with defaults" {
|
||||||
|
run ${KPOD_BINARY} ${KPOD_OPTIONS} run -dt ${ALPINE} /bin/sh
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ctr_id="$output"
|
||||||
|
echo $ctr_id
|
||||||
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} top $ctr_id"
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "top running container by id with ps opts" {
|
||||||
|
run ${KPOD_BINARY} ${KPOD_OPTIONS} run -d ${ALPINE} sleep 60
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ctr_id="$output"
|
||||||
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} top $ctr_id -o fuser,f,comm,label"
|
||||||
|
echo "$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
Reference in New Issue
Block a user