github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/client/stats.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "sort" 8 "strings" 9 "sync" 10 "text/tabwriter" 11 "time" 12 13 "github.com/docker/docker/api/types" 14 Cli "github.com/docker/docker/cli" 15 "github.com/docker/docker/pkg/jsonmessage" 16 "github.com/docker/go-units" 17 ) 18 19 type containerStats struct { 20 Name string 21 CPUPercentage float64 22 Memory float64 23 MemoryLimit float64 24 MemoryPercentage float64 25 NetworkRx float64 26 NetworkTx float64 27 BlockRead float64 28 BlockWrite float64 29 mu sync.RWMutex 30 err error 31 } 32 33 type stats struct { 34 mu sync.Mutex 35 cs []*containerStats 36 } 37 38 func (s *containerStats) Collect(cli *DockerCli, streamStats bool) { 39 responseBody, err := cli.client.ContainerStats(s.Name, streamStats) 40 if err != nil { 41 s.mu.Lock() 42 s.err = err 43 s.mu.Unlock() 44 return 45 } 46 defer responseBody.Close() 47 48 var ( 49 previousCPU uint64 50 previousSystem uint64 51 dec = json.NewDecoder(responseBody) 52 u = make(chan error, 1) 53 ) 54 go func() { 55 for { 56 var v *types.StatsJSON 57 if err := dec.Decode(&v); err != nil { 58 u <- err 59 return 60 } 61 62 var memPercent = 0.0 63 var cpuPercent = 0.0 64 65 // MemoryStats.Limit will never be 0 unless the container is not running and we haven't 66 // got any data from cgroup 67 if v.MemoryStats.Limit != 0 { 68 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 69 } 70 71 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 72 previousSystem = v.PreCPUStats.SystemUsage 73 cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) 74 blkRead, blkWrite := calculateBlockIO(v.BlkioStats) 75 s.mu.Lock() 76 s.CPUPercentage = cpuPercent 77 s.Memory = float64(v.MemoryStats.Usage) 78 s.MemoryLimit = float64(v.MemoryStats.Limit) 79 s.MemoryPercentage = memPercent 80 s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) 81 s.BlockRead = float64(blkRead) 82 s.BlockWrite = float64(blkWrite) 83 s.mu.Unlock() 84 u <- nil 85 if !streamStats { 86 return 87 } 88 } 89 }() 90 for { 91 select { 92 case <-time.After(2 * time.Second): 93 // zero out the values if we have not received an update within 94 // the specified duration. 95 s.mu.Lock() 96 s.CPUPercentage = 0 97 s.Memory = 0 98 s.MemoryPercentage = 0 99 s.MemoryLimit = 0 100 s.NetworkRx = 0 101 s.NetworkTx = 0 102 s.BlockRead = 0 103 s.BlockWrite = 0 104 s.mu.Unlock() 105 case err := <-u: 106 if err != nil { 107 s.mu.Lock() 108 s.err = err 109 s.mu.Unlock() 110 return 111 } 112 } 113 if !streamStats { 114 return 115 } 116 } 117 } 118 119 func (s *containerStats) Display(w io.Writer) error { 120 s.mu.RLock() 121 defer s.mu.RUnlock() 122 if s.err != nil { 123 return s.err 124 } 125 fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n", 126 s.Name, 127 s.CPUPercentage, 128 units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), 129 s.MemoryPercentage, 130 units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), 131 units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite)) 132 return nil 133 } 134 135 // CmdStats displays a live stream of resource usage statistics for one or more containers. 136 // 137 // This shows real-time information on CPU usage, memory usage, and network I/O. 138 // 139 // Usage: docker stats [OPTIONS] [CONTAINER...] 140 func (cli *DockerCli) CmdStats(args ...string) error { 141 cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true) 142 all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)") 143 noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result") 144 145 cmd.ParseFlags(args, true) 146 147 names := cmd.Args() 148 showAll := len(names) == 0 149 150 if showAll { 151 options := types.ContainerListOptions{ 152 All: *all, 153 } 154 cs, err := cli.client.ContainerList(options) 155 if err != nil { 156 return err 157 } 158 for _, c := range cs { 159 names = append(names, c.ID[:12]) 160 } 161 } 162 if len(names) == 0 && !showAll { 163 return fmt.Errorf("No containers found") 164 } 165 sort.Strings(names) 166 167 var ( 168 cStats = stats{} 169 w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 170 ) 171 printHeader := func() { 172 if !*noStream { 173 fmt.Fprint(cli.out, "\033[2J") 174 fmt.Fprint(cli.out, "\033[H") 175 } 176 io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n") 177 } 178 for _, n := range names { 179 s := &containerStats{Name: n} 180 // no need to lock here since only the main goroutine is running here 181 cStats.cs = append(cStats.cs, s) 182 go s.Collect(cli, !*noStream) 183 } 184 closeChan := make(chan error) 185 if showAll { 186 type watch struct { 187 cid string 188 event string 189 err error 190 } 191 getNewContainers := func(c chan<- watch) { 192 options := types.EventsOptions{} 193 resBody, err := cli.client.Events(options) 194 if err != nil { 195 c <- watch{err: err} 196 return 197 } 198 defer resBody.Close() 199 200 dec := json.NewDecoder(resBody) 201 for { 202 var j *jsonmessage.JSONMessage 203 if err := dec.Decode(&j); err != nil { 204 c <- watch{err: err} 205 return 206 } 207 c <- watch{j.ID[:12], j.Status, nil} 208 } 209 } 210 go func(stopChan chan<- error) { 211 cChan := make(chan watch) 212 go getNewContainers(cChan) 213 for { 214 c := <-cChan 215 if c.err != nil { 216 stopChan <- c.err 217 return 218 } 219 switch c.event { 220 case "create": 221 s := &containerStats{Name: c.cid} 222 cStats.mu.Lock() 223 cStats.cs = append(cStats.cs, s) 224 cStats.mu.Unlock() 225 go s.Collect(cli, !*noStream) 226 case "stop": 227 case "die": 228 if !*all { 229 var remove int 230 // cStats cannot be O(1) with a map cause ranging over it would cause 231 // containers in stats to move up and down in the list...:( 232 cStats.mu.Lock() 233 for i, s := range cStats.cs { 234 if s.Name == c.cid { 235 remove = i 236 break 237 } 238 } 239 cStats.cs = append(cStats.cs[:remove], cStats.cs[remove+1:]...) 240 cStats.mu.Unlock() 241 } 242 } 243 } 244 }(closeChan) 245 } else { 246 close(closeChan) 247 } 248 // do a quick pause so that any failed connections for containers that do not exist are able to be 249 // evicted before we display the initial or default values. 250 time.Sleep(1500 * time.Millisecond) 251 var errs []string 252 cStats.mu.Lock() 253 for _, c := range cStats.cs { 254 c.mu.Lock() 255 if c.err != nil { 256 errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err)) 257 } 258 c.mu.Unlock() 259 } 260 cStats.mu.Unlock() 261 if len(errs) > 0 { 262 return fmt.Errorf("%s", strings.Join(errs, ", ")) 263 } 264 for range time.Tick(500 * time.Millisecond) { 265 printHeader() 266 toRemove := []int{} 267 cStats.mu.Lock() 268 for i, s := range cStats.cs { 269 if err := s.Display(w); err != nil && !*noStream { 270 toRemove = append(toRemove, i) 271 } 272 } 273 for j := len(toRemove) - 1; j >= 0; j-- { 274 i := toRemove[j] 275 cStats.cs = append(cStats.cs[:i], cStats.cs[i+1:]...) 276 } 277 if len(cStats.cs) == 0 && !showAll { 278 return nil 279 } 280 cStats.mu.Unlock() 281 w.Flush() 282 if *noStream { 283 break 284 } 285 select { 286 case err, ok := <-closeChan: 287 if ok { 288 if err != nil { 289 // this is suppressing "unexpected EOF" in the cli when the 290 // daemon restarts so it shutdowns cleanly 291 if err == io.ErrUnexpectedEOF { 292 return nil 293 } 294 return err 295 } 296 } 297 default: 298 // just skip 299 } 300 } 301 return nil 302 } 303 304 func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { 305 var ( 306 cpuPercent = 0.0 307 // calculate the change for the cpu usage of the container in between readings 308 cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) 309 // calculate the change for the entire system between readings 310 systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) 311 ) 312 313 if systemDelta > 0.0 && cpuDelta > 0.0 { 314 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 315 } 316 return cpuPercent 317 } 318 319 func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) { 320 for _, bioEntry := range blkio.IoServiceBytesRecursive { 321 switch strings.ToLower(bioEntry.Op) { 322 case "read": 323 blkRead = blkRead + bioEntry.Value 324 case "write": 325 blkWrite = blkWrite + bioEntry.Value 326 } 327 } 328 return 329 } 330 331 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 332 var rx, tx float64 333 334 for _, v := range network { 335 rx += float64(v.RxBytes) 336 tx += float64(v.TxBytes) 337 } 338 return rx, tx 339 }