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-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-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-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)|
|
||||
|
@ -155,8 +155,13 @@ func genImagesFormat(format string, quiet, noHeading, digests bool) string {
|
||||
func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) {
|
||||
if len(templParams) > 0 {
|
||||
for _, v := range templParams {
|
||||
fmt.Println(v)
|
||||
fmt.Printf("%T\n", v)
|
||||
genericParams = append(genericParams, interface{}(v))
|
||||
}
|
||||
fmt.Println("###")
|
||||
fmt.Println(genericParams)
|
||||
fmt.Println("###")
|
||||
return
|
||||
}
|
||||
for _, v := range JSONParams {
|
||||
@ -178,6 +183,9 @@ func (i *imagesTemplateParams) headerMap() map[string]string {
|
||||
}
|
||||
values[key] = strings.ToUpper(splitCamelCase(value))
|
||||
}
|
||||
fmt.Println("!!!")
|
||||
fmt.Printf("%+v\n", values)
|
||||
fmt.Println("!!!")
|
||||
return values
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,7 @@ func main() {
|
||||
statsCommand,
|
||||
stopCommand,
|
||||
tagCommand,
|
||||
topCommand,
|
||||
umountCommand,
|
||||
unpauseCommand,
|
||||
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"
|
||||
}
|
||||
|
||||
kpod_top() {
|
||||
local options_with_args="
|
||||
"
|
||||
local boolean_options="
|
||||
"
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
||||
_kpod_version() {
|
||||
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
|
||||
Add an additional name to a local image
|
||||
|
||||
### top
|
||||
Display the running processes of a container
|
||||
|
||||
### umount
|
||||
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