stats.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package container
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/events"
  11. "github.com/docker/docker/api/types/filters"
  12. "github.com/docker/docker/cli"
  13. "github.com/docker/docker/cli/command"
  14. "github.com/docker/docker/cli/command/formatter"
  15. "github.com/spf13/cobra"
  16. "golang.org/x/net/context"
  17. )
  18. type statsOptions struct {
  19. all bool
  20. noStream bool
  21. format string
  22. containers []string
  23. }
  24. // NewStatsCommand creates a new cobra.Command for `docker stats`
  25. func NewStatsCommand(dockerCli *command.DockerCli) *cobra.Command {
  26. var opts statsOptions
  27. cmd := &cobra.Command{
  28. Use: "stats [OPTIONS] [CONTAINER...]",
  29. Short: "Display a live stream of container(s) resource usage statistics",
  30. Args: cli.RequiresMinArgs(0),
  31. RunE: func(cmd *cobra.Command, args []string) error {
  32. opts.containers = args
  33. return runStats(dockerCli, &opts)
  34. },
  35. }
  36. flags := cmd.Flags()
  37. flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
  38. flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
  39. flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
  40. return cmd
  41. }
  42. // runStats displays a live stream of resource usage statistics for one or more containers.
  43. // This shows real-time information on CPU usage, memory usage, and network I/O.
  44. func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
  45. showAll := len(opts.containers) == 0
  46. closeChan := make(chan error)
  47. ctx := context.Background()
  48. // monitorContainerEvents watches for container creation and removal (only
  49. // used when calling `docker stats` without arguments).
  50. monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
  51. f := filters.NewArgs()
  52. f.Add("type", "container")
  53. options := types.EventsOptions{
  54. Filters: f,
  55. }
  56. eventq, errq := dockerCli.Client().Events(ctx, options)
  57. // Whether we successfully subscribed to eventq or not, we can now
  58. // unblock the main goroutine.
  59. close(started)
  60. for {
  61. select {
  62. case event := <-eventq:
  63. c <- event
  64. case err := <-errq:
  65. closeChan <- err
  66. return
  67. }
  68. }
  69. }
  70. // Get the daemonOSType if not set already
  71. if daemonOSType == "" {
  72. svctx := context.Background()
  73. sv, err := dockerCli.Client().ServerVersion(svctx)
  74. if err != nil {
  75. return err
  76. }
  77. daemonOSType = sv.Os
  78. }
  79. // waitFirst is a WaitGroup to wait first stat data's reach for each container
  80. waitFirst := &sync.WaitGroup{}
  81. cStats := stats{}
  82. // getContainerList simulates creation event for all previously existing
  83. // containers (only used when calling `docker stats` without arguments).
  84. getContainerList := func() {
  85. options := types.ContainerListOptions{
  86. All: opts.all,
  87. }
  88. cs, err := dockerCli.Client().ContainerList(ctx, options)
  89. if err != nil {
  90. closeChan <- err
  91. }
  92. for _, container := range cs {
  93. s := formatter.NewContainerStats(container.ID[:12], daemonOSType)
  94. if cStats.add(s) {
  95. waitFirst.Add(1)
  96. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  97. }
  98. }
  99. }
  100. if showAll {
  101. // If no names were specified, start a long running goroutine which
  102. // monitors container events. We make sure we're subscribed before
  103. // retrieving the list of running containers to avoid a race where we
  104. // would "miss" a creation.
  105. started := make(chan struct{})
  106. eh := command.InitEventHandler()
  107. eh.Handle("create", func(e events.Message) {
  108. if opts.all {
  109. s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
  110. if cStats.add(s) {
  111. waitFirst.Add(1)
  112. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  113. }
  114. }
  115. })
  116. eh.Handle("start", func(e events.Message) {
  117. s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
  118. if cStats.add(s) {
  119. waitFirst.Add(1)
  120. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  121. }
  122. })
  123. eh.Handle("die", func(e events.Message) {
  124. if !opts.all {
  125. cStats.remove(e.ID[:12])
  126. }
  127. })
  128. eventChan := make(chan events.Message)
  129. go eh.Watch(eventChan)
  130. go monitorContainerEvents(started, eventChan)
  131. defer close(eventChan)
  132. <-started
  133. // Start a short-lived goroutine to retrieve the initial list of
  134. // containers.
  135. getContainerList()
  136. } else {
  137. // Artificially send creation events for the containers we were asked to
  138. // monitor (same code path than we use when monitoring all containers).
  139. for _, name := range opts.containers {
  140. s := formatter.NewContainerStats(name, daemonOSType)
  141. if cStats.add(s) {
  142. waitFirst.Add(1)
  143. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  144. }
  145. }
  146. // We don't expect any asynchronous errors: closeChan can be closed.
  147. close(closeChan)
  148. // Do a quick pause to detect any error with the provided list of
  149. // container names.
  150. time.Sleep(1500 * time.Millisecond)
  151. var errs []string
  152. cStats.mu.Lock()
  153. for _, c := range cStats.cs {
  154. if err := c.GetError(); err != nil {
  155. errs = append(errs, err.Error())
  156. }
  157. }
  158. cStats.mu.Unlock()
  159. if len(errs) > 0 {
  160. return errors.New(strings.Join(errs, "\n"))
  161. }
  162. }
  163. // before print to screen, make sure each container get at least one valid stat data
  164. waitFirst.Wait()
  165. format := opts.format
  166. if len(format) == 0 {
  167. if len(dockerCli.ConfigFile().StatsFormat) > 0 {
  168. format = dockerCli.ConfigFile().StatsFormat
  169. } else {
  170. format = formatter.TableFormatKey
  171. }
  172. }
  173. statsCtx := formatter.Context{
  174. Output: dockerCli.Out(),
  175. Format: formatter.NewStatsFormat(format, daemonOSType),
  176. }
  177. cleanScreen := func() {
  178. if !opts.noStream {
  179. fmt.Fprint(dockerCli.Out(), "\033[2J")
  180. fmt.Fprint(dockerCli.Out(), "\033[H")
  181. }
  182. }
  183. var err error
  184. for range time.Tick(500 * time.Millisecond) {
  185. cleanScreen()
  186. ccstats := []formatter.StatsEntry{}
  187. cStats.mu.Lock()
  188. for _, c := range cStats.cs {
  189. ccstats = append(ccstats, c.GetStatistics())
  190. }
  191. cStats.mu.Unlock()
  192. if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType); err != nil {
  193. break
  194. }
  195. if len(cStats.cs) == 0 && !showAll {
  196. break
  197. }
  198. if opts.noStream {
  199. break
  200. }
  201. select {
  202. case err, ok := <-closeChan:
  203. if ok {
  204. if err != nil {
  205. // this is suppressing "unexpected EOF" in the cli when the
  206. // daemon restarts so it shutdowns cleanly
  207. if err == io.ErrUnexpectedEOF {
  208. return nil
  209. }
  210. return err
  211. }
  212. }
  213. default:
  214. // just skip
  215. }
  216. }
  217. return err
  218. }