mirror of
https://github.com/containers/podman.git
synced 2025-06-03 20:33:20 +08:00

Make the output of top tabular to be compatible with Docker. Please note, that any user-input for `GetContainerPidInformation(...)` will be ignored until we have found a way to generically and reliably parse ps-1 output or until there is a go-lib to extract all the data from /proc in a ps-1 compatible fashion. Fixes: #458 Signed-off-by: Valentin Rothberg <vrothberg@suse.com> Closes: #939 Approved by: rhatdan
136 lines
4.1 KiB
Go
136 lines
4.1 KiB
Go
package libpod
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/projectatomic/libpod/pkg/util"
|
|
"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) {
|
|
if !c.batched {
|
|
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) {
|
|
cgroupPath, err := c.CGroupPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
taskFile := filepath.Join("/sys/fs/cgroup/pids", cgroupPath, "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 and the container's PIDs as a []string. Note that
|
|
// the ps output columns of each string are separated by a '\t\'. Currently,
|
|
// the args argument is overwriten with []string{"-ef"} until there is a
|
|
// portable library for ps-1 or to parse the procFS to extract all data.
|
|
func (c *Container) GetContainerPidInformation(args []string) ([]string, error) {
|
|
// XXX(ps-issue): args is overwriten with []{"-ef"} as the ps-1 tool
|
|
// doesn't support a generic way of splitting columns, rendering its
|
|
// output hard to parse. Docker first deterimes the number of columns
|
|
// and merges all exceeding ones into the last one. We believe that
|
|
// writing a go library which parses procFS in a ps-compatible way may
|
|
// be more beneficial in the long run. Until then, args will be
|
|
// ignored.
|
|
args = []string{"-ef"}
|
|
|
|
if !c.batched {
|
|
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")
|
|
}
|
|
|
|
filteredOutput, err := filterPids(results, pids)
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
return filteredOutput, nil
|
|
}
|
|
|
|
func filterPids(psOutput string, pids []string) ([]string, error) {
|
|
var output []string
|
|
results := strings.Split(psOutput, "\n")
|
|
// The headers are in the first line of the results
|
|
headers := fieldsASCII(results[0])
|
|
// We need to make sure PID in headers, so that we can filter
|
|
// Pids that don't belong.
|
|
|
|
// append the headers back in
|
|
output = append(output, strings.Join(headers, "\t"))
|
|
|
|
pidIndex := -1
|
|
for i, header := range headers {
|
|
if header == "PID" {
|
|
pidIndex = i
|
|
}
|
|
}
|
|
if pidIndex == -1 {
|
|
return []string{}, errors.Errorf("unable to find PID field in ps output. try a different set of ps arguments")
|
|
}
|
|
for _, l := range results[1:] {
|
|
if l == "" {
|
|
continue
|
|
}
|
|
cols := fieldsASCII(l)
|
|
pid := cols[pidIndex]
|
|
if util.StringInSlice(pid, pids) {
|
|
// XXX(ps-issue): Strip cols down to the header's size
|
|
// and merge exceeding fields. This is required to
|
|
// "merge" the overhanging CMD entries which can
|
|
// contain white spaces.
|
|
out := cols[:len(headers)-1]
|
|
out = append(out, strings.Join(cols[len(headers)-1:], " "))
|
|
output = append(output, strings.Join(out, "\t"))
|
|
}
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
// Detects 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)
|
|
}
|