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