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