aboutsummaryrefslogtreecommitdiff
path: root/libpod/container_top.go
blob: f5d314d49b8fc3cab011e2789075ce1013d4646e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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)
}