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