github.com/a4a881d4/docker@v1.9.0-rc2/api/client/stats.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/url" 8 "sort" 9 "strings" 10 "sync" 11 "text/tabwriter" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 Cli "github.com/docker/docker/cli" 16 flag "github.com/docker/docker/pkg/mflag" 17 "github.com/docker/docker/pkg/units" 18 ) 19 20 type containerStats struct { 21 Name string 22 CPUPercentage float64 23 Memory float64 24 MemoryLimit float64 25 MemoryPercentage float64 26 NetworkRx float64 27 NetworkTx float64 28 BlockRead float64 29 BlockWrite float64 30 mu sync.RWMutex 31 err error 32 } 33 34 func (s *containerStats) Collect(cli *DockerCli, streamStats bool) { 35 v := url.Values{} 36 if streamStats { 37 v.Set("stream", "1") 38 } else { 39 v.Set("stream", "0") 40 } 41 serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil) 42 if err != nil { 43 s.mu.Lock() 44 s.err = err 45 s.mu.Unlock() 46 return 47 } 48 49 defer serverResp.body.Close() 50 51 var ( 52 previousCPU uint64 53 previousSystem uint64 54 dec = json.NewDecoder(serverResp.body) 55 u = make(chan error, 1) 56 ) 57 go func() { 58 for { 59 var v *types.StatsJSON 60 if err := dec.Decode(&v); err != nil { 61 u <- err 62 return 63 } 64 65 var memPercent = 0.0 66 var cpuPercent = 0.0 67 68 // MemoryStats.Limit will never be 0 unless the container is not running and we havn't 69 // got any data from cgroup 70 if v.MemoryStats.Limit != 0 { 71 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 72 } 73 74 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 75 previousSystem = v.PreCPUStats.SystemUsage 76 cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) 77 blkRead, blkWrite := calculateBlockIO(v.BlkioStats) 78 s.mu.Lock() 79 s.CPUPercentage = cpuPercent 80 s.Memory = float64(v.MemoryStats.Usage) 81 s.MemoryLimit = float64(v.MemoryStats.Limit) 82 s.MemoryPercentage = memPercent 83 s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) 84 s.BlockRead = float64(blkRead) 85 s.BlockWrite = float64(blkWrite) 86 s.mu.Unlock() 87 u <- nil 88 if !streamStats { 89 return 90 } 91 } 92 }() 93 for { 94 select { 95 case <-time.After(2 * time.Second): 96 // zero out the values if we have not received an update within 97 // the specified duration. 98 s.mu.Lock() 99 s.CPUPercentage = 0 100 s.Memory = 0 101 s.MemoryPercentage = 0 102 s.MemoryLimit = 0 103 s.NetworkRx = 0 104 s.NetworkTx = 0 105 s.BlockRead = 0 106 s.BlockWrite = 0 107 s.mu.Unlock() 108 case err := <-u: 109 if err != nil { 110 s.mu.Lock() 111 s.err = err 112 s.mu.Unlock() 113 return 114 } 115 } 116 if !streamStats { 117 return 118 } 119 } 120 } 121 122 func (s *containerStats) Display(w io.Writer) error { 123 s.mu.RLock() 124 defer s.mu.RUnlock() 125 if s.err != nil { 126 return s.err 127 } 128 fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n", 129 s.Name, 130 s.CPUPercentage, 131 units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), 132 s.MemoryPercentage, 133 units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), 134 units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite)) 135 return nil 136 } 137 138 // CmdStats displays a live stream of resource usage statistics for one or more containers. 139 // 140 // This shows real-time information on CPU usage, memory usage, and network I/O. 141 // 142 // Usage: docker stats CONTAINER [CONTAINER...] 143 func (cli *DockerCli) CmdStats(args ...string) error { 144 cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true) 145 noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result") 146 cmd.Require(flag.Min, 1) 147 148 cmd.ParseFlags(args, true) 149 150 names := cmd.Args() 151 sort.Strings(names) 152 var ( 153 cStats []*containerStats 154 w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 155 ) 156 printHeader := func() { 157 if !*noStream { 158 fmt.Fprint(cli.out, "\033[2J") 159 fmt.Fprint(cli.out, "\033[H") 160 } 161 io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n") 162 } 163 for _, n := range names { 164 s := &containerStats{Name: n} 165 cStats = append(cStats, s) 166 go s.Collect(cli, !*noStream) 167 } 168 // do a quick pause so that any failed connections for containers that do not exist are able to be 169 // evicted before we display the initial or default values. 170 time.Sleep(1500 * time.Millisecond) 171 var errs []string 172 for _, c := range cStats { 173 c.mu.Lock() 174 if c.err != nil { 175 errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err)) 176 } 177 c.mu.Unlock() 178 } 179 if len(errs) > 0 { 180 return fmt.Errorf("%s", strings.Join(errs, ", ")) 181 } 182 for range time.Tick(500 * time.Millisecond) { 183 printHeader() 184 toRemove := []int{} 185 for i, s := range cStats { 186 if err := s.Display(w); err != nil && !*noStream { 187 toRemove = append(toRemove, i) 188 } 189 } 190 for j := len(toRemove) - 1; j >= 0; j-- { 191 i := toRemove[j] 192 cStats = append(cStats[:i], cStats[i+1:]...) 193 } 194 if len(cStats) == 0 { 195 return nil 196 } 197 w.Flush() 198 if *noStream { 199 break 200 } 201 } 202 return nil 203 } 204 205 func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { 206 var ( 207 cpuPercent = 0.0 208 // calculate the change for the cpu usage of the container in between readings 209 cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU) 210 // calculate the change for the entire system between readings 211 systemDelta = float64(v.CPUStats.SystemUsage - previousSystem) 212 ) 213 214 if systemDelta > 0.0 && cpuDelta > 0.0 { 215 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 216 } 217 return cpuPercent 218 } 219 220 func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) { 221 for _, bioEntry := range blkio.IoServiceBytesRecursive { 222 switch strings.ToLower(bioEntry.Op) { 223 case "read": 224 blkRead = blkRead + bioEntry.Value 225 case "write": 226 blkWrite = blkWrite + bioEntry.Value 227 } 228 } 229 return 230 } 231 232 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 233 var rx, tx float64 234 235 for _, v := range network { 236 rx += float64(v.RxBytes) 237 tx += float64(v.TxBytes) 238 } 239 return rx, tx 240 }