github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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.Mutex 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.Container); !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.Container == cid { 50 return i, true 51 } 52 } 53 return -1, false 54 } 55 56 func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) { 57 logrus.Debugf("collecting stats for %s", s.Container) 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.Container, streamStats) 74 if err != nil { 75 s.SetError(err) 76 return 77 } 78 defer response.Body.Close() 79 80 dec := json.NewDecoder(response.Body) 81 go func() { 82 for { 83 var ( 84 v *types.StatsJSON 85 memPercent, cpuPercent float64 86 blkRead, blkWrite uint64 // Only used on Linux 87 mem, memLimit, memPerc float64 88 pidsStatsCurrent uint64 89 ) 90 91 if err := dec.Decode(&v); err != nil { 92 dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body)) 93 u <- err 94 if err == io.EOF { 95 break 96 } 97 time.Sleep(100 * time.Millisecond) 98 continue 99 } 100 101 daemonOSType = response.OSType 102 103 if daemonOSType != "windows" { 104 // MemoryStats.Limit will never be 0 unless the container is not running and we haven't 105 // got any data from cgroup 106 if v.MemoryStats.Limit != 0 { 107 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 108 } 109 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 110 previousSystem = v.PreCPUStats.SystemUsage 111 cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v) 112 blkRead, blkWrite = calculateBlockIO(v.BlkioStats) 113 mem = float64(v.MemoryStats.Usage) 114 memLimit = float64(v.MemoryStats.Limit) 115 memPerc = memPercent 116 pidsStatsCurrent = v.PidsStats.Current 117 } else { 118 cpuPercent = calculateCPUPercentWindows(v) 119 blkRead = v.StorageStats.ReadSizeBytes 120 blkWrite = v.StorageStats.WriteSizeBytes 121 mem = float64(v.MemoryStats.PrivateWorkingSet) 122 } 123 netRx, netTx := calculateNetwork(v.Networks) 124 s.SetStatistics(formatter.StatsEntry{ 125 Name: v.Name, 126 ID: v.ID, 127 CPUPercentage: cpuPercent, 128 Memory: mem, 129 MemoryPercentage: memPerc, 130 MemoryLimit: memLimit, 131 NetworkRx: netRx, 132 NetworkTx: netTx, 133 BlockRead: float64(blkRead), 134 BlockWrite: float64(blkWrite), 135 PidsCurrent: pidsStatsCurrent, 136 }) 137 u <- nil 138 if !streamStats { 139 return 140 } 141 } 142 }() 143 for { 144 select { 145 case <-time.After(2 * time.Second): 146 // zero out the values if we have not received an update within 147 // the specified duration. 148 s.SetErrorAndReset(errors.New("timeout waiting for stats")) 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 s.SetError(err) 156 if err == io.EOF { 157 break 158 } 159 if err != nil { 160 continue 161 } 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 calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { 175 var ( 176 cpuPercent = 0.0 177 // calculate the change for the cpu usage of the container in between readings 178 cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) 179 // calculate the change for the entire system between readings 180 systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) 181 ) 182 183 if systemDelta > 0.0 && cpuDelta > 0.0 { 184 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 185 } 186 return cpuPercent 187 } 188 189 func calculateCPUPercentWindows(v *types.StatsJSON) float64 { 190 // Max number of 100ns intervals between the previous time read and now 191 possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals 192 possIntervals /= 100 // Convert to number of 100ns intervals 193 possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors 194 195 // Intervals used 196 intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage 197 198 // Percentage avoiding divide-by-zero 199 if possIntervals > 0 { 200 return float64(intervalsUsed) / float64(possIntervals) * 100.0 201 } 202 return 0.00 203 } 204 205 func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) { 206 for _, bioEntry := range blkio.IoServiceBytesRecursive { 207 switch strings.ToLower(bioEntry.Op) { 208 case "read": 209 blkRead = blkRead + bioEntry.Value 210 case "write": 211 blkWrite = blkWrite + bioEntry.Value 212 } 213 } 214 return 215 } 216 217 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 218 var rx, tx float64 219 220 for _, v := range network { 221 rx += float64(v.RxBytes) 222 tx += float64(v.TxBytes) 223 } 224 return rx, tx 225 }