stats.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package formatter
  2. import (
  3. "fmt"
  4. "sync"
  5. units "github.com/docker/go-units"
  6. )
  7. const (
  8. winOSType = "windows"
  9. defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
  10. winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{{.MemUsage}}\t{.NetIO}}\t{{.BlockIO}}"
  11. emptyStatsTableFormat = "Waiting for statistics..."
  12. containerHeader = "CONTAINER"
  13. cpuPercHeader = "CPU %"
  14. netIOHeader = "NET I/O"
  15. blockIOHeader = "BLOCK I/O"
  16. winMemPercHeader = "PRIV WORKING SET" // Used only on Window
  17. memPercHeader = "MEM %" // Used only on Linux
  18. memUseHeader = "MEM USAGE / LIMIT" // Used only on Linux
  19. pidsHeader = "PIDS" // Used only on Linux
  20. )
  21. // StatsEntry represents represents the statistics data collected from a container
  22. type StatsEntry struct {
  23. Name string
  24. CPUPercentage float64
  25. Memory float64 // On Windows this is the private working set
  26. MemoryLimit float64 // Not used on Windows
  27. MemoryPercentage float64 // Not used on Windows
  28. NetworkRx float64
  29. NetworkTx float64
  30. BlockRead float64
  31. BlockWrite float64
  32. PidsCurrent uint64 // Not used on Windows
  33. IsInvalid bool
  34. OSType string
  35. }
  36. // ContainerStats represents an entity to store containers statistics synchronously
  37. type ContainerStats struct {
  38. mutex sync.Mutex
  39. StatsEntry
  40. err error
  41. }
  42. // GetError returns the container statistics error.
  43. // This is used to determine whether the statistics are valid or not
  44. func (cs *ContainerStats) GetError() error {
  45. cs.mutex.Lock()
  46. defer cs.mutex.Unlock()
  47. return cs.err
  48. }
  49. // SetErrorAndReset zeroes all the container statistics and store the error.
  50. // It is used when receiving time out error during statistics collecting to reduce lock overhead
  51. func (cs *ContainerStats) SetErrorAndReset(err error) {
  52. cs.mutex.Lock()
  53. defer cs.mutex.Unlock()
  54. cs.CPUPercentage = 0
  55. cs.Memory = 0
  56. cs.MemoryPercentage = 0
  57. cs.MemoryLimit = 0
  58. cs.NetworkRx = 0
  59. cs.NetworkTx = 0
  60. cs.BlockRead = 0
  61. cs.BlockWrite = 0
  62. cs.PidsCurrent = 0
  63. cs.err = err
  64. cs.IsInvalid = true
  65. }
  66. // SetError sets container statistics error
  67. func (cs *ContainerStats) SetError(err error) {
  68. cs.mutex.Lock()
  69. defer cs.mutex.Unlock()
  70. cs.err = err
  71. if err != nil {
  72. cs.IsInvalid = true
  73. }
  74. }
  75. // SetStatistics set the container statistics
  76. func (cs *ContainerStats) SetStatistics(s StatsEntry) {
  77. cs.mutex.Lock()
  78. defer cs.mutex.Unlock()
  79. s.Name = cs.Name
  80. s.OSType = cs.OSType
  81. cs.StatsEntry = s
  82. }
  83. // GetStatistics returns container statistics with other meta data such as the container name
  84. func (cs *ContainerStats) GetStatistics() StatsEntry {
  85. cs.mutex.Lock()
  86. defer cs.mutex.Unlock()
  87. return cs.StatsEntry
  88. }
  89. // NewStatsFormat returns a format for rendering an CStatsContext
  90. func NewStatsFormat(source, osType string) Format {
  91. if source == TableFormatKey {
  92. if osType == winOSType {
  93. return Format(winDefaultStatsTableFormat)
  94. }
  95. return Format(defaultStatsTableFormat)
  96. }
  97. return Format(source)
  98. }
  99. // NewContainerStats returns a new ContainerStats entity and sets in it the given name
  100. func NewContainerStats(name, osType string) *ContainerStats {
  101. return &ContainerStats{
  102. StatsEntry: StatsEntry{Name: name, OSType: osType},
  103. }
  104. }
  105. // ContainerStatsWrite renders the context for a list of containers statistics
  106. func ContainerStatsWrite(ctx Context, containerStats []StatsEntry) error {
  107. render := func(format func(subContext subContext) error) error {
  108. for _, cstats := range containerStats {
  109. containerStatsCtx := &containerStatsContext{
  110. s: cstats,
  111. }
  112. if err := format(containerStatsCtx); err != nil {
  113. return err
  114. }
  115. }
  116. return nil
  117. }
  118. return ctx.Write(&containerStatsContext{}, render)
  119. }
  120. type containerStatsContext struct {
  121. HeaderContext
  122. s StatsEntry
  123. }
  124. func (c *containerStatsContext) Container() string {
  125. c.AddHeader(containerHeader)
  126. return c.s.Name
  127. }
  128. func (c *containerStatsContext) CPUPerc() string {
  129. c.AddHeader(cpuPercHeader)
  130. if c.s.IsInvalid {
  131. return fmt.Sprintf("--")
  132. }
  133. return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
  134. }
  135. func (c *containerStatsContext) MemUsage() string {
  136. c.AddHeader(memUseHeader)
  137. if c.s.IsInvalid || c.s.OSType == winOSType {
  138. return fmt.Sprintf("-- / --")
  139. }
  140. return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
  141. }
  142. func (c *containerStatsContext) MemPerc() string {
  143. header := memPercHeader
  144. if c.s.OSType == winOSType {
  145. header = winMemPercHeader
  146. }
  147. c.AddHeader(header)
  148. if c.s.IsInvalid {
  149. return fmt.Sprintf("--")
  150. }
  151. return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
  152. }
  153. func (c *containerStatsContext) NetIO() string {
  154. c.AddHeader(netIOHeader)
  155. if c.s.IsInvalid {
  156. return fmt.Sprintf("--")
  157. }
  158. return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3))
  159. }
  160. func (c *containerStatsContext) BlockIO() string {
  161. c.AddHeader(blockIOHeader)
  162. if c.s.IsInvalid {
  163. return fmt.Sprintf("--")
  164. }
  165. return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3))
  166. }
  167. func (c *containerStatsContext) PIDs() string {
  168. c.AddHeader(pidsHeader)
  169. if c.s.IsInvalid || c.s.OSType == winOSType {
  170. return fmt.Sprintf("--")
  171. }
  172. return fmt.Sprintf("%d", c.s.PidsCurrent)
  173. }