mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Merge pull request #16742 from runcom/10772-docker-stats-all
Allow docker stats without arguments
This commit is contained in:
		
						commit
						fdc8cce070
					
				
					 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -178,6 +178,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…
	
	Add table
		Add a link
		
	
		Reference in a new issue