stats.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package container
  2. import (
  3. "fmt"
  4. "io"
  5. "strings"
  6. "sync"
  7. "time"
  8. "golang.org/x/net/context"
  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. )
  17. type statsOptions struct {
  18. all bool
  19. noStream bool
  20. format string
  21. containers []string
  22. }
  23. // NewStatsCommand creates a new cobra.Command for `docker stats`
  24. func NewStatsCommand(dockerCli *command.DockerCli) *cobra.Command {
  25. var opts statsOptions
  26. cmd := &cobra.Command{
  27. Use: "stats [OPTIONS] [CONTAINER...]",
  28. Short: "Display a live stream of container(s) resource usage statistics",
  29. Args: cli.RequiresMinArgs(0),
  30. RunE: func(cmd *cobra.Command, args []string) error {
  31. opts.containers = args
  32. return runStats(dockerCli, &opts)
  33. },
  34. }
  35. flags := cmd.Flags()
  36. flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
  37. flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
  38. flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
  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 *command.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. eventq, errq := dockerCli.Client().Events(ctx, options)
  56. // Whether we successfully subscribed to eventq or not, we can now
  57. // unblock the main goroutine.
  58. close(started)
  59. for {
  60. select {
  61. case event := <-eventq:
  62. c <- event
  63. case err := <-errq:
  64. closeChan <- err
  65. return
  66. }
  67. }
  68. }
  69. // Get the daemonOSType if not set already
  70. if daemonOSType == "" {
  71. svctx := context.Background()
  72. sv, err := dockerCli.Client().ServerVersion(svctx)
  73. if err != nil {
  74. return err
  75. }
  76. daemonOSType = sv.Os
  77. }
  78. // waitFirst is a WaitGroup to wait first stat data's reach for each container
  79. waitFirst := &sync.WaitGroup{}
  80. cStats := stats{}
  81. // getContainerList simulates creation event for all previously existing
  82. // containers (only used when calling `docker stats` without arguments).
  83. getContainerList := func() {
  84. options := types.ContainerListOptions{
  85. All: opts.all,
  86. }
  87. cs, err := dockerCli.Client().ContainerList(ctx, options)
  88. if err != nil {
  89. closeChan <- err
  90. }
  91. for _, container := range cs {
  92. s := formatter.NewContainerStats(container.ID[:12], daemonOSType)
  93. if cStats.add(s) {
  94. waitFirst.Add(1)
  95. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  96. }
  97. }
  98. }
  99. if showAll {
  100. // If no names were specified, start a long running goroutine which
  101. // monitors container events. We make sure we're subscribed before
  102. // retrieving the list of running containers to avoid a race where we
  103. // would "miss" a creation.
  104. started := make(chan struct{})
  105. eh := command.InitEventHandler()
  106. eh.Handle("create", func(e events.Message) {
  107. if opts.all {
  108. s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
  109. if cStats.add(s) {
  110. waitFirst.Add(1)
  111. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  112. }
  113. }
  114. })
  115. eh.Handle("start", func(e events.Message) {
  116. s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
  117. if cStats.add(s) {
  118. waitFirst.Add(1)
  119. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  120. }
  121. })
  122. eh.Handle("die", func(e events.Message) {
  123. if !opts.all {
  124. cStats.remove(e.ID[:12])
  125. }
  126. })
  127. eventChan := make(chan events.Message)
  128. go eh.Watch(eventChan)
  129. go monitorContainerEvents(started, eventChan)
  130. defer close(eventChan)
  131. <-started
  132. // Start a short-lived goroutine to retrieve the initial list of
  133. // containers.
  134. getContainerList()
  135. } else {
  136. // Artificially send creation events for the containers we were asked to
  137. // monitor (same code path than we use when monitoring all containers).
  138. for _, name := range opts.containers {
  139. s := formatter.NewContainerStats(name, daemonOSType)
  140. if cStats.add(s) {
  141. waitFirst.Add(1)
  142. go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
  143. }
  144. }
  145. // We don't expect any asynchronous errors: closeChan can be closed.
  146. close(closeChan)
  147. // Do a quick pause to detect any error with the provided list of
  148. // container names.
  149. time.Sleep(1500 * time.Millisecond)
  150. var errs []string
  151. cStats.mu.Lock()
  152. for _, c := range cStats.cs {
  153. cErr := c.GetError()
  154. if cErr != nil {
  155. errs = append(errs, fmt.Sprintf("%s: %v", c.Name, cErr))
  156. }
  157. }
  158. cStats.mu.Unlock()
  159. if len(errs) > 0 {
  160. return fmt.Errorf("%s", strings.Join(errs, ", "))
  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); 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. }