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