1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

ContainerTop: speed up

Current ContainerTop (a.k.a. docker top) implementation uses "ps"
to get the info about *all* running processes, then parses it, then
filters the results to only contain PIDs used by the container.
Collecting data only to throw most of it away is inefficient,
especially on a system running many containers (or processes).
For example, "docker top" on a container with a single process
can take up to 0.5 seconds to execute (on a mostly idle system)
which is noticeably slow.

Since the containers PIDs are known beforehand, let's use ps's
"-q" option to provide it with a list of PIDs we want info about.

The problem with this approach is, some ps options can't be used
with "-q" (the only one I'm aware of is "f" ("forest view") but
there might be more). As the list of such options is not known,
in case ps fails, it is executed again without "q" (retaining
the old behavior).

Next, the data produced by "ps" is filtered in the same way as before.
The difference here is, in case "-q" worked, the list is much shorter.

I ran some benchmarks on my laptop, with about 8000 "sleep" processes
running to amplify the savings.

The improvement in "docker top" execution times is 5x to 10x (roughly
0.05s vs 0.5s).

The improvement in ContainerTop() execution time is up to 100x
(roughly 3ms vs 300ms).

I haven't measured the memory or the CPU time savings, guess those
are not that critical.

NOTE that busybox ps does not implement -q so the fallback is always
used, but AFAIK it is not usable anyway and Docker expects a normal
ps to be on the system (say the list of fields produced by
"busybox ps -ef" differs from normal "ps -ef" etc.).

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
Kir Kolyshkin 2018-05-22 16:26:30 -07:00
parent 654a7625fc
commit a076badb8b

View file

@ -113,6 +113,20 @@ func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody
return procList, nil return procList, nil
} }
// psPidsArg converts a slice of PIDs to a string consisting
// of comma-separated list of PIDs prepended by "-q".
// For example, psPidsArg([]uint32{1,2,3}) returns "-q1,2,3".
func psPidsArg(pids []uint32) string {
b := []byte{'-', 'q'}
for i, p := range pids {
b = strconv.AppendUint(b, uint64(p), 10)
if i < len(pids)-1 {
b = append(b, ',')
}
}
return string(b)
}
// ContainerTop lists the processes running inside of the given // ContainerTop lists the processes running inside of the given
// container by calling ps with the given args, or with the flags // container by calling ps with the given args, or with the flags
// "-ef" if no args are given. An error is returned if the container // "-ef" if no args are given. An error is returned if the container
@ -145,9 +159,16 @@ func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.Conta
return nil, err return nil, err
} }
output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output() args := strings.Split(psArgs, " ")
pids := psPidsArg(procs)
output, err := exec.Command("ps", append(args, pids)...).Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("Error running ps: %v", err) // some ps options (such as f) can't be used together with q,
// so retry without it
output, err = exec.Command("ps", args...).Output()
if err != nil {
return nil, fmt.Errorf("Error running ps: %v", err)
}
} }
procList, err := parsePSOutput(output, procs) procList, err := parsePSOutput(output, procs)
if err != nil { if err != nil {