moby/cli/command/formatter/stats.go

221 lines
5.8 KiB
Go
Raw Normal View History

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 {
Container string
Name string
ID 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
}
// 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.Container = cs.Container
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(container, osType string) *ContainerStats {
return &ContainerStats{
StatsEntry: StatsEntry{Container: container},
}
}
// ContainerStatsWrite renders the context for a list of containers statistics
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string) error {
render := func(format func(subContext subContext) error) error {
for _, cstats := range containerStats {
containerStatsCtx := &containerStatsContext{
s: cstats,
os: osType,
}
if err := format(containerStatsCtx); err != nil {
return err
}
}
return nil
}
memUsage := memUseHeader
if osType == winOSType {
memUsage = winMemUseHeader
}
containerStatsCtx := containerStatsContext{}
containerStatsCtx.header = map[string]string{
"Container": containerHeader,
"Name": nameHeader,
"ID": containerIDHeader,
"CPUPerc": cpuPercHeader,
"MemUsage": memUsage,
"MemPerc": memPercHeader,
"NetIO": netIOHeader,
"BlockIO": blockIOHeader,
"PIDs": pidsHeader,
}
containerStatsCtx.os = osType
return ctx.Write(&containerStatsCtx, render)
}
type containerStatsContext struct {
HeaderContext
s StatsEntry
os string
}
func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *containerStatsContext) Container() string {
return c.s.Container
}
func (c *containerStatsContext) Name() string {
Fix panic of "docker stats --format {{.Name}} --all" This commit fixes panic when execute stats command: * use --format {{.Name}} with --all when there're exited containers. * use --format {{.Name}} while stating exited container. The root cause is when stating an exited container, the result from the api didn't contain the Name and ID field, which will make format process panic. Panic log is like this: ``` panic: runtime error: slice bounds out of range [recovered] panic: runtime error: slice bounds out of range goroutine 1 [running]: panic(0xb20f80, 0xc420014110) /usr/local/go/src/runtime/panic.go:500 +0x1a1 text/template.errRecover(0xc4201773e8) /usr/local/go/src/text/template/exec.go:140 +0x2ad panic(0xb20f80, 0xc420014110) /usr/local/go/src/runtime/panic.go:458 +0x243 github.com/docker/docker/cli/command/formatter.(*containerStatsContext).Name(0xc420430160, 0x0, 0x0) /go/src/github.com/docker/docker/cli/command/formatter/stats.go:148 +0x86 reflect.Value.call(0xb9a3a0, 0xc420430160, 0x2213, 0xbe3657, 0x4, 0x11bc9f8, 0x0, 0x0, 0x4d75b3, 0x1198940, ...) /usr/local/go/src/reflect/value.go:434 +0x5c8 reflect.Value.Call(0xb9a3a0, 0xc420430160, 0x2213, 0x11bc9f8, 0x0, 0x0, 0xc420424028, 0xb, 0xb) /usr/local/go/src/reflect/value.go:302 +0xa4 text/template.(*state).evalCall(0xc420177368, 0xb9a3a0, 0xc420430160, 0x16, 0xb9a3a0, 0xc420430160, 0x2213, 0x1178fa0, 0xc4203ea330, 0xc4203de283, ...) /usr/local/go/src/text/template/exec.go:658 +0x530 ``` Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
2017-02-07 02:27:40 +00:00
if len(c.s.Name) > 1 {
return c.s.Name[1:]
}
return "--"
}
func (c *containerStatsContext) ID() string {
return c.s.ID
}
func (c *containerStatsContext) CPUPerc() string {
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
}
func (c *containerStatsContext) MemUsage() string {
if c.s.IsInvalid {
return fmt.Sprintf("-- / --")
}
if c.os == 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 {
if c.s.IsInvalid || c.os == winOSType {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
}
func (c *containerStatsContext) NetIO() string {
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 {
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 {
if c.s.IsInvalid || c.os == winOSType {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%d", c.s.PidsCurrent)
}