diff --git a/api/client/stats.go b/api/client/stats.go index b2dd36d683..332b78003b 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "net/url" "sort" "strings" "sync" @@ -27,8 +28,14 @@ type containerStats struct { err error } -func (s *containerStats) Collect(cli *DockerCli) { - stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, nil) +func (s *containerStats) Collect(cli *DockerCli, streamStats bool) { + v := url.Values{} + if streamStats { + v.Set("stream", "1") + } else { + v.Set("stream", "0") + } + stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil) if err != nil { s.err = err return @@ -67,6 +74,9 @@ func (s *containerStats) Collect(cli *DockerCli) { previousCPU = v.CpuStats.CpuUsage.TotalUsage previousSystem = v.CpuStats.SystemUsage u <- nil + if !streamStats { + return + } } }() for { @@ -87,6 +97,9 @@ func (s *containerStats) Collect(cli *DockerCli) { return } } + if !streamStats { + return + } } } @@ -112,6 +125,7 @@ func (s *containerStats) Display(w io.Writer) error { // Usage: docker stats CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStats(args ...string) error { cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) + 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) @@ -122,14 +136,16 @@ func (cli *DockerCli) CmdStats(args ...string) error { w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) ) printHeader := func() { - io.WriteString(cli.out, "\033[2J") - io.WriteString(cli.out, "\033[H") + if !*noStream { + fmt.Fprint(cli.out, "\033[2J") + fmt.Fprint(cli.out, "\033[H") + } io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n") } for _, n := range names { s := &containerStats{Name: n} cStats = append(cStats, s) - go s.Collect(cli) + go s.Collect(cli, !*noStream) } // 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. @@ -149,7 +165,7 @@ func (cli *DockerCli) CmdStats(args ...string) error { printHeader() toRemove := []int{} for i, s := range cStats { - if err := s.Display(w); err != nil { + if err := s.Display(w); err != nil && !*noStream { toRemove = append(toRemove, i) } } @@ -161,6 +177,9 @@ func (cli *DockerCli) CmdStats(args ...string) error { return nil } w.Flush() + if *noStream { + break + } } return nil } diff --git a/api/server/server.go b/api/server/server.go index 61e8162659..2485561b9d 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -611,7 +611,7 @@ func (s *Server) getContainersStats(eng *engine.Engine, version version.Version, return fmt.Errorf("Missing parameter") } - return s.daemon.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) + return s.daemon.ContainerStats(vars["name"], boolValue(r, "stream"), utils.NewWriteFlusher(w)) } func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f3b8331587..f399de1774 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1003,7 +1003,7 @@ _docker_start() { _docker_stats() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--no-stream --help" -- "$cur" ) ) ;; *) __docker_containers_running diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index d3237588ef..39ab9e33b4 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -16,7 +16,7 @@ function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand' for i in (commandline -opc) - if contains -- $i attach build commit cp create diff events exec export history images import info inspect kill load login logout logs pause port ps pull push rename restart rm rmi run save search start stop tag top unpause version wait + if contains -- $i attach build commit cp create diff events exec export history images import info inspect kill load login logout logs pause port ps pull push rename restart rm rmi run save search start stop tag top unpause version wait stats return 1 end end @@ -361,6 +361,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from start' -a '(__fish_prin # stats complete -c docker -f -n '__fish_docker_no_subcommand' -a stats -d "Display a live stream of one or more containers' resource usage statistics" complete -c docker -A -f -n '__fish_seen_subcommand_from stats' -l help -d 'Print usage' +complete -c docker -A -f -n '__fish_seen_subcommand_from stats' -l no-stream -d 'Disable streaming stats and only pull the first result' complete -c docker -A -f -n '__fish_seen_subcommand_from stats' -a '(__fish_print_docker_containers running)' -d "Container" # stop diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 28398f7524..3cff8fbbb6 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -326,6 +326,7 @@ __docker_subcommand () { ;; (stats) _arguments \ + '--no-stream[Disable streaming stats and only pull the first result]' \ '*:containers:__docker_runningcontainers' ;; (rm) diff --git a/daemon/stats.go b/daemon/stats.go index a95168d128..c7da913223 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -10,7 +10,7 @@ import ( "github.com/docker/libcontainer/cgroups" ) -func (daemon *Daemon) ContainerStats(name string, out io.Writer) error { +func (daemon *Daemon) ContainerStats(name string, stream bool, out io.Writer) error { updates, err := daemon.SubscribeToContainerStats(name) if err != nil { return err @@ -27,6 +27,9 @@ func (daemon *Daemon) ContainerStats(name string, out io.Writer) error { daemon.UnsubscribeToContainerStats(name, updates) return err } + if !stream { + break + } } return nil } diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index f6fc3f7f23..4b48588559 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -17,6 +17,9 @@ Display a live stream of one or more containers' resource usage statistics **--help** Print usage statement +**--no-stream**="false" + Disable streaming stats and only pull the first result + # EXAMPLES Run **docker stats** with multiple containers. diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index d92084f29b..676f210ebc 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -46,6 +46,11 @@ You can still call an old version of the API using ### What's new +`GET /containers/(id)/stats` + +**New!** +You can now supply a `stream` bool to get only one set of stats and +disconnect ## v1.18 diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index cede2e1073..a214341d68 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -644,6 +644,10 @@ This endpoint returns a live stream of a container's resource usage statistics. } } +Query Parameters: + +- **stream** – 1/True/true or 0/False/false, pull stats once then disconnect. Default true + Status Codes: - **200** – no error diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ffa..159fa80862 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2377,6 +2377,7 @@ more details on finding shared images from the command line. Display a live stream of one or more containers' resource usage statistics --help=false Print usage + --no-stream=false Disable streaming stats and only pull the first result Running `docker stats` on multiple containers diff --git a/integration-cli/docker_cli_stats_test.go b/integration-cli/docker_cli_stats_test.go new file mode 100644 index 0000000000..7664de5977 --- /dev/null +++ b/integration-cli/docker_cli_stats_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "os/exec" + "strings" + "time" + + "github.com/go-check/check" +) + +func (s *DockerSuite) TestCliStatsNoStream(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "top")) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %s", err, out) + } + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + c.Fatalf("error waiting for container to start: %v", err) + } + + statsCmd := exec.Command(dockerBinary, "stats", "--no-stream", id) + chErr := make(chan error) + go func() { + chErr <- statsCmd.Run() + }() + + select { + case err := <-chErr: + if err != nil { + c.Fatalf("Error running stats: %v", err) + } + case <-time.After(2 * time.Second): + statsCmd.Process.Kill() + c.Fatalf("stats did not return immediately when not streaming") + } +}