github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/api/client/stats_helpers.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/docker/engine-api/client" 12 "github.com/docker/engine-api/types" 13 "github.com/docker/go-units" 14 "golang.org/x/net/context" 15 ) 16 17 type containerStats struct { 18 Name string 19 CPUPercentage float64 20 Memory float64 21 MemoryLimit float64 22 MemoryPercentage float64 23 NetworkRx float64 24 NetworkTx float64 25 BlockRead float64 26 BlockWrite float64 27 PidsCurrent uint64 28 mu sync.RWMutex 29 err error 30 } 31 32 type stats struct { 33 mu sync.Mutex 34 cs []*containerStats 35 } 36 37 func (s *stats) add(cs *containerStats) bool { 38 s.mu.Lock() 39 defer s.mu.Unlock() 40 if _, exists := s.isKnownContainer(cs.Name); !exists { 41 s.cs = append(s.cs, cs) 42 return true 43 } 44 return false 45 } 46 47 func (s *stats) remove(id string) { 48 s.mu.Lock() 49 if i, exists := s.isKnownContainer(id); exists { 50 s.cs = append(s.cs[:i], s.cs[i+1:]...) 51 } 52 s.mu.Unlock() 53 } 54 55 func (s *stats) isKnownContainer(cid string) (int, bool) { 56 for i, c := range s.cs { 57 if c.Name == cid { 58 return i, true 59 } 60 } 61 return -1, false 62 } 63 64 func (s *containerStats) Collect(cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) { 65 var ( 66 getFirst bool 67 previousCPU uint64 68 previousSystem uint64 69 u = make(chan error, 1) 70 ) 71 72 defer func() { 73 // if error happens and we get nothing of stats, release wait group whatever 74 if !getFirst { 75 getFirst = true 76 waitFirst.Done() 77 } 78 }() 79 80 responseBody, err := cli.ContainerStats(context.Background(), s.Name, streamStats) 81 if err != nil { 82 s.mu.Lock() 83 s.err = err 84 s.mu.Unlock() 85 return 86 } 87 defer responseBody.Close() 88 89 dec := json.NewDecoder(responseBody) 90 go func() { 91 for { 92 var v *types.StatsJSON 93 if err := dec.Decode(&v); err != nil { 94 u <- err 95 return 96 } 97 98 var memPercent = 0.0 99 var cpuPercent = 0.0 100 101 // MemoryStats.Limit will never be 0 unless the container is not running and we haven't 102 // got any data from cgroup 103 if v.MemoryStats.Limit != 0 { 104 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 105 } 106 107 previousCPU = v.PreCPUStats.CPUUsage.TotalUsage 108 previousSystem = v.PreCPUStats.SystemUsage 109 cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) 110 blkRead, blkWrite := calculateBlockIO(v.BlkioStats) 111 s.mu.Lock() 112 s.CPUPercentage = cpuPercent 113 s.Memory = float64(v.MemoryStats.Usage) 114 s.MemoryLimit = float64(v.MemoryStats.Limit) 115 s.MemoryPercentage = memPercent 116 s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) 117 s.BlockRead = float64(blkRead) 118 s.BlockWrite = float64(blkWrite) 119 s.PidsCurrent = v.PidsStats.Current 120 s.mu.Unlock() 121 u <- nil 122 if !streamStats { 123 return 124 } 125 } 126 }() 127 for { 128 select { 129 case <-time.After(2 * time.Second): 130 // zero out the values if we have not received an update within 131 // the specified duration. 132 s.mu.Lock() 133 s.CPUPercentage = 0 134 s.Memory = 0 135 s.MemoryPercentage = 0 136 s.MemoryLimit = 0 137 s.NetworkRx = 0 138 s.NetworkTx = 0 139 s.BlockRead = 0 140 s.BlockWrite = 0 141 s.PidsCurrent = 0 142 s.mu.Unlock() 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 if err != nil { 150 s.mu.Lock() 151 s.err = err 152 s.mu.Unlock() 153 return 154 } 155 // if this is the first stat you get, release WaitGroup 156 if !getFirst { 157 getFirst = true 158 waitFirst.Done() 159 } 160 } 161 if !streamStats { 162 return 163 } 164 } 165 } 166 167 func (s *containerStats) Display(w io.Writer) error { 168 s.mu.RLock() 169 defer s.mu.RUnlock() 170 if s.err != nil { 171 return s.err 172 } 173 fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n", 174 s.Name, 175 s.CPUPercentage, 176 units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), 177 s.MemoryPercentage, 178 units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), 179 units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite), 180 s.PidsCurrent) 181 return nil 182 } 183 184 func calculateCPUPercent(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 calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) { 200 for _, bioEntry := range blkio.IoServiceBytesRecursive { 201 switch strings.ToLower(bioEntry.Op) { 202 case "read": 203 blkRead = blkRead + bioEntry.Value 204 case "write": 205 blkWrite = blkWrite + bioEntry.Value 206 } 207 } 208 return 209 } 210 211 func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { 212 var rx, tx float64 213 214 for _, v := range network { 215 rx += float64(v.RxBytes) 216 tx += float64(v.TxBytes) 217 } 218 return rx, tx 219 }