github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/container/stats_helpers.go (about) 1 package container 2 3 import ( 4 "context" 5 "encoding/json" 6 "io" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/docker/cli/cli/command/formatter" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/client" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 ) 17 18 type stats struct { 19 mu sync.Mutex 20 cs []*formatter.ContainerStats 21 } 22 23 // daemonOSType is set once we have at least one stat for a container 24 // from the daemon. It is used to ensure we print the right header based 25 // on the daemon platform. 26 var daemonOSType string 27 28 func (s *stats) add(cs *formatter.ContainerStats) bool { 29 s.mu.Lock() 30 defer s.mu.Unlock() 31 if _, exists := s.isKnownContainer(cs.Container); !exists { 32 s.cs = append(s.cs, cs) 33 return true 34 } 35 return false 36 } 37 38 func (s *stats) remove(id string) { 39 s.mu.Lock() 40 if i, exists := s.isKnownContainer(id); exists { 41 s.cs = append(s.cs[:i], s.cs[i+1:]...) 42 } 43 s.mu.Unlock() 44 } 45 46 func (s *stats) isKnownContainer(cid string) (int, bool) { 47 for i, c := range s.cs { 48 if c.Container == cid { 49 return i, true 50 } 51 } 52 return -1, false 53 } 54 55 func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) { 56 logrus.Debugf("collecting stats for %s", s.Container) 57 var ( 58 getFirst bool 59 previousCPU uint64 60 previousSystem uint64 61 u = make(chan error, 1) 62 ) 63 64 defer func() { 65 // if error happens and we get nothing of stats, release wait group whatever 66 if !getFirst { 67 getFirst = true 68 waitFirst.Done() 69 } 70 }() 71 72 response, err := cli.ContainerStats(ctx, s.Container, streamStats) 73 if err != nil { 74 s.SetError(err) 75 return 76 } 77 defer response.Body.Close() 78 79 dec := json.NewDecoder(response.Body) 80 go func() { 81 for { 82 var ( 83 v *types.StatsJSON 84 memPercent, cpuPercent float64 85 blkRead, blkWrite uint64 // Only used on Linux 86 mem, memLimit float64 87 pidsStatsCurrent uint64 88 ) 89 90 if err := dec.Decode(&v); err != nil { 91 dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body)) 92 u <- err 93 if err == io.EOF { 94 break 95 } 96 time.Sleep(100 * time.Millisecond) 97 continue 98 } 99 100 daemonOSType = response.OSType 101 102 if daemonOSType != "windows" { 103 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 104 previousSystem = v.PreCPUStats.SystemUsage 105 cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v) 106 blkRead, blkWrite = calculateBlockIO(v.BlkioStats) 107 mem = calculateMemUsageUnixNoCache(v.MemoryStats) 108 memLimit = float64(v.MemoryStats.Limit) 109 memPercent = calculateMemPercentUnixNoCache(memLimit, mem) 110 pidsStatsCurrent = v.PidsStats.Current 111 } else { 112 cpuPercent = calculateCPUPercentWindows(v) 113 blkRead = v.StorageStats.ReadSizeBytes 114 blkWrite = v.StorageStats.WriteSizeBytes 115 mem = float64(v.MemoryStats.PrivateWorkingSet) 116 } 117 netRx, netTx := calculateNetwork(v.Networks) 118 s.SetStatistics(formatter.StatsEntry{ 119 Name: v.Name, 120 ID: v.ID, 121 CPUPercentage: cpuPercent, 122 Memory: mem, 123 MemoryPercentage: memPercent, 124 MemoryLimit: memLimit, 125 NetworkRx: netRx, 126 NetworkTx: netTx, 127 BlockRead: float64(blkRead), 128 BlockWrite: float64(blkWrite), 129 PidsCurrent: pidsStatsCurrent, 130 }) 131 u <- nil 132 if !streamStats { 133 return 134 } 135 } 136 }() 137 for { 138 select { 139 case <-time.After(2 * time.Second): 140 // zero out the values if we have not received an update within 141 // the specified duration. 142 s.SetErrorAndReset(errors.New("timeout waiting for stats")) 143 // if this is the first stat you get, release WaitGroup 144 if !getFirst { 145 getFirst = true 146 waitFirst.Done() 147 } 148 case err := <-u: 149 s.SetError(err) 150 if err == io.EOF { 151 break 152 } 153 if err != nil { 154 continue 155 } 156 // if this is the first stat you get, release WaitGroup 157 if !getFirst { 158 getFirst = true 159 waitFirst.Done() 160 } 161 } 162 if !streamStats { 163 return 164 } 165 } 166 } 167 168 func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { 169 var ( 170 cpuPercent = 0.0 171 // calculate the change for the cpu usage of the container in between readings 172 cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) 173 // calculate the change for the entire system between readings 174 systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) 175 onlineCPUs = float64(v.CPUStats.OnlineCPUs) 176 ) 177 178 if onlineCPUs == 0.0 { 179 onlineCPUs = float64(len(v.CPUStats.CPUUsage.PercpuUsage)) 180 } 181 if systemDelta > 0.0 && cpuDelta > 0.0 { 182 cpuPercent = (cpuDelta / systemDelta) * onlineCPUs * 100.0 183 } 184 return cpuPercent 185 } 186 187 func calculateCPUPercentWindows(v *types.StatsJSON) float64 { 188 // Max number of 100ns intervals between the previous time read and now 189 possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals 190 possIntervals /= 100 // Convert to number of 100ns intervals 191 possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors 192 193 // Intervals used 194 intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage 195 196 // Percentage avoiding divide-by-zero 197 if possIntervals > 0 { 198 return float64(intervalsUsed) / float64(possIntervals) * 100.0 199 } 200 return 0.00 201 } 202 203 func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) { 204 var blkRead, blkWrite uint64 205 for _, bioEntry := range blkio.IoServiceBytesRecursive { 206 switch strings.ToLower(bioEntry.Op) { 207 case "read": 208 blkRead = blkRead + bioEntry.Value 209 case "write": 210 blkWrite = blkWrite + bioEntry.Value 211 } 212 } 213 return blkRead, blkWrite 214 } 215 216 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 217 var rx, tx float64 218 219 for _, v := range network { 220 rx += float64(v.RxBytes) 221 tx += float64(v.TxBytes) 222 } 223 return rx, tx 224 } 225 226 // calculateMemUsageUnixNoCache calculate memory usage of the container. 227 // Page cache is intentionally excluded to avoid misinterpretation of the output. 228 func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 { 229 return float64(mem.Usage - mem.Stats["cache"]) 230 } 231 232 func calculateMemPercentUnixNoCache(limit float64, usedNoCache float64) float64 { 233 // MemoryStats.Limit will never be 0 unless the container is not running and we haven't 234 // got any data from cgroup 235 if limit != 0 { 236 return usedNoCache / limit * 100.0 237 } 238 return 0 239 }