mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Allow docker stats without arguments
This patch adds the ability to run `docker stats` w/o arguments and get statistics for all running containers by default. Also add a new `--all` flag to list statistics for all containers (like `docker ps`). New running containers are added to the list as they show up also. Add integration tests for this new behavior. Docs updated accordingly. Fix missing stuff in man/commandline reference for `docker stats`. Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
7a19164c17
commit
ae818a820f
6 changed files with 232 additions and 23 deletions
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
|
@ -31,6 +31,11 @@ type containerStats struct {
|
|||
err error
|
||||
}
|
||||
|
||||
type stats struct {
|
||||
mu sync.Mutex
|
||||
cs []*containerStats
|
||||
}
|
||||
|
||||
func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
||||
v := url.Values{}
|
||||
if streamStats {
|
||||
|
@ -139,18 +144,41 @@ func (s *containerStats) Display(w io.Writer) error {
|
|||
//
|
||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
||||
//
|
||||
// Usage: docker stats CONTAINER [CONTAINER...]
|
||||
// Usage: docker stats [OPTIONS] [CONTAINER...]
|
||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
|
||||
cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
|
||||
all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
||||
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
names := cmd.Args()
|
||||
showAll := len(names) == 0
|
||||
|
||||
if showAll {
|
||||
v := url.Values{}
|
||||
if *all {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cs []types.Container
|
||||
if err := json.Unmarshal(body, &cs); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range cs {
|
||||
names = append(names, c.ID[:12])
|
||||
}
|
||||
}
|
||||
if len(names) == 0 && !showAll {
|
||||
return fmt.Errorf("No containers found")
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
var (
|
||||
cStats []*containerStats
|
||||
cStats = stats{}
|
||||
w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
)
|
||||
printHeader := func() {
|
||||
|
@ -162,42 +190,125 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
|||
}
|
||||
for _, n := range names {
|
||||
s := &containerStats{Name: n}
|
||||
cStats = append(cStats, s)
|
||||
// no need to lock here since only the main goroutine is running here
|
||||
cStats.cs = append(cStats.cs, s)
|
||||
go s.Collect(cli, !*noStream)
|
||||
}
|
||||
closeChan := make(chan error)
|
||||
if showAll {
|
||||
type watch struct {
|
||||
cid string
|
||||
event string
|
||||
err error
|
||||
}
|
||||
getNewContainers := func(c chan<- watch) {
|
||||
res, err := cli.call("GET", "/events", nil, nil)
|
||||
if err != nil {
|
||||
c <- watch{err: err}
|
||||
return
|
||||
}
|
||||
defer res.body.Close()
|
||||
|
||||
dec := json.NewDecoder(res.body)
|
||||
for {
|
||||
var j *jsonmessage.JSONMessage
|
||||
if err := dec.Decode(&j); err != nil {
|
||||
c <- watch{err: err}
|
||||
return
|
||||
}
|
||||
c <- watch{j.ID[:12], j.Status, nil}
|
||||
}
|
||||
}
|
||||
go func(stopChan chan<- error) {
|
||||
cChan := make(chan watch)
|
||||
go getNewContainers(cChan)
|
||||
for {
|
||||
c := <-cChan
|
||||
if c.err != nil {
|
||||
stopChan <- c.err
|
||||
return
|
||||
}
|
||||
switch c.event {
|
||||
case "create":
|
||||
s := &containerStats{Name: c.cid}
|
||||
cStats.mu.Lock()
|
||||
cStats.cs = append(cStats.cs, s)
|
||||
cStats.mu.Unlock()
|
||||
go s.Collect(cli, !*noStream)
|
||||
case "stop":
|
||||
case "die":
|
||||
if !*all {
|
||||
var remove int
|
||||
// cStats cannot be O(1) with a map cause ranging over it would cause
|
||||
// containers in stats to move up and down in the list...:(
|
||||
cStats.mu.Lock()
|
||||
for i, s := range cStats.cs {
|
||||
if s.Name == c.cid {
|
||||
remove = i
|
||||
break
|
||||
}
|
||||
}
|
||||
cStats.cs = append(cStats.cs[:remove], cStats.cs[remove+1:]...)
|
||||
cStats.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}(closeChan)
|
||||
} else {
|
||||
close(closeChan)
|
||||
}
|
||||
// do a quick pause so that any failed connections for containers that do not exist are able to be
|
||||
// evicted before we display the initial or default values.
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
var errs []string
|
||||
for _, c := range cStats {
|
||||
cStats.mu.Lock()
|
||||
for _, c := range cStats.cs {
|
||||
c.mu.Lock()
|
||||
if c.err != nil {
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
cStats.mu.Unlock()
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(errs, ", "))
|
||||
}
|
||||
for range time.Tick(500 * time.Millisecond) {
|
||||
printHeader()
|
||||
toRemove := []int{}
|
||||
for i, s := range cStats {
|
||||
cStats.mu.Lock()
|
||||
for i, s := range cStats.cs {
|
||||
if err := s.Display(w); err != nil && !*noStream {
|
||||
toRemove = append(toRemove, i)
|
||||
}
|
||||
}
|
||||
for j := len(toRemove) - 1; j >= 0; j-- {
|
||||
i := toRemove[j]
|
||||
cStats = append(cStats[:i], cStats[i+1:]...)
|
||||
cStats.cs = append(cStats.cs[:i], cStats.cs[i+1:]...)
|
||||
}
|
||||
if len(cStats) == 0 {
|
||||
if len(cStats.cs) == 0 && !showAll {
|
||||
return nil
|
||||
}
|
||||
cStats.mu.Unlock()
|
||||
w.Flush()
|
||||
if *noStream {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case err, ok := <-closeChan:
|
||||
if ok {
|
||||
if err != nil {
|
||||
// this is suppressing "unexpected EOF" in the cli when the
|
||||
// daemon restarts so it shudowns cleanly
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
// just skip
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ type ContainerStatsConfig struct {
|
|||
// ContainerStats writes information about the container to the stream
|
||||
// given in the config object.
|
||||
func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStatsConfig) error {
|
||||
|
||||
container, err := daemon.Get(prefixOrName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -10,24 +10,31 @@ parent = "smn_cli"
|
|||
|
||||
# stats
|
||||
|
||||
Usage: docker stats [OPTIONS] CONTAINER [CONTAINER...]
|
||||
Usage: docker stats [OPTIONS] [CONTAINER...]
|
||||
|
||||
Display a live stream of one or more containers' resource usage statistics
|
||||
|
||||
-a, --all=false Show all containers (default shows just running)
|
||||
--help=false Print usage
|
||||
--no-stream=false Disable streaming stats and only pull the first result
|
||||
|
||||
Running `docker stats` on multiple containers
|
||||
The `docker stats` command returns a live data stream for running containers. To limit data to one or more specific containers, specify a list of container names or ids separated by a space. You can specify a stopped container but stopped containers do not return any data.
|
||||
|
||||
$ docker stats redis1 redis2
|
||||
If you want more detailed information about a container's resource usage, use the `/containers/(id)/stats` API endpoint.
|
||||
|
||||
## Examples
|
||||
|
||||
Running `docker stats` on all running containers
|
||||
|
||||
$ docker stats
|
||||
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
|
||||
redis1 0.07% 796 KB / 64 MB 1.21% 788 B / 648 B 3.568 MB / 512 KB
|
||||
redis2 0.07% 2.746 MB / 64 MB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B
|
||||
nginx1 0.03% 4.583 MB / 64 MB 6.30% 2.854 KB / 648 B 27.7 MB / 0 B
|
||||
|
||||
Running `docker stats` on multiple containers by name and id.
|
||||
|
||||
The `docker stats` command will only return a live stream of data for running
|
||||
containers. Stopped containers will not return any data.
|
||||
|
||||
> **Note:**
|
||||
> If you want more detailed information about a container's resource
|
||||
> usage, use the API endpoint.
|
||||
$ docker stats fervent_panini 5acfcb1b4fd1
|
||||
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
|
||||
5acfcb1b4fd1 0.00% 115.2 MB/1.045 GB 11.03% 1.422 kB/648 B
|
||||
fervent_panini 0.02% 11.08 MB/1.045 GB 1.06% 648 B/648 B
|
||||
|
|
|
@ -206,6 +206,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||
"login": "",
|
||||
"logout": "",
|
||||
"network": "",
|
||||
"stats": "",
|
||||
}
|
||||
|
||||
if _, ok := noShortUsage[cmd]; !ok {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -48,3 +50,80 @@ func (s *DockerSuite) TestStatsContainerNotFound(c *check.C) {
|
|||
c.Assert(err, checker.NotNil)
|
||||
c.Assert(out, checker.Contains, "no such id: notfound", check.Commentf("Expected to fail on not found container stats with --no-stream, got %q instead", out))
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestStatsAllRunningNoStream(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
|
||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
id1 := strings.TrimSpace(out)[:12]
|
||||
c.Assert(waitRun(id1), check.IsNil)
|
||||
out, _ = dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
id2 := strings.TrimSpace(out)[:12]
|
||||
c.Assert(waitRun(id2), check.IsNil)
|
||||
out, _ = dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
id3 := strings.TrimSpace(out)[:12]
|
||||
c.Assert(waitRun(id3), check.IsNil)
|
||||
dockerCmd(c, "stop", id3)
|
||||
|
||||
out, _ = dockerCmd(c, "stats", "--no-stream")
|
||||
if !strings.Contains(out, id1) || !strings.Contains(out, id2) {
|
||||
c.Fatalf("Expected stats output to contain both %s and %s, got %s", id1, id2, out)
|
||||
}
|
||||
if strings.Contains(out, id3) {
|
||||
c.Fatalf("Did not expect %s in stats, got %s", id3, out)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestStatsAllNoStream(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
|
||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
id1 := strings.TrimSpace(out)[:12]
|
||||
c.Assert(waitRun(id1), check.IsNil)
|
||||
dockerCmd(c, "stop", id1)
|
||||
out, _ = dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
id2 := strings.TrimSpace(out)[:12]
|
||||
c.Assert(waitRun(id2), check.IsNil)
|
||||
|
||||
out, _ = dockerCmd(c, "stats", "--all", "--no-stream")
|
||||
if !strings.Contains(out, id1) || !strings.Contains(out, id2) {
|
||||
c.Fatalf("Expected stats output to contain both %s and %s, got %s", id1, id2, out)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestStatsAllNewContainersAdded(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
|
||||
id := make(chan string)
|
||||
addedChan := make(chan struct{})
|
||||
|
||||
dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
statsCmd := exec.Command(dockerBinary, "stats")
|
||||
stdout, err := statsCmd.StdoutPipe()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(statsCmd.Start(), check.IsNil)
|
||||
defer statsCmd.Process.Kill()
|
||||
|
||||
go func() {
|
||||
containerID := <-id
|
||||
matchID := regexp.MustCompile(containerID)
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
switch {
|
||||
case matchID.MatchString(scanner.Text()):
|
||||
close(addedChan)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
|
||||
id <- strings.TrimSpace(out)[:12]
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
c.Fatal("failed to observe new container created added to stats")
|
||||
case <-addedChan:
|
||||
// ignore, done
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,19 @@ docker-stats - Display a live stream of one or more containers' resource usage s
|
|||
|
||||
# SYNOPSIS
|
||||
**docker stats**
|
||||
[**-a**|**--all**[=*false*]]
|
||||
[**--help**]
|
||||
[**--no-stream**[=*false*]]
|
||||
CONTAINER [CONTAINER...]
|
||||
[CONTAINER...]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Display a live stream of one or more containers' resource usage statistics
|
||||
|
||||
# OPTIONS
|
||||
**-a**, **--all**=*true*|*false*
|
||||
Show all containers. Only running containers are shown by default. The default is *false*.
|
||||
|
||||
**--help**
|
||||
Print usage statement
|
||||
|
||||
|
@ -23,9 +27,17 @@ Display a live stream of one or more containers' resource usage statistics
|
|||
|
||||
# EXAMPLES
|
||||
|
||||
Run **docker stats** with multiple containers.
|
||||
Running `docker stats` on all running containers
|
||||
|
||||
$ docker stats redis1 redis2
|
||||
$ docker stats
|
||||
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
|
||||
redis1 0.07% 796 KB / 64 MB 1.21% 788 B / 648 B 3.568 MB / 512 KB
|
||||
redis2 0.07% 2.746 MB / 64 MB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B
|
||||
nginx1 0.03% 4.583 MB / 64 MB 6.30% 2.854 KB / 648 B 27.7 MB / 0 B
|
||||
|
||||
Running `docker stats` on multiple containers by name and id.
|
||||
|
||||
$ docker stats fervent_panini 5acfcb1b4fd1
|
||||
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
|
||||
5acfcb1b4fd1 0.00% 115.2 MB/1.045 GB 11.03% 1.422 kB/648 B
|
||||
fervent_panini 0.02% 11.08 MB/1.045 GB 1.06% 648 B/648 B
|
||||
|
|
Loading…
Reference in a new issue