github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/container/formatter_stats.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/docker/cli/cli/command/formatter" 8 "github.com/docker/docker/pkg/stringid" 9 units "github.com/docker/go-units" 10 ) 11 12 const ( 13 winOSType = "windows" 14 defaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}" 15 winDefaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" 16 17 containerHeader = "CONTAINER" 18 cpuPercHeader = "CPU %" 19 netIOHeader = "NET I/O" 20 blockIOHeader = "BLOCK I/O" 21 memPercHeader = "MEM %" // Used only on Linux 22 winMemUseHeader = "PRIV WORKING SET" // Used only on Windows 23 memUseHeader = "MEM USAGE / LIMIT" // Used only on Linux 24 pidsHeader = "PIDS" // Used only on Linux 25 ) 26 27 // StatsEntry represents represents the statistics data collected from a container 28 type StatsEntry struct { 29 Container string 30 Name string 31 ID string 32 CPUPercentage float64 33 Memory float64 // On Windows this is the private working set 34 MemoryLimit float64 // Not used on Windows 35 MemoryPercentage float64 // Not used on Windows 36 NetworkRx float64 37 NetworkTx float64 38 BlockRead float64 39 BlockWrite float64 40 PidsCurrent uint64 // Not used on Windows 41 IsInvalid bool 42 } 43 44 // Stats represents an entity to store containers statistics synchronously 45 type Stats struct { 46 mutex sync.Mutex 47 StatsEntry 48 err error 49 } 50 51 // GetError returns the container statistics error. 52 // This is used to determine whether the statistics are valid or not 53 func (cs *Stats) GetError() error { 54 cs.mutex.Lock() 55 defer cs.mutex.Unlock() 56 return cs.err 57 } 58 59 // SetErrorAndReset zeroes all the container statistics and store the error. 60 // It is used when receiving time out error during statistics collecting to reduce lock overhead 61 func (cs *Stats) SetErrorAndReset(err error) { 62 cs.mutex.Lock() 63 defer cs.mutex.Unlock() 64 cs.CPUPercentage = 0 65 cs.Memory = 0 66 cs.MemoryPercentage = 0 67 cs.MemoryLimit = 0 68 cs.NetworkRx = 0 69 cs.NetworkTx = 0 70 cs.BlockRead = 0 71 cs.BlockWrite = 0 72 cs.PidsCurrent = 0 73 cs.err = err 74 cs.IsInvalid = true 75 } 76 77 // SetError sets container statistics error 78 func (cs *Stats) SetError(err error) { 79 cs.mutex.Lock() 80 defer cs.mutex.Unlock() 81 cs.err = err 82 if err != nil { 83 cs.IsInvalid = true 84 } 85 } 86 87 // SetStatistics set the container statistics 88 func (cs *Stats) SetStatistics(s StatsEntry) { 89 cs.mutex.Lock() 90 defer cs.mutex.Unlock() 91 s.Container = cs.Container 92 cs.StatsEntry = s 93 } 94 95 // GetStatistics returns container statistics with other meta data such as the container name 96 func (cs *Stats) GetStatistics() StatsEntry { 97 cs.mutex.Lock() 98 defer cs.mutex.Unlock() 99 return cs.StatsEntry 100 } 101 102 // NewStatsFormat returns a format for rendering an CStatsContext 103 func NewStatsFormat(source, osType string) formatter.Format { 104 if source == formatter.TableFormatKey { 105 if osType == winOSType { 106 return winDefaultStatsTableFormat 107 } 108 return defaultStatsTableFormat 109 } 110 return formatter.Format(source) 111 } 112 113 // NewStats returns a new Stats entity and sets in it the given name 114 func NewStats(container string) *Stats { 115 return &Stats{StatsEntry: StatsEntry{Container: container}} 116 } 117 118 // statsFormatWrite renders the context for a list of containers statistics 119 func statsFormatWrite(ctx formatter.Context, Stats []StatsEntry, osType string, trunc bool) error { 120 render := func(format func(subContext formatter.SubContext) error) error { 121 for _, cstats := range Stats { 122 statsCtx := &statsContext{ 123 s: cstats, 124 os: osType, 125 trunc: trunc, 126 } 127 if err := format(statsCtx); err != nil { 128 return err 129 } 130 } 131 return nil 132 } 133 memUsage := memUseHeader 134 if osType == winOSType { 135 memUsage = winMemUseHeader 136 } 137 statsCtx := statsContext{} 138 statsCtx.Header = formatter.SubHeaderContext{ 139 "Container": containerHeader, 140 "Name": formatter.NameHeader, 141 "ID": formatter.ContainerIDHeader, 142 "CPUPerc": cpuPercHeader, 143 "MemUsage": memUsage, 144 "MemPerc": memPercHeader, 145 "NetIO": netIOHeader, 146 "BlockIO": blockIOHeader, 147 "PIDs": pidsHeader, 148 } 149 statsCtx.os = osType 150 return ctx.Write(&statsCtx, render) 151 } 152 153 type statsContext struct { 154 formatter.HeaderContext 155 s StatsEntry 156 os string 157 trunc bool 158 } 159 160 func (c *statsContext) MarshalJSON() ([]byte, error) { 161 return formatter.MarshalJSON(c) 162 } 163 164 func (c *statsContext) Container() string { 165 return c.s.Container 166 } 167 168 func (c *statsContext) Name() string { 169 if len(c.s.Name) > 1 { 170 return c.s.Name[1:] 171 } 172 return "--" 173 } 174 175 func (c *statsContext) ID() string { 176 if c.trunc { 177 return stringid.TruncateID(c.s.ID) 178 } 179 return c.s.ID 180 } 181 182 func (c *statsContext) CPUPerc() string { 183 if c.s.IsInvalid { 184 return fmt.Sprintf("--") 185 } 186 return fmt.Sprintf("%.2f%%", c.s.CPUPercentage) 187 } 188 189 func (c *statsContext) MemUsage() string { 190 if c.s.IsInvalid { 191 return fmt.Sprintf("-- / --") 192 } 193 if c.os == winOSType { 194 return units.BytesSize(c.s.Memory) 195 } 196 return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit)) 197 } 198 199 func (c *statsContext) MemPerc() string { 200 if c.s.IsInvalid || c.os == winOSType { 201 return fmt.Sprintf("--") 202 } 203 return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage) 204 } 205 206 func (c *statsContext) NetIO() string { 207 if c.s.IsInvalid { 208 return fmt.Sprintf("--") 209 } 210 return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3)) 211 } 212 213 func (c *statsContext) BlockIO() string { 214 if c.s.IsInvalid { 215 return fmt.Sprintf("--") 216 } 217 return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3)) 218 } 219 220 func (c *statsContext) PIDs() string { 221 if c.s.IsInvalid || c.os == winOSType { 222 return fmt.Sprintf("--") 223 } 224 return fmt.Sprintf("%d", c.s.PidsCurrent) 225 }