stats.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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. }
  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.Container = cs.Container
  80. cs.StatsEntry = s
  81. }
  82. // GetStatistics returns container statistics with other meta data such as the container name
  83. func (cs *ContainerStats) GetStatistics() StatsEntry {
  84. cs.mutex.Lock()
  85. defer cs.mutex.Unlock()
  86. return cs.StatsEntry
  87. }
  88. // NewStatsFormat returns a format for rendering an CStatsContext
  89. func NewStatsFormat(source, osType string) Format {
  90. if source == TableFormatKey {
  91. if osType == winOSType {
  92. return Format(winDefaultStatsTableFormat)
  93. }
  94. return Format(defaultStatsTableFormat)
  95. }
  96. return Format(source)
  97. }
  98. // NewContainerStats returns a new ContainerStats entity and sets in it the given name
  99. func NewContainerStats(container, osType string) *ContainerStats {
  100. return &ContainerStats{
  101. StatsEntry: StatsEntry{Container: container},
  102. }
  103. }
  104. // ContainerStatsWrite renders the context for a list of containers statistics
  105. func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string) error {
  106. render := func(format func(subContext subContext) error) error {
  107. for _, cstats := range containerStats {
  108. containerStatsCtx := &containerStatsContext{
  109. s: cstats,
  110. os: osType,
  111. }
  112. if err := format(containerStatsCtx); err != nil {
  113. return err
  114. }
  115. }
  116. return nil
  117. }
  118. return ctx.Write(&containerStatsContext{os: osType}, render)
  119. }
  120. type containerStatsContext struct {
  121. HeaderContext
  122. s StatsEntry
  123. os string
  124. }
  125. func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
  126. return marshalJSON(c)
  127. }
  128. func (c *containerStatsContext) Container() string {
  129. c.AddHeader(containerHeader)
  130. return c.s.Container
  131. }
  132. func (c *containerStatsContext) Name() string {
  133. c.AddHeader(nameHeader)
  134. if len(c.s.Name) > 1 {
  135. return c.s.Name[1:]
  136. }
  137. return "--"
  138. }
  139. func (c *containerStatsContext) ID() string {
  140. c.AddHeader(containerIDHeader)
  141. return c.s.ID
  142. }
  143. func (c *containerStatsContext) CPUPerc() string {
  144. c.AddHeader(cpuPercHeader)
  145. if c.s.IsInvalid {
  146. return fmt.Sprintf("--")
  147. }
  148. return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
  149. }
  150. func (c *containerStatsContext) MemUsage() string {
  151. header := memUseHeader
  152. if c.os == winOSType {
  153. header = winMemUseHeader
  154. }
  155. c.AddHeader(header)
  156. if c.s.IsInvalid {
  157. return fmt.Sprintf("-- / --")
  158. }
  159. if c.os == winOSType {
  160. return fmt.Sprintf("%s", units.BytesSize(c.s.Memory))
  161. }
  162. return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
  163. }
  164. func (c *containerStatsContext) MemPerc() string {
  165. header := memPercHeader
  166. c.AddHeader(header)
  167. if c.s.IsInvalid || c.os == winOSType {
  168. return fmt.Sprintf("--")
  169. }
  170. return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
  171. }
  172. func (c *containerStatsContext) NetIO() string {
  173. c.AddHeader(netIOHeader)
  174. if c.s.IsInvalid {
  175. return fmt.Sprintf("--")
  176. }
  177. return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3))
  178. }
  179. func (c *containerStatsContext) BlockIO() string {
  180. c.AddHeader(blockIOHeader)
  181. if c.s.IsInvalid {
  182. return fmt.Sprintf("--")
  183. }
  184. return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3))
  185. }
  186. func (c *containerStatsContext) PIDs() string {
  187. c.AddHeader(pidsHeader)
  188. if c.s.IsInvalid || c.os == winOSType {
  189. return fmt.Sprintf("--")
  190. }
  191. return fmt.Sprintf("%d", c.s.PidsCurrent)
  192. }