stats_helpers.go 5.5 KB

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