stats_helpers.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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.mu.Unlock()
  106. u <- nil
  107. if !streamStats {
  108. return
  109. }
  110. }
  111. }()
  112. for {
  113. select {
  114. case <-time.After(2 * time.Second):
  115. // zero out the values if we have not received an update within
  116. // the specified duration.
  117. s.mu.Lock()
  118. s.CPUPercentage = 0
  119. s.Memory = 0
  120. s.MemoryPercentage = 0
  121. s.MemoryLimit = 0
  122. s.NetworkRx = 0
  123. s.NetworkTx = 0
  124. s.BlockRead = 0
  125. s.BlockWrite = 0
  126. s.mu.Unlock()
  127. // if this is the first stat you get, release WaitGroup
  128. if !getFirst {
  129. getFirst = true
  130. waitFirst.Done()
  131. }
  132. case err := <-u:
  133. if err != nil {
  134. s.mu.Lock()
  135. s.err = err
  136. s.mu.Unlock()
  137. return
  138. }
  139. // if this is the first stat you get, release WaitGroup
  140. if !getFirst {
  141. getFirst = true
  142. waitFirst.Done()
  143. }
  144. }
  145. if !streamStats {
  146. return
  147. }
  148. }
  149. }
  150. func (s *containerStats) Display(w io.Writer) error {
  151. s.mu.RLock()
  152. defer s.mu.RUnlock()
  153. if s.err != nil {
  154. return s.err
  155. }
  156. fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n",
  157. s.Name,
  158. s.CPUPercentage,
  159. units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
  160. s.MemoryPercentage,
  161. units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
  162. units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite),
  163. s.PidsCurrent)
  164. return nil
  165. }
  166. func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
  167. var (
  168. cpuPercent = 0.0
  169. // calculate the change for the cpu usage of the container in between readings
  170. cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
  171. // calculate the change for the entire system between readings
  172. systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
  173. )
  174. if systemDelta > 0.0 && cpuDelta > 0.0 {
  175. cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
  176. }
  177. return cpuPercent
  178. }
  179. func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
  180. for _, bioEntry := range blkio.IoServiceBytesRecursive {
  181. switch strings.ToLower(bioEntry.Op) {
  182. case "read":
  183. blkRead = blkRead + bioEntry.Value
  184. case "write":
  185. blkWrite = blkWrite + bioEntry.Value
  186. }
  187. }
  188. return
  189. }
  190. func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
  191. var rx, tx float64
  192. for _, v := range network {
  193. rx += float64(v.RxBytes)
  194. tx += float64(v.TxBytes)
  195. }
  196. return rx, tx
  197. }