stats.go 5.6 KB

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