stats_helpers.go 4.9 KB

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