Browse code

Reimplement lxc-ps

Instead of calling lxc-ps in top endpoint, we reimplement it by
calling ps and filter for pids running in a given container.

Johannes 'fish' Ziemke authored on 2013/12/11 23:56:44
Showing 2 changed files
... ...
@@ -1,7 +1,6 @@
1 1
 package docker
2 2
 
3 3
 import (
4
-	"bufio"
5 4
 	"encoding/json"
6 5
 	"errors"
7 6
 	"fmt"
... ...
@@ -690,34 +689,54 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
690 690
 
691 691
 func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) {
692 692
 	if container := srv.runtime.Get(name); container != nil {
693
-		output, err := exec.Command("lxc-ps", "--name", container.ID, "--", psArgs).CombinedOutput()
693
+		pids, err := utils.GetPidsForContainer(container.ID)
694 694
 		if err != nil {
695
-			return nil, fmt.Errorf("lxc-ps: %s (%s)", err, output)
695
+			return nil, err
696
+		}
697
+		if len(psArgs) == 0 {
698
+			psArgs = "-ef"
699
+		}
700
+		output, err := exec.Command("ps", psArgs).Output()
701
+		if err != nil {
702
+			return nil, fmt.Errorf("Error running ps: %s", err)
703
+		}
704
+
705
+		lines := strings.Split(string(output), "\n")
706
+		header := strings.Fields(lines[0])
707
+		procs := APITop{
708
+			Titles: header,
709
+		}
710
+
711
+		pidIndex := -1
712
+		for i, name := range header {
713
+			if name == "PID" {
714
+				pidIndex = i
715
+			}
696 716
 		}
697
-		procs := APITop{}
698
-		for i, line := range strings.Split(string(output), "\n") {
717
+		if pidIndex == -1 {
718
+			return nil, errors.New("Couldn't find PID field in ps output")
719
+		}
720
+
721
+		for _, line := range lines[1:] {
699 722
 			if len(line) == 0 {
700 723
 				continue
701 724
 			}
702
-			words := []string{}
703
-			scanner := bufio.NewScanner(strings.NewReader(line))
704
-			scanner.Split(bufio.ScanWords)
705
-			if !scanner.Scan() {
706
-				return nil, fmt.Errorf("Wrong output using lxc-ps")
725
+			fields := strings.Fields(line)
726
+			p, err := strconv.Atoi(fields[pidIndex])
727
+			if err != nil {
728
+				return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err)
707 729
 			}
708
-			// no scanner.Text because we skip container id
709
-			for scanner.Scan() {
710
-				if i != 0 && len(words) == len(procs.Titles) {
711
-					words[len(words)-1] = fmt.Sprintf("%s %s", words[len(words)-1], scanner.Text())
712
-				} else {
713
-					words = append(words, scanner.Text())
730
+
731
+			for _, pid := range pids {
732
+				if pid == p {
733
+					// Make sure number of fields equals number of header titles
734
+					// merging "overhanging" fields
735
+					processes := fields[:len(procs.Titles)-1]
736
+					processes = append(processes, strings.Join(fields[len(procs.Titles)-1:], " "))
737
+
738
+					procs.Processes = append(procs.Processes, processes)
714 739
 				}
715 740
 			}
716
-			if i == 0 {
717
-				procs.Titles = words
718
-			} else {
719
-				procs.Processes = append(procs.Processes, words)
720
-			}
721 741
 		}
722 742
 		return &procs, nil
723 743
 
... ...
@@ -1117,3 +1117,59 @@ func CopyFile(src, dst string) (int64, error) {
1117 1117
 	defer df.Close()
1118 1118
 	return io.Copy(df, sf)
1119 1119
 }
1120
+
1121
+// Returns the relative path to the cgroup docker is running in.
1122
+func GetThisCgroup(cgroupType string) (string, error) {
1123
+	output, err := ioutil.ReadFile("/proc/self/cgroup")
1124
+	if err != nil {
1125
+		return "", err
1126
+	}
1127
+	for _, line := range strings.Split(string(output), "\n") {
1128
+		parts := strings.Split(line, ":")
1129
+		// any type used by docker should work
1130
+		if parts[1] == cgroupType {
1131
+			return parts[2], nil
1132
+		}
1133
+	}
1134
+	return "", fmt.Errorf("cgroup '%s' not found in /proc/self/cgroup", cgroupType)
1135
+}
1136
+
1137
+// Returns a list of pids for the given container.
1138
+func GetPidsForContainer(id string) ([]int, error) {
1139
+	pids := []int{}
1140
+
1141
+	// memory is chosen randomly, any cgroup used by docker works
1142
+	cgroupType := "memory"
1143
+
1144
+	cgroupRoot, err := FindCgroupMountpoint(cgroupType)
1145
+	if err != nil {
1146
+		return pids, err
1147
+	}
1148
+
1149
+	cgroupThis, err := GetThisCgroup(cgroupType)
1150
+	if err != nil {
1151
+		return pids, err
1152
+	}
1153
+
1154
+	filename := filepath.Join(cgroupRoot, cgroupThis, id, "tasks")
1155
+	if _, err := os.Stat(filename); os.IsNotExist(err) {
1156
+		// With more recent lxc versions use, cgroup will be in lxc/
1157
+		filename = filepath.Join(cgroupRoot, cgroupThis, "lxc", id, "tasks")
1158
+	}
1159
+
1160
+	output, err := ioutil.ReadFile(filename)
1161
+	if err != nil {
1162
+		return pids, err
1163
+	}
1164
+	for _, p := range strings.Split(string(output), "\n") {
1165
+		if len(p) == 0 {
1166
+			continue
1167
+		}
1168
+		pid, err := strconv.Atoi(p)
1169
+		if err != nil {
1170
+			return pids, fmt.Errorf("Invalid pid '%s': %s", p, err)
1171
+		}
1172
+		pids = append(pids, pid)
1173
+	}
1174
+	return pids, nil
1175
+}