github.com/kobeld/docker@v1.12.0-rc1/api/client/container/stats_helpers.go (about) 1 package container 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/engine-api/client" 14 "github.com/docker/engine-api/types" 15 "github.com/docker/go-units" 16 "golang.org/x/net/context" 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 PidsCurrent uint64 30 mu sync.Mutex 31 err error 32 } 33 34 type stats struct { 35 mu sync.Mutex 36 cs []*containerStats 37 } 38 39 func (s *stats) add(cs *containerStats) bool { 40 s.mu.Lock() 41 defer s.mu.Unlock() 42 if _, exists := s.isKnownContainer(cs.Name); !exists { 43 s.cs = append(s.cs, cs) 44 return true 45 } 46 return false 47 } 48 49 func (s *stats) remove(id string) { 50 s.mu.Lock() 51 if i, exists := s.isKnownContainer(id); exists { 52 s.cs = append(s.cs[:i], s.cs[i+1:]...) 53 } 54 s.mu.Unlock() 55 } 56 57 func (s *stats) isKnownContainer(cid string) (int, bool) { 58 for i, c := range s.cs { 59 if c.Name == cid { 60 return i, true 61 } 62 } 63 return -1, false 64 } 65 66 func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) { 67 logrus.Debugf("collecting stats for %s", s.Name) 68 var ( 69 getFirst bool 70 previousCPU uint64 71 previousSystem uint64 72 u = make(chan error, 1) 73 ) 74 75 defer func() { 76 // if error happens and we get nothing of stats, release wait group whatever 77 if !getFirst { 78 getFirst = true 79 waitFirst.Done() 80 } 81 }() 82 83 responseBody, err := cli.ContainerStats(ctx, s.Name, streamStats) 84 if err != nil { 85 s.mu.Lock() 86 s.err = err 87 s.mu.Unlock() 88 return 89 } 90 defer responseBody.Close() 91 92 dec := json.NewDecoder(responseBody) 93 go func() { 94 for { 95 var v *types.StatsJSON 96 97 if err := dec.Decode(&v); err != nil { 98 dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody)) 99 u <- err 100 continue 101 } 102 103 var memPercent = 0.0 104 var cpuPercent = 0.0 105 106 // MemoryStats.Limit will never be 0 unless the container is not running and we haven't 107 // got any data from cgroup 108 if v.MemoryStats.Limit != 0 { 109 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 110 } 111 112 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 113 previousSystem = v.PreCPUStats.SystemUsage 114 cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) 115 blkRead, blkWrite := calculateBlockIO(v.BlkioStats) 116 s.mu.Lock() 117 s.CPUPercentage = cpuPercent 118 s.Memory = float64(v.MemoryStats.Usage) 119 s.MemoryLimit = float64(v.MemoryStats.Limit) 120 s.MemoryPercentage = memPercent 121 s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) 122 s.BlockRead = float64(blkRead) 123 s.BlockWrite = float64(blkWrite) 124 s.PidsCurrent = v.PidsStats.Current 125 s.mu.Unlock() 126 u <- nil 127 if !streamStats { 128 return 129 } 130 } 131 }() 132 for { 133 select { 134 case <-time.After(2 * time.Second): 135 // zero out the values if we have not received an update within 136 // the specified duration. 137 s.mu.Lock() 138 s.CPUPercentage = 0 139 s.Memory = 0 140 s.MemoryPercentage = 0 141 s.MemoryLimit = 0 142 s.NetworkRx = 0 143 s.NetworkTx = 0 144 s.BlockRead = 0 145 s.BlockWrite = 0 146 s.PidsCurrent = 0 147 s.err = errors.New("timeout waiting for stats") 148 s.mu.Unlock() 149 // if this is the first stat you get, release WaitGroup 150 if !getFirst { 151 getFirst = true 152 waitFirst.Done() 153 } 154 case err := <-u: 155 if err != nil { 156 s.mu.Lock() 157 s.err = err 158 s.mu.Unlock() 159 continue 160 } 161 s.err = nil 162 // if this is the first stat you get, release WaitGroup 163 if !getFirst { 164 getFirst = true 165 waitFirst.Done() 166 } 167 } 168 if !streamStats { 169 return 170 } 171 } 172 } 173 174 func (s *containerStats) Display(w io.Writer) error { 175 s.mu.Lock() 176 defer s.mu.Unlock() 177 // NOTE: if you change this format, you must also change the err format below! 178 format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n" 179 if s.err != nil { 180 format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n" 181 errStr := "--" 182 fmt.Fprintf(w, format, 183 s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, 184 ) 185 err := s.err 186 return err 187 } 188 fmt.Fprintf(w, format, 189 s.Name, 190 s.CPUPercentage, 191 units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), 192 s.MemoryPercentage, 193 units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), 194 units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite), 195 s.PidsCurrent) 196 return nil 197 } 198 199 func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { 200 var ( 201 cpuPercent = 0.0 202 // calculate the change for the cpu usage of the container in between readings 203 cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) 204 // calculate the change for the entire system between readings 205 systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) 206 ) 207 208 if systemDelta > 0.0 && cpuDelta > 0.0 { 209 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 210 } 211 return cpuPercent 212 } 213 214 func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) { 215 for _, bioEntry := range blkio.IoServiceBytesRecursive { 216 switch strings.ToLower(bioEntry.Op) { 217 case "read": 218 blkRead = blkRead + bioEntry.Value 219 case "write": 220 blkWrite = blkWrite + bioEntry.Value 221 } 222 } 223 return 224 } 225 226 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 227 var rx, tx float64 228 229 for _, v := range network { 230 rx += float64(v.RxBytes) 231 tx += float64(v.TxBytes) 232 } 233 return rx, tx 234 }