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