diff --git a/server.go b/server.go index a0e6191090..8a8819c04a 100644 --- a/server.go +++ b/server.go @@ -1,7 +1,6 @@ package docker import ( - "bufio" "encoding/json" "errors" "fmt" @@ -690,34 +689,54 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) { func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) { if container := srv.runtime.Get(name); container != nil { - output, err := exec.Command("lxc-ps", "--name", container.ID, "--", psArgs).CombinedOutput() + pids, err := utils.GetPidsForContainer(container.ID) if err != nil { - return nil, fmt.Errorf("lxc-ps: %s (%s)", err, output) + return nil, err } - procs := APITop{} - for i, line := range strings.Split(string(output), "\n") { + if len(psArgs) == 0 { + psArgs = "-ef" + } + output, err := exec.Command("ps", psArgs).Output() + if err != nil { + return nil, fmt.Errorf("Error running ps: %s", err) + } + + lines := strings.Split(string(output), "\n") + header := strings.Fields(lines[0]) + procs := APITop{ + Titles: header, + } + + pidIndex := -1 + for i, name := range header { + if name == "PID" { + pidIndex = i + } + } + if pidIndex == -1 { + return nil, errors.New("Couldn't find PID field in ps output") + } + + for _, line := range lines[1:] { if len(line) == 0 { continue } - words := []string{} - scanner := bufio.NewScanner(strings.NewReader(line)) - scanner.Split(bufio.ScanWords) - if !scanner.Scan() { - return nil, fmt.Errorf("Wrong output using lxc-ps") + fields := strings.Fields(line) + p, err := strconv.Atoi(fields[pidIndex]) + if err != nil { + return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) } - // no scanner.Text because we skip container id - for scanner.Scan() { - if i != 0 && len(words) == len(procs.Titles) { - words[len(words)-1] = fmt.Sprintf("%s %s", words[len(words)-1], scanner.Text()) - } else { - words = append(words, scanner.Text()) + + for _, pid := range pids { + if pid == p { + // Make sure number of fields equals number of header titles + // merging "overhanging" fields + processes := fields[:len(procs.Titles)-1] + processes = append(processes, strings.Join(fields[len(procs.Titles)-1:], " ")) + + procs.Processes = append(procs.Processes, processes) } } - if i == 0 { - procs.Titles = words - } else { - procs.Processes = append(procs.Processes, words) - } } return &procs, nil diff --git a/utils/utils.go b/utils/utils.go index 6864dddf82..410f33f5ee 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1117,3 +1117,59 @@ func CopyFile(src, dst string) (int64, error) { defer df.Close() return io.Copy(df, sf) } + +// Returns the relative path to the cgroup docker is running in. +func GetThisCgroup(cgroupType string) (string, error) { + output, err := ioutil.ReadFile("/proc/self/cgroup") + if err != nil { + return "", err + } + for _, line := range strings.Split(string(output), "\n") { + parts := strings.Split(line, ":") + // any type used by docker should work + if parts[1] == cgroupType { + return parts[2], nil + } + } + return "", fmt.Errorf("cgroup '%s' not found in /proc/self/cgroup", cgroupType) +} + +// Returns a list of pids for the given container. +func GetPidsForContainer(id string) ([]int, error) { + pids := []int{} + + // memory is chosen randomly, any cgroup used by docker works + cgroupType := "memory" + + cgroupRoot, err := FindCgroupMountpoint(cgroupType) + if err != nil { + return pids, err + } + + cgroupThis, err := GetThisCgroup(cgroupType) + if err != nil { + return pids, err + } + + filename := filepath.Join(cgroupRoot, cgroupThis, id, "tasks") + if _, err := os.Stat(filename); os.IsNotExist(err) { + // With more recent lxc versions use, cgroup will be in lxc/ + filename = filepath.Join(cgroupRoot, cgroupThis, "lxc", id, "tasks") + } + + output, err := ioutil.ReadFile(filename) + if err != nil { + return pids, err + } + for _, p := range strings.Split(string(output), "\n") { + if len(p) == 0 { + continue + } + pid, err := strconv.Atoi(p) + if err != nil { + return pids, fmt.Errorf("Invalid pid '%s': %s", p, err) + } + pids = append(pids, pid) + } + return pids, nil +}