stats.go 6.1 KB

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