Browse code

Merge pull request #37131 from kolyshkin/top-opt

Optimize ContainerTop() aka docker top

Sebastiaan van Stijn authored on 2018/05/30 10:28:07
Showing 4 changed files
... ...
@@ -229,6 +229,10 @@ func checkMounted(t *testing.T, p string, expect bool) {
229 229
 }
230 230
 
231 231
 func TestRootMountCleanup(t *testing.T) {
232
+	if os.Getuid() != 0 {
233
+		t.Skip("root required")
234
+	}
235
+
232 236
 	t.Parallel()
233 237
 
234 238
 	testRoot, err := ioutil.TempDir("", t.Name())
... ...
@@ -153,6 +153,10 @@ func TestValidContainerNames(t *testing.T) {
153 153
 }
154 154
 
155 155
 func TestContainerInitDNS(t *testing.T) {
156
+	if os.Getuid() != 0 {
157
+		t.Skip("root required") // for chown
158
+	}
159
+
156 160
 	tmp, err := ioutil.TempDir("", "docker-container-test-")
157 161
 	if err != nil {
158 162
 		t.Fatal(err)
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4
+	"os"
4 5
 	"reflect"
5 6
 	"sort"
6 7
 	"testing"
... ...
@@ -499,6 +500,9 @@ func TestDaemonDiscoveryReloadOnlyClusterAdvertise(t *testing.T) {
499 499
 }
500 500
 
501 501
 func TestDaemonReloadNetworkDiagnosticPort(t *testing.T) {
502
+	if os.Getuid() != 0 {
503
+		t.Skip("root required")
504
+	}
502 505
 	daemon := &Daemon{
503 506
 		imageService: images.NewImageService(images.ImageServiceConfig{}),
504 507
 	}
... ...
@@ -3,6 +3,7 @@
3 3
 package daemon // import "github.com/docker/docker/daemon"
4 4
 
5 5
 import (
6
+	"bytes"
6 7
 	"context"
7 8
 	"fmt"
8 9
 	"os/exec"
... ...
@@ -11,6 +12,8 @@ import (
11 11
 	"strings"
12 12
 
13 13
 	"github.com/docker/docker/api/types/container"
14
+	"github.com/docker/docker/errdefs"
15
+	"github.com/pkg/errors"
14 16
 )
15 17
 
16 18
 func validatePSArgs(psArgs string) error {
... ...
@@ -70,6 +73,7 @@ func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody
70 70
 	for i, name := range procList.Titles {
71 71
 		if name == "PID" {
72 72
 			pidIndex = i
73
+			break
73 74
 		}
74 75
 	}
75 76
 	if pidIndex == -1 {
... ...
@@ -112,6 +116,20 @@ func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody
112 112
 	return procList, nil
113 113
 }
114 114
 
115
+// psPidsArg converts a slice of PIDs to a string consisting
116
+// of comma-separated list of PIDs prepended by "-q".
117
+// For example, psPidsArg([]uint32{1,2,3}) returns "-q1,2,3".
118
+func psPidsArg(pids []uint32) string {
119
+	b := []byte{'-', 'q'}
120
+	for i, p := range pids {
121
+		b = strconv.AppendUint(b, uint64(p), 10)
122
+		if i < len(pids)-1 {
123
+			b = append(b, ',')
124
+		}
125
+	}
126
+	return string(b)
127
+}
128
+
115 129
 // ContainerTop lists the processes running inside of the given
116 130
 // container by calling ps with the given args, or with the flags
117 131
 // "-ef" if no args are given.  An error is returned if the container
... ...
@@ -144,9 +162,23 @@ func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.Conta
144 144
 		return nil, err
145 145
 	}
146 146
 
147
-	output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output()
147
+	args := strings.Split(psArgs, " ")
148
+	pids := psPidsArg(procs)
149
+	output, err := exec.Command("ps", append(args, pids)...).Output()
148 150
 	if err != nil {
149
-		return nil, fmt.Errorf("Error running ps: %v", err)
151
+		// some ps options (such as f) can't be used together with q,
152
+		// so retry without it
153
+		output, err = exec.Command("ps", args...).Output()
154
+		if err != nil {
155
+			if ee, ok := err.(*exec.ExitError); ok {
156
+				// first line of stderr shows why ps failed
157
+				line := bytes.SplitN(ee.Stderr, []byte{'\n'}, 2)
158
+				if len(line) > 0 && len(line[0]) > 0 {
159
+					err = errors.New(string(line[0]))
160
+				}
161
+			}
162
+			return nil, errdefs.System(errors.Wrap(err, "ps"))
163
+		}
150 164
 	}
151 165
 	procList, err := parsePSOutput(output, procs)
152 166
 	if err != nil {