stats_helpers.go 5.0 KB

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