package formatter import ( "fmt" "sync" units "github.com/docker/go-units" ) const ( 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}}" containerHeader = "CONTAINER" cpuPercHeader = "CPU %" netIOHeader = "NET I/O" blockIOHeader = "BLOCK I/O" memPercHeader = "MEM %" // Used only on Linux winMemUseHeader = "PRIV WORKING SET" // Used only on Windows memUseHeader = "MEM USAGE / LIMIT" // Used only on Linux pidsHeader = "PIDS" // Used only on Linux ) // 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 MemoryLimit float64 // Not used on Windows MemoryPercentage float64 // Not used on Windows NetworkRx float64 NetworkTx float64 BlockRead float64 BlockWrite float64 PidsCurrent uint64 // Not used on Windows IsInvalid bool OSType string } // ContainerStats represents an entity to store containers statistics synchronously type ContainerStats struct { 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 == winOSType { return Format(winDefaultStatsTableFormat) } return Format(defaultStatsTableFormat) } return Format(source) } // NewContainerStats returns a new ContainerStats entity and sets in it the given name func NewContainerStats(name, osType string) *ContainerStats { return &ContainerStats{ StatsEntry: StatsEntry{Name: name, OSType: osType}, } } // ContainerStatsWrite renders the context for a list of containers statistics func ContainerStatsWrite(ctx Context, containerStats []StatsEntry) error { render := func(format func(subContext subContext) error) error { for _, cstats := range containerStats { containerStatsCtx := &containerStatsContext{ s: cstats, } if err := format(containerStatsCtx); err != nil { return err } } return nil } return ctx.Write(&containerStatsContext{}, render) } type containerStatsContext struct { HeaderContext s StatsEntry } func (c *containerStatsContext) Container() string { c.AddHeader(containerHeader) return c.s.Name } 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 { header := memUseHeader if c.s.OSType == winOSType { header = winMemUseHeader } c.AddHeader(header) if c.s.IsInvalid { return fmt.Sprintf("-- / --") } if c.s.OSType == winOSType { return fmt.Sprintf("%s", units.BytesSize(c.s.Memory)) } return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit)) } func (c *containerStatsContext) MemPerc() string { header := memPercHeader c.AddHeader(header) if c.s.IsInvalid || c.s.OSType == winOSType { 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.IsInvalid || c.s.OSType == winOSType { return fmt.Sprintf("--") } return fmt.Sprintf("%d", c.s.PidsCurrent) }