github.com/portworx/docker@v1.12.1/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 if err == io.EOF { 101 break 102 } 103 time.Sleep(100 * time.Millisecond) 104 continue 105 } 106 107 var memPercent = 0.0 108 var cpuPercent = 0.0 109 110 // MemoryStats.Limit will never be 0 unless the container is not running and we haven't 111 // got any data from cgroup 112 if v.MemoryStats.Limit != 0 { 113 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 114 } 115 116 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 117 previousSystem = v.PreCPUStats.SystemUsage 118 cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) 119 blkRead, blkWrite := calculateBlockIO(v.BlkioStats) 120 s.mu.Lock() 121 s.CPUPercentage = cpuPercent 122 s.Memory = float64(v.MemoryStats.Usage) 123 s.MemoryLimit = float64(v.MemoryStats.Limit) 124 s.MemoryPercentage = memPercent 125 s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) 126 s.BlockRead = float64(blkRead) 127 s.BlockWrite = float64(blkWrite) 128 s.PidsCurrent = v.PidsStats.Current 129 s.mu.Unlock() 130 u <- nil 131 if !streamStats { 132 return 133 } 134 } 135 }() 136 for { 137 select { 138 case <-time.After(2 * time.Second): 139 // zero out the values if we have not received an update within 140 // the specified duration. 141 s.mu.Lock() 142 s.CPUPercentage = 0 143 s.Memory = 0 144 s.MemoryPercentage = 0 145 s.MemoryLimit = 0 146 s.NetworkRx = 0 147 s.NetworkTx = 0 148 s.BlockRead = 0 149 s.BlockWrite = 0 150 s.PidsCurrent = 0 151 s.err = errors.New("timeout waiting for stats") 152 s.mu.Unlock() 153 // if this is the first stat you get, release WaitGroup 154 if !getFirst { 155 getFirst = true 156 waitFirst.Done() 157 } 158 case err := <-u: 159 if err != nil { 160 s.mu.Lock() 161 s.err = err 162 s.mu.Unlock() 163 continue 164 } 165 s.err = nil 166 // if this is the first stat you get, release WaitGroup 167 if !getFirst { 168 getFirst = true 169 waitFirst.Done() 170 } 171 } 172 if !streamStats { 173 return 174 } 175 } 176 } 177 178 func (s *containerStats) Display(w io.Writer) error { 179 s.mu.Lock() 180 defer s.mu.Unlock() 181 // NOTE: if you change this format, you must also change the err format below! 182 format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n" 183 if s.err != nil { 184 format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n" 185 errStr := "--" 186 fmt.Fprintf(w, format, 187 s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, 188 ) 189 err := s.err 190 return err 191 } 192 fmt.Fprintf(w, format, 193 s.Name, 194 s.CPUPercentage, 195 units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), 196 s.MemoryPercentage, 197 units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), 198 units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite), 199 s.PidsCurrent) 200 return nil 201 } 202 203 func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { 204 var ( 205 cpuPercent = 0.0 206 // calculate the change for the cpu usage of the container in between readings 207 cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) 208 // calculate the change for the entire system between readings 209 systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) 210 ) 211 212 if systemDelta > 0.0 && cpuDelta > 0.0 { 213 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 214 } 215 return cpuPercent 216 } 217 218 func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) { 219 for _, bioEntry := range blkio.IoServiceBytesRecursive { 220 switch strings.ToLower(bioEntry.Op) { 221 case "read": 222 blkRead = blkRead + bioEntry.Value 223 case "write": 224 blkWrite = blkWrite + bioEntry.Value 225 } 226 } 227 return 228 } 229 230 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 231 var rx, tx float64 232 233 for _, v := range network { 234 rx += float64(v.RxBytes) 235 tx += float64(v.TxBytes) 236 } 237 return rx, tx 238 }