github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/cli/command/container/stats.go (about) 1 package container 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/events" 13 "github.com/docker/docker/api/types/filters" 14 "github.com/docker/docker/cli" 15 "github.com/docker/docker/cli/command" 16 "github.com/docker/docker/cli/command/formatter" 17 "github.com/spf13/cobra" 18 "golang.org/x/net/context" 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 // Get the daemonOSType if not set already 84 if daemonOSType == "" { 85 svctx := context.Background() 86 sv, err := dockerCli.Client().ServerVersion(svctx) 87 if err != nil { 88 return err 89 } 90 daemonOSType = sv.Os 91 } 92 93 // waitFirst is a WaitGroup to wait first stat data's reach for each container 94 waitFirst := &sync.WaitGroup{} 95 96 cStats := stats{} 97 // getContainerList simulates creation event for all previously existing 98 // containers (only used when calling `docker stats` without arguments). 99 getContainerList := func() { 100 options := types.ContainerListOptions{ 101 All: opts.all, 102 } 103 cs, err := dockerCli.Client().ContainerList(ctx, options) 104 if err != nil { 105 closeChan <- err 106 } 107 for _, container := range cs { 108 s := formatter.NewContainerStats(container.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 116 if showAll { 117 // If no names were specified, start a long running goroutine which 118 // monitors container events. We make sure we're subscribed before 119 // retrieving the list of running containers to avoid a race where we 120 // would "miss" a creation. 121 started := make(chan struct{}) 122 eh := command.InitEventHandler() 123 eh.Handle("create", func(e events.Message) { 124 if opts.all { 125 s := formatter.NewContainerStats(e.ID[:12], daemonOSType) 126 if cStats.add(s) { 127 waitFirst.Add(1) 128 go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) 129 } 130 } 131 }) 132 133 eh.Handle("start", func(e events.Message) { 134 s := formatter.NewContainerStats(e.ID[:12], daemonOSType) 135 if cStats.add(s) { 136 waitFirst.Add(1) 137 go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) 138 } 139 }) 140 141 eh.Handle("die", func(e events.Message) { 142 if !opts.all { 143 cStats.remove(e.ID[:12]) 144 } 145 }) 146 147 eventChan := make(chan events.Message) 148 go eh.Watch(eventChan) 149 go monitorContainerEvents(started, eventChan) 150 defer close(eventChan) 151 <-started 152 153 // Start a short-lived goroutine to retrieve the initial list of 154 // containers. 155 getContainerList() 156 } else { 157 // Artificially send creation events for the containers we were asked to 158 // monitor (same code path than we use when monitoring all containers). 159 for _, name := range opts.containers { 160 s := formatter.NewContainerStats(name, daemonOSType) 161 if cStats.add(s) { 162 waitFirst.Add(1) 163 go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) 164 } 165 } 166 167 // We don't expect any asynchronous errors: closeChan can be closed. 168 close(closeChan) 169 170 // Do a quick pause to detect any error with the provided list of 171 // container names. 172 time.Sleep(1500 * time.Millisecond) 173 var errs []string 174 cStats.mu.Lock() 175 for _, c := range cStats.cs { 176 if err := c.GetError(); err != nil { 177 errs = append(errs, err.Error()) 178 } 179 } 180 cStats.mu.Unlock() 181 if len(errs) > 0 { 182 return errors.New(strings.Join(errs, "\n")) 183 } 184 } 185 186 // before print to screen, make sure each container get at least one valid stat data 187 waitFirst.Wait() 188 format := opts.format 189 if len(format) == 0 { 190 if len(dockerCli.ConfigFile().StatsFormat) > 0 { 191 format = dockerCli.ConfigFile().StatsFormat 192 } else { 193 format = formatter.TableFormatKey 194 } 195 } 196 statsCtx := formatter.Context{ 197 Output: dockerCli.Out(), 198 Format: formatter.NewStatsFormat(format, daemonOSType), 199 } 200 cleanScreen := func() { 201 if !opts.noStream { 202 fmt.Fprint(dockerCli.Out(), "\033[2J") 203 fmt.Fprint(dockerCli.Out(), "\033[H") 204 } 205 } 206 207 var err error 208 for range time.Tick(500 * time.Millisecond) { 209 cleanScreen() 210 ccstats := []formatter.StatsEntry{} 211 cStats.mu.Lock() 212 for _, c := range cStats.cs { 213 ccstats = append(ccstats, c.GetStatistics()) 214 } 215 cStats.mu.Unlock() 216 if err = formatter.ContainerStatsWrite(statsCtx, ccstats); err != nil { 217 break 218 } 219 if len(cStats.cs) == 0 && !showAll { 220 break 221 } 222 if opts.noStream { 223 break 224 } 225 select { 226 case err, ok := <-closeChan: 227 if ok { 228 if err != nil { 229 // this is suppressing "unexpected EOF" in the cli when the 230 // daemon restarts so it shutdowns cleanly 231 if err == io.ErrUnexpectedEOF { 232 return nil 233 } 234 return err 235 } 236 } 237 default: 238 // just skip 239 } 240 } 241 return err 242 }