summaryrefslogtreecommitdiff
path: root/libpod/container_top_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'libpod/container_top_linux.go')
-rw-r--r--libpod/container_top_linux.go88
1 files changed, 87 insertions, 1 deletions
diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go
index 5f4f28130..98edc340a 100644
--- a/libpod/container_top_linux.go
+++ b/libpod/container_top_linux.go
@@ -3,6 +3,8 @@
package libpod
import (
+ "bufio"
+ "os"
"strconv"
"strings"
@@ -10,6 +12,7 @@ import (
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/psgo"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// Top gathers statistics about the running processes in a container. It returns a
@@ -36,7 +39,34 @@ func (c *Container) Top(descriptors []string) ([]string, error) {
}
}
}
- return c.GetContainerPidInformation(psgoDescriptors)
+
+ // If we encountered an ErrUnknownDescriptor error, fallback to executing
+ // ps(1). This ensures backwards compatibility to users depending on ps(1)
+ // and makes sure we're ~compatible with docker.
+ output, psgoErr := c.GetContainerPidInformation(psgoDescriptors)
+ if psgoErr == nil {
+ return output, nil
+ }
+ if errors.Cause(psgoErr) != psgo.ErrUnknownDescriptor {
+ return nil, psgoErr
+ }
+
+ output, err = c.execPS(descriptors)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error executing ps(1) in the container")
+ }
+
+ // Trick: filter the ps command from the output instead of
+ // checking/requiring PIDs in the output.
+ filtered := []string{}
+ cmd := strings.Join(descriptors, " ")
+ for _, line := range output {
+ if !strings.Contains(line, cmd) {
+ filtered = append(filtered, line)
+ }
+ }
+
+ return filtered, nil
}
// GetContainerPidInformation returns process-related data of all processes in
@@ -65,3 +95,59 @@ func (c *Container) GetContainerPidInformation(descriptors []string) ([]string,
}
return res, nil
}
+
+// execPS executes ps(1) with the specified args in the container.
+func (c *Container) execPS(args []string) ([]string, error) {
+ rPipe, wPipe, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ defer wPipe.Close()
+ defer rPipe.Close()
+
+ rErrPipe, wErrPipe, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ defer wErrPipe.Close()
+ defer rErrPipe.Close()
+
+ streams := new(AttachStreams)
+ streams.OutputStream = wPipe
+ streams.ErrorStream = wErrPipe
+ streams.AttachOutput = true
+ streams.AttachError = true
+
+ stdout := []string{}
+ go func() {
+ scanner := bufio.NewScanner(rPipe)
+ for scanner.Scan() {
+ stdout = append(stdout, scanner.Text())
+ }
+ }()
+ stderr := []string{}
+ go func() {
+ scanner := bufio.NewScanner(rErrPipe)
+ for scanner.Scan() {
+ stderr = append(stderr, scanner.Text())
+ }
+ }()
+
+ cmd := append([]string{"ps"}, args...)
+ ec, err := c.Exec(false, false, map[string]string{}, cmd, "", "", streams, 0, nil, "")
+ if err != nil {
+ return nil, err
+ } else if ec != 0 {
+ return nil, errors.Errorf("Runtime failed with exit status: %d and output: %s", ec, strings.Join(stderr, " "))
+ }
+
+ if logrus.GetLevel() >= logrus.DebugLevel {
+ // If we're running in debug mode or higher, we might want to have a
+ // look at stderr which includes debug logs from conmon.
+ for _, log := range stderr {
+ logrus.Debugf("%s", log)
+ }
+ }
+
+ return stdout, nil
+}