Skip to content

Commit

Permalink
setup top cmd
Browse files Browse the repository at this point in the history
Signed-off-by: fahed dorgaa <fahed.dorgaa@gmail.com>
  • Loading branch information
fahedouch committed May 21, 2021
1 parent afdaec8 commit a5b8d88
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 40 deletions.
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ func newApp() *cli.App {
versionCommand,
// Inspect
inspectCommand,
// stats
topCommand,
// Management
containerCommand,
imageCommand,
Expand Down
203 changes: 163 additions & 40 deletions top.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,72 @@
package main

import (
"bytes"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"

"github.com/containerd/containerd"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"regexp"
)

// ContainerTopOKBody OK response to ContainerTop operation
type ContainerTopOKBody struct {

// Each process running in the container, where each is process
// is an array of values corresponding to the titles.
//
// Required: true
Processes [][]string `json:"Processes"`

// The ps column titles
// Required: true
Titles []string `json:"Titles"`
}

var topCommand = &cli.Command{
Name: "top",
Usage: "docker top CONTAINER",
ArgsUsage: "CONTAINER [ps OPTIONS]",
Action: topAction,
BashComplete: topBashComplete,
Name: "top",
Usage: "Display the running processes of a container",
ArgsUsage: "CONTAINER [ps OPTIONS]",
Action: topAction,
//BashComplete: topBashComplete,
}

func topAction(clicontext *cli.Context) error {
if clicontext.NArg() < 1 {
return errors.Errorf("requires at least 1 argument")
}
//TODO
return containerTop(clicontext, clicontext.Args().First(), strings.Join(clicontext.Args().Tail(), " "))
}

//function from moby/moby
//function from moby/moby/daemon/top_unix.go
func appendProcess2ProcList(procList *ContainerTopOKBody, fields []string) {
// Make sure number of fields equals number of header titles
// merging "overhanging" fields
process := fields[:len(procList.Titles)-1]
process = append(process, strings.Join(fields[len(procList.Titles)-1:], " "))
procList.Processes = append(procList.Processes, process)
}

//function from moby/moby/daemon/top_unix.go
// psPidsArg converts a slice of PIDs to a string consisting
// of comma-separated list of PIDs prepended by "-q".
// For example, psPidsArg([]uint32{1,2,3}) returns "-q1,2,3".
func psPidsArg(pids []uint32) string {
b := []byte{'-', 'q'}
for i, p := range pids {
b = strconv.AppendUint(b, uint64(p), 10)
if i < len(pids)-1 {
b = append(b, ',')
}
}
return string(b)
}

//function from moby/moby/daemon/top_unix.go
func validatePSArgs(psArgs string) error {
// NOTE: \\s does not detect unicode whitespaces.
// So we use fieldsASCII instead of strings.Fields in parsePSOutput.
Expand All @@ -57,57 +101,137 @@ func validatePSArgs(psArgs string) error {
return nil
}

//function from moby/moby
//function from moby/moby/daemon/top_unix.go
// fieldsASCII is similar to strings.Fields but only allows ASCII whitespaces
func fieldsASCII(s string) []string {
fn := func(r rune) bool {
switch r {
case '\t', '\n', '\f', '\r', ' ':
return true
}
return false
}
return strings.FieldsFunc(s, fn)
}

//function from moby/moby/daemon/top_unix.go
func hasPid(procs []uint32, pid int) bool {
for _, p := range procs {
if int(p) == pid {
return true
}
}
return false
}

//function from moby/moby/daemon/top_unix.go
func parsePSOutput(output []byte, procs []uint32) (*ContainerTopOKBody, error) {
procList := &ContainerTopOKBody{}

lines := strings.Split(string(output), "\n")
procList.Titles = fieldsASCII(lines[0])

pidIndex := -1
for i, name := range procList.Titles {
if name == "PID" {
pidIndex = i
break
}
}
if pidIndex == -1 {
return nil, fmt.Errorf("Couldn't find PID field in ps output")
}

// loop through the output and extract the PID from each line
// fixing #30580, be able to display thread line also when "m" option used
// in "docker top" client command
preContainedPidFlag := false
for _, line := range lines[1:] {
if len(line) == 0 {
continue
}
fields := fieldsASCII(line)

var (
p int
err error
)

if fields[pidIndex] == "-" {
if preContainedPidFlag {
appendProcess2ProcList(procList, fields)
}
continue
}
p, err = strconv.Atoi(fields[pidIndex])
if err != nil {
return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err)
}

if hasPid(procs, p) {
preContainedPidFlag = true
appendProcess2ProcList(procList, fields)
continue
}
preContainedPidFlag = false
}
return procList, nil
}

// function inspired from moby/moby/daemon/top_unix.go
// ContainerTop lists the processes running inside of the given
// container by calling ps with the given args, or with the flags
// "-ef" if no args are given. An error is returned if the container
// is not found, or is not running, or if there are any problems
// running ps, or parsing the output.
func (daemon *Daemon) ContainerTop(clicontext *cli.Context, name string, psArgs string) (*container.ContainerTopOKBody, error) {
func containerTop(clicontext *cli.Context, name string, psArgs string) error {
if psArgs == "" {
psArgs = "-ef"
}

if err := validatePSArgs(psArgs); err != nil {
return nil, err
return err
}

client, ctx, cancel, err := newClient(clicontext)
if err != nil {
return err
}
defer cancel()
client, ctx, cancel, err := newClient(clicontext)
if err != nil {
return err
}
defer cancel()

container, err := client.LoadContainer(ctx, name)
if err != nil {
return err
}
container, err := client.LoadContainer(ctx, name)
if err != nil {
return err
}

task, err := container.Task(ctx, nil)
if err != nil {
return err
}
if err != nil {
return err
}

status, err := task.Status(ctx)
if err != nil {
return err
}
status, err := task.Status(ctx)
if err != nil {
return err
}

if status.Status != containerd.Running {
return nil, errNotRunning(ctr.ID)
return nil
}

if ctr.IsRestarting() {
return nil, errContainerIsRestarting(ctr.ID)
}
//TO DO handle restarting case: wait for container to restart and then launch top command

procs, err := containerd.ListPids(context.Background(), ctr.ID)
procs, err := task.Pids(ctx)
if err != nil {
return nil, err
return err
}

var psList []uint32
for _, ps := range procs {
psList = append(psList, ps.Pid)
}

args := strings.Split(psArgs, " ")
pids := psPidsArg(procs)
pids := psPidsArg(psList)
output, err := exec.Command("ps", append(args, pids)...).Output()
if err != nil {
// some ps options (such as f) can't be used together with q,
Expand All @@ -121,14 +245,13 @@ func (daemon *Daemon) ContainerTop(clicontext *cli.Context, name string, psArgs
err = errors.New(string(line[0]))
}
}
return nil, errdefs.System(errors.Wrap(err, "ps"))
return nil
}
}
procList, err := parsePSOutput(output, procs)
procList, err := parsePSOutput(output, psList)
if err != nil {
return nil, err
return err
}
//daemon.LogContainerEvent(ctr, "top")
return procList, nil
fmt.Println(procList)
return nil
}

0 comments on commit a5b8d88

Please sign in to comment.