stats.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package client
  2. import (
  3. "fmt"
  4. "io"
  5. "strings"
  6. "sync"
  7. "text/tabwriter"
  8. "time"
  9. "golang.org/x/net/context"
  10. Cli "github.com/docker/docker/cli"
  11. "github.com/docker/engine-api/types"
  12. "github.com/docker/engine-api/types/events"
  13. "github.com/docker/engine-api/types/filters"
  14. )
  15. // CmdStats displays a live stream of resource usage statistics for one or more containers.
  16. //
  17. // This shows real-time information on CPU usage, memory usage, and network I/O.
  18. //
  19. // Usage: docker stats [OPTIONS] [CONTAINER...]
  20. func (cli *DockerCli) CmdStats(args ...string) error {
  21. cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
  22. all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
  23. noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
  24. cmd.ParseFlags(args, true)
  25. names := cmd.Args()
  26. showAll := len(names) == 0
  27. closeChan := make(chan error)
  28. // monitorContainerEvents watches for container creation and removal (only
  29. // used when calling `docker stats` without arguments).
  30. monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
  31. f := filters.NewArgs()
  32. f.Add("type", "container")
  33. options := types.EventsOptions{
  34. Filters: f,
  35. }
  36. resBody, err := cli.client.Events(context.Background(), options)
  37. // Whether we successfully subscribed to events or not, we can now
  38. // unblock the main goroutine.
  39. close(started)
  40. if err != nil {
  41. closeChan <- err
  42. return
  43. }
  44. defer resBody.Close()
  45. decodeEvents(resBody, func(event events.Message, err error) error {
  46. if err != nil {
  47. closeChan <- err
  48. return nil
  49. }
  50. c <- event
  51. return nil
  52. })
  53. }
  54. // waitFirst is a WaitGroup to wait first stat data's reach for each container
  55. waitFirst := &sync.WaitGroup{}
  56. cStats := stats{}
  57. // getContainerList simulates creation event for all previously existing
  58. // containers (only used when calling `docker stats` without arguments).
  59. getContainerList := func() {
  60. options := types.ContainerListOptions{
  61. All: *all,
  62. }
  63. cs, err := cli.client.ContainerList(options)
  64. if err != nil {
  65. closeChan <- err
  66. }
  67. for _, container := range cs {
  68. s := &containerStats{Name: container.ID[:12]}
  69. if cStats.add(s) {
  70. waitFirst.Add(1)
  71. go s.Collect(cli.client, !*noStream, waitFirst)
  72. }
  73. }
  74. }
  75. if showAll {
  76. // If no names were specified, start a long running goroutine which
  77. // monitors container events. We make sure we're subscribed before
  78. // retrieving the list of running containers to avoid a race where we
  79. // would "miss" a creation.
  80. started := make(chan struct{})
  81. eh := eventHandler{handlers: make(map[string]func(events.Message))}
  82. eh.Handle("create", func(e events.Message) {
  83. if *all {
  84. s := &containerStats{Name: e.ID[:12]}
  85. if cStats.add(s) {
  86. waitFirst.Add(1)
  87. go s.Collect(cli.client, !*noStream, waitFirst)
  88. }
  89. }
  90. })
  91. eh.Handle("start", func(e events.Message) {
  92. s := &containerStats{Name: e.ID[:12]}
  93. if cStats.add(s) {
  94. waitFirst.Add(1)
  95. go s.Collect(cli.client, !*noStream, waitFirst)
  96. }
  97. })
  98. eh.Handle("die", func(e events.Message) {
  99. if !*all {
  100. cStats.remove(e.ID[:12])
  101. }
  102. })
  103. eventChan := make(chan events.Message)
  104. go eh.Watch(eventChan)
  105. go monitorContainerEvents(started, eventChan)
  106. defer close(eventChan)
  107. <-started
  108. // Start a short-lived goroutine to retrieve the initial list of
  109. // containers.
  110. getContainerList()
  111. } else {
  112. // Artificially send creation events for the containers we were asked to
  113. // monitor (same code path than we use when monitoring all containers).
  114. for _, name := range names {
  115. s := &containerStats{Name: name}
  116. if cStats.add(s) {
  117. waitFirst.Add(1)
  118. go s.Collect(cli.client, !*noStream, waitFirst)
  119. }
  120. }
  121. // We don't expect any asynchronous errors: closeChan can be closed.
  122. close(closeChan)
  123. // Do a quick pause to detect any error with the provided list of
  124. // container names.
  125. time.Sleep(1500 * time.Millisecond)
  126. var errs []string
  127. cStats.mu.Lock()
  128. for _, c := range cStats.cs {
  129. c.mu.Lock()
  130. if c.err != nil {
  131. errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
  132. }
  133. c.mu.Unlock()
  134. }
  135. cStats.mu.Unlock()
  136. if len(errs) > 0 {
  137. return fmt.Errorf("%s", strings.Join(errs, ", "))
  138. }
  139. }
  140. // before print to screen, make sure each container get at least one valid stat data
  141. waitFirst.Wait()
  142. w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
  143. printHeader := func() {
  144. if !*noStream {
  145. fmt.Fprint(cli.out, "\033[2J")
  146. fmt.Fprint(cli.out, "\033[H")
  147. }
  148. io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
  149. }
  150. for range time.Tick(500 * time.Millisecond) {
  151. printHeader()
  152. toRemove := []int{}
  153. cStats.mu.Lock()
  154. for i, s := range cStats.cs {
  155. if err := s.Display(w); err != nil && !*noStream {
  156. toRemove = append(toRemove, i)
  157. }
  158. }
  159. for j := len(toRemove) - 1; j >= 0; j-- {
  160. i := toRemove[j]
  161. cStats.cs = append(cStats.cs[:i], cStats.cs[i+1:]...)
  162. }
  163. if len(cStats.cs) == 0 && !showAll {
  164. return nil
  165. }
  166. cStats.mu.Unlock()
  167. w.Flush()
  168. if *noStream {
  169. break
  170. }
  171. select {
  172. case err, ok := <-closeChan:
  173. if ok {
  174. if err != nil {
  175. // this is suppressing "unexpected EOF" in the cli when the
  176. // daemon restarts so it shutdowns cleanly
  177. if err == io.ErrUnexpectedEOF {
  178. return nil
  179. }
  180. return err
  181. }
  182. }
  183. default:
  184. // just skip
  185. }
  186. }
  187. return nil
  188. }