| package container |
| |
| import ( |
| "strconv" |
| "strings" |
| "sync" |
| |
| "github.com/docker/cli/cli/command/formatter" |
| "github.com/docker/go-units" |
| ) |
| |
| const ( |
| winOSType = "windows" |
| defaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}" |
| winDefaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\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 |
| |
| noValue = "--" |
| ) |
| |
| // StatsEntry 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 |
| } |
| |
| // Stats represents an entity to store containers statistics synchronously |
| type Stats struct { |
| mutex sync.RWMutex |
| StatsEntry |
| err error |
| } |
| |
| // GetError returns the container statistics error. |
| // This is used to determine whether the statistics are valid or not |
| func (cs *Stats) GetError() error { |
| cs.mutex.RLock() |
| defer cs.mutex.RUnlock() |
| 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 *Stats) 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 *Stats) 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 *Stats) 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 *Stats) GetStatistics() StatsEntry { |
| cs.mutex.RLock() |
| defer cs.mutex.RUnlock() |
| return cs.StatsEntry |
| } |
| |
| // NewStatsFormat returns a format for rendering an CStatsContext |
| func NewStatsFormat(source, osType string) formatter.Format { |
| if source == formatter.TableFormatKey { |
| if osType == winOSType { |
| return winDefaultStatsTableFormat |
| } |
| return defaultStatsTableFormat |
| } |
| return formatter.Format(source) |
| } |
| |
| // NewStats returns a new Stats entity and sets in it the given name |
| func NewStats(container string) *Stats { |
| return &Stats{StatsEntry: StatsEntry{Container: container}} |
| } |
| |
| // statsFormatWrite renders the context for a list of containers statistics |
| func statsFormatWrite(ctx formatter.Context, stats []StatsEntry, osType string, trunc bool) error { |
| // TODO(thaJeztah): this should be taken from the (first) StatsEntry instead. |
| // also, assuming all stats are for the same platform (and basing the |
| // column headers on that) won't allow aggregated results, which could |
| // be mixed platform. |
| memUsage := memUseHeader |
| if osType == winOSType { |
| memUsage = winMemUseHeader |
| } |
| statsCtx := statsContext{os: osType} |
| statsCtx.Header = formatter.SubHeaderContext{ |
| "Container": containerHeader, |
| "Name": formatter.NameHeader, |
| "ID": formatter.ContainerIDHeader, |
| "CPUPerc": cpuPercHeader, |
| "MemUsage": memUsage, |
| "MemPerc": memPercHeader, |
| "NetIO": netIOHeader, |
| "BlockIO": blockIOHeader, |
| "PIDs": pidsHeader, |
| } |
| return ctx.Write(&statsCtx, func(format func(subContext formatter.SubContext) error) error { |
| for _, cstats := range stats { |
| if err := format(&statsContext{ |
| s: cstats, |
| os: osType, |
| trunc: trunc, |
| }); err != nil { |
| return err |
| } |
| } |
| return nil |
| }) |
| } |
| |
| type statsContext struct { |
| formatter.HeaderContext |
| s StatsEntry |
| os string |
| trunc bool |
| } |
| |
| func (c *statsContext) MarshalJSON() ([]byte, error) { |
| return formatter.MarshalJSON(c) |
| } |
| |
| func (c *statsContext) Container() string { |
| return c.s.Container |
| } |
| |
| func (c *statsContext) Name() string { |
| // Trim the "/" prefix (if present). |
| if name := strings.TrimPrefix(c.s.Name, "/"); name != "" { |
| return name |
| } |
| return noValue |
| } |
| |
| func (c *statsContext) ID() string { |
| if c.trunc { |
| return formatter.TruncateID(c.s.ID) |
| } |
| return c.s.ID |
| } |
| |
| func (c *statsContext) CPUPerc() string { |
| if c.s.IsInvalid { |
| return noValue |
| } |
| return formatPercentage(c.s.CPUPercentage) |
| } |
| |
| func (c *statsContext) MemUsage() string { |
| if c.s.IsInvalid { |
| return "-- / --" |
| } |
| if c.os == winOSType { |
| return units.BytesSize(c.s.Memory) |
| } |
| return units.BytesSize(c.s.Memory) + " / " + units.BytesSize(c.s.MemoryLimit) |
| } |
| |
| func (c *statsContext) MemPerc() string { |
| if c.s.IsInvalid || c.os == winOSType { |
| return noValue |
| } |
| return formatPercentage(c.s.MemoryPercentage) |
| } |
| |
| func (c *statsContext) NetIO() string { |
| if c.s.IsInvalid { |
| return noValue |
| } |
| return units.HumanSizeWithPrecision(c.s.NetworkRx, 3) + " / " + units.HumanSizeWithPrecision(c.s.NetworkTx, 3) |
| } |
| |
| func (c *statsContext) BlockIO() string { |
| if c.s.IsInvalid { |
| return noValue |
| } |
| return units.HumanSizeWithPrecision(c.s.BlockRead, 3) + " / " + units.HumanSizeWithPrecision(c.s.BlockWrite, 3) |
| } |
| |
| func (c *statsContext) PIDs() string { |
| if c.s.IsInvalid || c.os == winOSType { |
| return noValue |
| } |
| return strconv.FormatUint(c.s.PidsCurrent, 10) |
| } |
| |
| func formatPercentage(val float64) string { |
| return strconv.FormatFloat(val, 'f', 2, 64) + "%" |
| } |