summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--cmd/kpod/images.go8
-rw-r--r--cmd/kpod/main.go1
-rw-r--r--cmd/kpod/top.go258
-rw-r--r--completions/bash/kpod8
-rw-r--r--docs/kpod-top.1.md59
-rw-r--r--docs/kpod.1.md3
-rw-r--r--libpod/container_top.go57
-rw-r--r--test/kpod_top.bats51
9 files changed, 446 insertions, 0 deletions
diff --git a/README.md b/README.md
index 2ee07a23d..7e51dff87 100644
--- a/README.md
+++ b/README.md
@@ -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 |[![...](/docs/play.png)](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 |[![...](/docs/play.png)](https://asciinema.org/a/141292)|
| [kpod-version(1)](/docs/kpod-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)|
diff --git a/cmd/kpod/images.go b/cmd/kpod/images.go
index 2b1003ebd..6384d61b8 100644
--- a/cmd/kpod/images.go
+++ b/cmd/kpod/images.go
@@ -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
}
diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go
index 708031dc9..90a4d1d7f 100644
--- a/cmd/kpod/main.go
+++ b/cmd/kpod/main.go
@@ -62,6 +62,7 @@ func main() {
statsCommand,
stopCommand,
tagCommand,
+ topCommand,
umountCommand,
unpauseCommand,
versionCommand,
diff --git a/cmd/kpod/top.go b/cmd/kpod/top.go
new file mode 100644
index 000000000..9c2fcd34e
--- /dev/null
+++ b/cmd/kpod/top.go
@@ -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
+}
diff --git a/completions/bash/kpod b/completions/bash/kpod
index 3e291c526..f5caeceb3 100644
--- a/completions/bash/kpod
+++ b/completions/bash/kpod
@@ -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="
"
diff --git a/docs/kpod-top.1.md b/docs/kpod-top.1.md
new file mode 100644
index 000000000..e19b7342c
--- /dev/null
+++ b/docs/kpod-top.1.md
@@ -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>
diff --git a/docs/kpod.1.md b/docs/kpod.1.md
index dec29e395..02f97739e 100644
--- a/docs/kpod.1.md
+++ b/docs/kpod.1.md
@@ -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
diff --git a/libpod/container_top.go b/libpod/container_top.go
new file mode 100644
index 000000000..7d6dad2b4
--- /dev/null
+++ b/libpod/container_top.go
@@ -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
+}
diff --git a/test/kpod_top.bats b/test/kpod_top.bats
new file mode 100644
index 000000000..189772a11
--- /dev/null
+++ b/test/kpod_top.bats
@@ -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 ]
+}