stats_helpers.go 4.4 KB

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