|
@@ -4,27 +4,27 @@ import (
|
|
|
"fmt"
|
|
|
"sync"
|
|
|
|
|
|
- "github.com/docker/go-units"
|
|
|
+ units "src/github.com/docker/go-units"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPrec}}\t{{.MemUsage}}\t{{.MemPrec}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
|
|
|
- winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPrec}}\t{{{.MemUsage}}\t{.NetIO}}\t{{.BlockIO}}"
|
|
|
+ winOSType = "windows"
|
|
|
+ defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
|
|
|
+ winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{{.MemUsage}}\t{.NetIO}}\t{{.BlockIO}}"
|
|
|
emptyStatsTableFormat = "Waiting for statistics..."
|
|
|
|
|
|
containerHeader = "CONTAINER"
|
|
|
- cpuPrecHeader = "CPU %"
|
|
|
+ cpuPercHeader = "CPU %"
|
|
|
netIOHeader = "NET I/O"
|
|
|
blockIOHeader = "BLOCK I/O"
|
|
|
- winMemPrecHeader = "PRIV WORKING SET" // Used only on Window
|
|
|
- memPrecHeader = "MEM %" // Used only on Linux
|
|
|
+ winMemPercHeader = "PRIV WORKING SET" // Used only on Window
|
|
|
+ memPercHeader = "MEM %" // Used only on Linux
|
|
|
memUseHeader = "MEM USAGE / LIMIT" // Used only on Linux
|
|
|
pidsHeader = "PIDS" // Used only on Linux
|
|
|
)
|
|
|
|
|
|
-// ContainerStatsAttrs represents the statistics data collected from a container.
|
|
|
-type ContainerStatsAttrs struct {
|
|
|
- Windows bool
|
|
|
+// StatsEntry represents represents the statistics data collected from a container
|
|
|
+type StatsEntry struct {
|
|
|
Name string
|
|
|
CPUPercentage float64
|
|
|
Memory float64 // On Windows this is the private working set
|
|
@@ -35,19 +35,73 @@ type ContainerStatsAttrs struct {
|
|
|
BlockRead float64
|
|
|
BlockWrite float64
|
|
|
PidsCurrent uint64 // Not used on Windows
|
|
|
+ IsInvalid bool
|
|
|
+ OSType string
|
|
|
}
|
|
|
|
|
|
-// ContainerStats represents the containers statistics data.
|
|
|
+// ContainerStats represents an entity to store containers statistics synchronously
|
|
|
type ContainerStats struct {
|
|
|
- Mu sync.RWMutex
|
|
|
- ContainerStatsAttrs
|
|
|
- Err error
|
|
|
+ mutex sync.Mutex
|
|
|
+ StatsEntry
|
|
|
+ err error
|
|
|
+}
|
|
|
+
|
|
|
+// GetError returns the container statistics error.
|
|
|
+// This is used to determine whether the statistics are valid or not
|
|
|
+func (cs *ContainerStats) GetError() error {
|
|
|
+ cs.mutex.Lock()
|
|
|
+ defer cs.mutex.Unlock()
|
|
|
+ return cs.err
|
|
|
+}
|
|
|
+
|
|
|
+// SetErrorAndReset zeroes all the container statistics and store the error.
|
|
|
+// It is used when receiving time out error during statistics collecting to reduce lock overhead
|
|
|
+func (cs *ContainerStats) SetErrorAndReset(err error) {
|
|
|
+ cs.mutex.Lock()
|
|
|
+ defer cs.mutex.Unlock()
|
|
|
+ cs.CPUPercentage = 0
|
|
|
+ cs.Memory = 0
|
|
|
+ cs.MemoryPercentage = 0
|
|
|
+ cs.MemoryLimit = 0
|
|
|
+ cs.NetworkRx = 0
|
|
|
+ cs.NetworkTx = 0
|
|
|
+ cs.BlockRead = 0
|
|
|
+ cs.BlockWrite = 0
|
|
|
+ cs.PidsCurrent = 0
|
|
|
+ cs.err = err
|
|
|
+ cs.IsInvalid = true
|
|
|
+}
|
|
|
+
|
|
|
+// SetError sets container statistics error
|
|
|
+func (cs *ContainerStats) SetError(err error) {
|
|
|
+ cs.mutex.Lock()
|
|
|
+ defer cs.mutex.Unlock()
|
|
|
+ cs.err = err
|
|
|
+ if err != nil {
|
|
|
+ cs.IsInvalid = true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// SetStatistics set the container statistics
|
|
|
+func (cs *ContainerStats) SetStatistics(s StatsEntry) {
|
|
|
+ cs.mutex.Lock()
|
|
|
+ defer cs.mutex.Unlock()
|
|
|
+ s.Name = cs.Name
|
|
|
+ s.OSType = cs.OSType
|
|
|
+ cs.StatsEntry = s
|
|
|
+}
|
|
|
+
|
|
|
+// GetStatistics returns container statistics with other meta data such as the container name
|
|
|
+func (cs *ContainerStats) GetStatistics() StatsEntry {
|
|
|
+ cs.mutex.Lock()
|
|
|
+ defer cs.mutex.Unlock()
|
|
|
+ return cs.StatsEntry
|
|
|
}
|
|
|
|
|
|
// NewStatsFormat returns a format for rendering an CStatsContext
|
|
|
func NewStatsFormat(source, osType string) Format {
|
|
|
if source == TableFormatKey {
|
|
|
- if osType == "windows" {
|
|
|
+ if osType == winOSType {
|
|
|
return Format(winDefaultStatsTableFormat)
|
|
|
}
|
|
|
return Format(defaultStatsTableFormat)
|
|
@@ -58,22 +112,16 @@ func NewStatsFormat(source, osType string) Format {
|
|
|
// NewContainerStats returns a new ContainerStats entity and sets in it the given name
|
|
|
func NewContainerStats(name, osType string) *ContainerStats {
|
|
|
return &ContainerStats{
|
|
|
- ContainerStatsAttrs: ContainerStatsAttrs{
|
|
|
- Name: name,
|
|
|
- Windows: (osType == "windows"),
|
|
|
- },
|
|
|
+ StatsEntry: StatsEntry{Name: name, OSType: osType},
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ContainerStatsWrite renders the context for a list of containers statistics
|
|
|
-func ContainerStatsWrite(ctx Context, containerStats []*ContainerStats) error {
|
|
|
+func ContainerStatsWrite(ctx Context, containerStats []StatsEntry) error {
|
|
|
render := func(format func(subContext subContext) error) error {
|
|
|
for _, cstats := range containerStats {
|
|
|
- cstats.Mu.RLock()
|
|
|
- cstatsAttrs := cstats.ContainerStatsAttrs
|
|
|
- cstats.Mu.RUnlock()
|
|
|
containerStatsCtx := &containerStatsContext{
|
|
|
- s: cstatsAttrs,
|
|
|
+ s: cstats,
|
|
|
}
|
|
|
if err := format(containerStatsCtx); err != nil {
|
|
|
return err
|
|
@@ -86,7 +134,7 @@ func ContainerStatsWrite(ctx Context, containerStats []*ContainerStats) error {
|
|
|
|
|
|
type containerStatsContext struct {
|
|
|
HeaderContext
|
|
|
- s ContainerStatsAttrs
|
|
|
+ s StatsEntry
|
|
|
}
|
|
|
|
|
|
func (c *containerStatsContext) Container() string {
|
|
@@ -94,42 +142,54 @@ func (c *containerStatsContext) Container() string {
|
|
|
return c.s.Name
|
|
|
}
|
|
|
|
|
|
-func (c *containerStatsContext) CPUPrec() string {
|
|
|
- c.AddHeader(cpuPrecHeader)
|
|
|
+func (c *containerStatsContext) CPUPerc() string {
|
|
|
+ c.AddHeader(cpuPercHeader)
|
|
|
+ if c.s.IsInvalid {
|
|
|
+ return fmt.Sprintf("--")
|
|
|
+ }
|
|
|
return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
|
|
|
}
|
|
|
|
|
|
func (c *containerStatsContext) MemUsage() string {
|
|
|
c.AddHeader(memUseHeader)
|
|
|
- if !c.s.Windows {
|
|
|
- return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
|
|
|
+ if c.s.IsInvalid || c.s.OSType == winOSType {
|
|
|
+ return fmt.Sprintf("-- / --")
|
|
|
}
|
|
|
- return fmt.Sprintf("-- / --")
|
|
|
+ return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
|
|
|
}
|
|
|
|
|
|
-func (c *containerStatsContext) MemPrec() string {
|
|
|
- header := memPrecHeader
|
|
|
- if c.s.Windows {
|
|
|
- header = winMemPrecHeader
|
|
|
+func (c *containerStatsContext) MemPerc() string {
|
|
|
+ header := memPercHeader
|
|
|
+ if c.s.OSType == winOSType {
|
|
|
+ header = winMemPercHeader
|
|
|
}
|
|
|
c.AddHeader(header)
|
|
|
+ if c.s.IsInvalid {
|
|
|
+ return fmt.Sprintf("--")
|
|
|
+ }
|
|
|
return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
|
|
|
}
|
|
|
|
|
|
func (c *containerStatsContext) NetIO() string {
|
|
|
c.AddHeader(netIOHeader)
|
|
|
+ if c.s.IsInvalid {
|
|
|
+ return fmt.Sprintf("--")
|
|
|
+ }
|
|
|
return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3))
|
|
|
}
|
|
|
|
|
|
func (c *containerStatsContext) BlockIO() string {
|
|
|
c.AddHeader(blockIOHeader)
|
|
|
+ if c.s.IsInvalid {
|
|
|
+ return fmt.Sprintf("--")
|
|
|
+ }
|
|
|
return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3))
|
|
|
}
|
|
|
|
|
|
func (c *containerStatsContext) PIDs() string {
|
|
|
c.AddHeader(pidsHeader)
|
|
|
- if !c.s.Windows {
|
|
|
- return fmt.Sprintf("%d", c.s.PidsCurrent)
|
|
|
+ if c.s.IsInvalid || c.s.OSType == winOSType {
|
|
|
+ return fmt.Sprintf("--")
|
|
|
}
|
|
|
- return fmt.Sprintf("-")
|
|
|
+ return fmt.Sprintf("%d", c.s.PidsCurrent)
|
|
|
}
|