| ... | ... |
@@ -5,25 +5,24 @@ import ( |
| 5 | 5 |
"io" |
| 6 | 6 |
"strings" |
| 7 | 7 |
"sync" |
| 8 |
- "text/tabwriter" |
|
| 9 | 8 |
"time" |
| 10 | 9 |
|
| 11 | 10 |
"golang.org/x/net/context" |
| 12 | 11 |
|
| 13 |
- "github.com/Sirupsen/logrus" |
|
| 14 | 12 |
"github.com/docker/docker/api/types" |
| 15 | 13 |
"github.com/docker/docker/api/types/events" |
| 16 | 14 |
"github.com/docker/docker/api/types/filters" |
| 17 | 15 |
"github.com/docker/docker/cli" |
| 18 | 16 |
"github.com/docker/docker/cli/command" |
| 17 |
+ "github.com/docker/docker/cli/command/formatter" |
|
| 19 | 18 |
"github.com/docker/docker/cli/command/system" |
| 20 | 19 |
"github.com/spf13/cobra" |
| 21 | 20 |
) |
| 22 | 21 |
|
| 23 | 22 |
type statsOptions struct {
|
| 24 |
- all bool |
|
| 25 |
- noStream bool |
|
| 26 |
- |
|
| 23 |
+ all bool |
|
| 24 |
+ noStream bool |
|
| 25 |
+ format string |
|
| 27 | 26 |
containers []string |
| 28 | 27 |
} |
| 29 | 28 |
|
| ... | ... |
@@ -44,6 +43,7 @@ func NewStatsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 44 | 44 |
flags := cmd.Flags() |
| 45 | 45 |
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)") |
| 46 | 46 |
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result") |
| 47 |
+ flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template") |
|
| 47 | 48 |
return cmd |
| 48 | 49 |
} |
| 49 | 50 |
|
| ... | ... |
@@ -98,10 +98,10 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 98 | 98 |
closeChan <- err |
| 99 | 99 |
} |
| 100 | 100 |
for _, container := range cs {
|
| 101 |
- s := &containerStats{Name: container.ID[:12]}
|
|
| 101 |
+ s := formatter.NewContainerStats(container.ID[:12], daemonOSType) |
|
| 102 | 102 |
if cStats.add(s) {
|
| 103 | 103 |
waitFirst.Add(1) |
| 104 |
- go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 104 |
+ go collect(s, ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 105 | 105 |
} |
| 106 | 106 |
} |
| 107 | 107 |
} |
| ... | ... |
@@ -115,19 +115,19 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 115 | 115 |
eh := system.InitEventHandler() |
| 116 | 116 |
eh.Handle("create", func(e events.Message) {
|
| 117 | 117 |
if opts.all {
|
| 118 |
- s := &containerStats{Name: e.ID[:12]}
|
|
| 118 |
+ s := formatter.NewContainerStats(e.ID[:12], daemonOSType) |
|
| 119 | 119 |
if cStats.add(s) {
|
| 120 | 120 |
waitFirst.Add(1) |
| 121 |
- go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 121 |
+ go collect(s, ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 122 | 122 |
} |
| 123 | 123 |
} |
| 124 | 124 |
}) |
| 125 | 125 |
|
| 126 | 126 |
eh.Handle("start", func(e events.Message) {
|
| 127 |
- s := &containerStats{Name: e.ID[:12]}
|
|
| 127 |
+ s := formatter.NewContainerStats(e.ID[:12], daemonOSType) |
|
| 128 | 128 |
if cStats.add(s) {
|
| 129 | 129 |
waitFirst.Add(1) |
| 130 |
- go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 130 |
+ go collect(s, ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 131 | 131 |
} |
| 132 | 132 |
}) |
| 133 | 133 |
|
| ... | ... |
@@ -150,10 +150,10 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 150 | 150 |
// Artificially send creation events for the containers we were asked to |
| 151 | 151 |
// monitor (same code path than we use when monitoring all containers). |
| 152 | 152 |
for _, name := range opts.containers {
|
| 153 |
- s := &containerStats{Name: name}
|
|
| 153 |
+ s := formatter.NewContainerStats(name, daemonOSType) |
|
| 154 | 154 |
if cStats.add(s) {
|
| 155 | 155 |
waitFirst.Add(1) |
| 156 |
- go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 156 |
+ go collect(s, ctx, dockerCli.Client(), !opts.noStream, waitFirst) |
|
| 157 | 157 |
} |
| 158 | 158 |
} |
| 159 | 159 |
|
| ... | ... |
@@ -166,11 +166,11 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 166 | 166 |
var errs []string |
| 167 | 167 |
cStats.mu.Lock() |
| 168 | 168 |
for _, c := range cStats.cs {
|
| 169 |
- c.mu.Lock() |
|
| 170 |
- if c.err != nil {
|
|
| 171 |
- errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
|
| 169 |
+ c.Mu.Lock() |
|
| 170 |
+ if c.Err != nil {
|
|
| 171 |
+ errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.Err))
|
|
| 172 | 172 |
} |
| 173 |
- c.mu.Unlock() |
|
| 173 |
+ c.Mu.Unlock() |
|
| 174 | 174 |
} |
| 175 | 175 |
cStats.mu.Unlock() |
| 176 | 176 |
if len(errs) > 0 {
|
| ... | ... |
@@ -180,44 +180,34 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 180 | 180 |
|
| 181 | 181 |
// before print to screen, make sure each container get at least one valid stat data |
| 182 | 182 |
waitFirst.Wait() |
| 183 |
+ f := "table" |
|
| 184 |
+ if len(opts.format) > 0 {
|
|
| 185 |
+ f = opts.format |
|
| 186 |
+ } |
|
| 187 |
+ statsCtx := formatter.Context{
|
|
| 188 |
+ Output: dockerCli.Out(), |
|
| 189 |
+ Format: formatter.NewStatsFormat(f, daemonOSType), |
|
| 190 |
+ } |
|
| 183 | 191 |
|
| 184 |
- w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0) |
|
| 185 |
- printHeader := func() {
|
|
| 192 |
+ cleanHeader := func() {
|
|
| 186 | 193 |
if !opts.noStream {
|
| 187 | 194 |
fmt.Fprint(dockerCli.Out(), "\033[2J") |
| 188 | 195 |
fmt.Fprint(dockerCli.Out(), "\033[H") |
| 189 | 196 |
} |
| 190 |
- switch daemonOSType {
|
|
| 191 |
- case "": |
|
| 192 |
- // Before we have any stats from the daemon, we don't know the platform... |
|
| 193 |
- io.WriteString(w, "Waiting for statistics...\n") |
|
| 194 |
- case "windows": |
|
| 195 |
- io.WriteString(w, "CONTAINER\tCPU %\tPRIV WORKING SET\tNET I/O\tBLOCK I/O\n") |
|
| 196 |
- default: |
|
| 197 |
- io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n") |
|
| 198 |
- } |
|
| 199 | 197 |
} |
| 200 | 198 |
|
| 199 |
+ var err error |
|
| 201 | 200 |
for range time.Tick(500 * time.Millisecond) {
|
| 202 |
- printHeader() |
|
| 203 |
- toRemove := []string{}
|
|
| 204 |
- cStats.mu.Lock() |
|
| 205 |
- for _, s := range cStats.cs {
|
|
| 206 |
- if err := s.Display(w); err != nil && !opts.noStream {
|
|
| 207 |
- logrus.Debugf("stats: got error for %s: %v", s.Name, err)
|
|
| 208 |
- if err == io.EOF {
|
|
| 209 |
- toRemove = append(toRemove, s.Name) |
|
| 210 |
- } |
|
| 211 |
- } |
|
| 212 |
- } |
|
| 213 |
- cStats.mu.Unlock() |
|
| 214 |
- for _, name := range toRemove {
|
|
| 215 |
- cStats.remove(name) |
|
| 201 |
+ cleanHeader() |
|
| 202 |
+ cStats.mu.RLock() |
|
| 203 |
+ csLen := len(cStats.cs) |
|
| 204 |
+ if err = formatter.ContainerStatsWrite(statsCtx, cStats.cs); err != nil {
|
|
| 205 |
+ break |
|
| 216 | 206 |
} |
| 217 |
- if len(cStats.cs) == 0 && !showAll {
|
|
| 218 |
- return nil |
|
| 207 |
+ cStats.mu.RUnlock() |
|
| 208 |
+ if csLen == 0 && !showAll {
|
|
| 209 |
+ break |
|
| 219 | 210 |
} |
| 220 |
- w.Flush() |
|
| 221 | 211 |
if opts.noStream {
|
| 222 | 212 |
break |
| 223 | 213 |
} |
| ... | ... |
@@ -237,5 +227,5 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 237 | 237 |
// just skip |
| 238 | 238 |
} |
| 239 | 239 |
} |
| 240 |
- return nil |
|
| 240 |
+ return err |
|
| 241 | 241 |
} |
| ... | ... |
@@ -3,7 +3,6 @@ package container |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"errors" |
| 6 |
- "fmt" |
|
| 7 | 6 |
"io" |
| 8 | 7 |
"strings" |
| 9 | 8 |
"sync" |
| ... | ... |
@@ -11,30 +10,15 @@ import ( |
| 11 | 11 |
|
| 12 | 12 |
"github.com/Sirupsen/logrus" |
| 13 | 13 |
"github.com/docker/docker/api/types" |
| 14 |
+ "github.com/docker/docker/cli/command/formatter" |
|
| 14 | 15 |
"github.com/docker/docker/client" |
| 15 |
- "github.com/docker/go-units" |
|
| 16 | 16 |
"golang.org/x/net/context" |
| 17 | 17 |
) |
| 18 | 18 |
|
| 19 |
-type containerStats struct {
|
|
| 20 |
- Name string |
|
| 21 |
- CPUPercentage float64 |
|
| 22 |
- Memory float64 // On Windows this is the private working set |
|
| 23 |
- MemoryLimit float64 // Not used on Windows |
|
| 24 |
- MemoryPercentage float64 // Not used on Windows |
|
| 25 |
- NetworkRx float64 |
|
| 26 |
- NetworkTx float64 |
|
| 27 |
- BlockRead float64 |
|
| 28 |
- BlockWrite float64 |
|
| 29 |
- PidsCurrent uint64 // Not used on Windows |
|
| 30 |
- mu sync.Mutex |
|
| 31 |
- err error |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 | 19 |
type stats struct {
|
| 35 |
- mu sync.Mutex |
|
| 36 | 20 |
ostype string |
| 37 |
- cs []*containerStats |
|
| 21 |
+ mu sync.RWMutex |
|
| 22 |
+ cs []*formatter.ContainerStats |
|
| 38 | 23 |
} |
| 39 | 24 |
|
| 40 | 25 |
// daemonOSType is set once we have at least one stat for a container |
| ... | ... |
@@ -42,7 +26,7 @@ type stats struct {
|
| 42 | 42 |
// on the daemon platform. |
| 43 | 43 |
var daemonOSType string |
| 44 | 44 |
|
| 45 |
-func (s *stats) add(cs *containerStats) bool {
|
|
| 45 |
+func (s *stats) add(cs *formatter.ContainerStats) bool {
|
|
| 46 | 46 |
s.mu.Lock() |
| 47 | 47 |
defer s.mu.Unlock() |
| 48 | 48 |
if _, exists := s.isKnownContainer(cs.Name); !exists {
|
| ... | ... |
@@ -69,7 +53,7 @@ func (s *stats) isKnownContainer(cid string) (int, bool) {
|
| 69 | 69 |
return -1, false |
| 70 | 70 |
} |
| 71 | 71 |
|
| 72 |
-func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
|
|
| 72 |
+func collect(s *formatter.ContainerStats, ctx context.Context, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
|
|
| 73 | 73 |
logrus.Debugf("collecting stats for %s", s.Name)
|
| 74 | 74 |
var ( |
| 75 | 75 |
getFirst bool |
| ... | ... |
@@ -88,9 +72,9 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 88 | 88 |
|
| 89 | 89 |
response, err := cli.ContainerStats(ctx, s.Name, streamStats) |
| 90 | 90 |
if err != nil {
|
| 91 |
- s.mu.Lock() |
|
| 92 |
- s.err = err |
|
| 93 |
- s.mu.Unlock() |
|
| 91 |
+ s.Mu.Lock() |
|
| 92 |
+ s.Err = err |
|
| 93 |
+ s.Mu.Unlock() |
|
| 94 | 94 |
return |
| 95 | 95 |
} |
| 96 | 96 |
defer response.Body.Close() |
| ... | ... |
@@ -137,7 +121,7 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 137 | 137 |
mem = float64(v.MemoryStats.PrivateWorkingSet) |
| 138 | 138 |
} |
| 139 | 139 |
|
| 140 |
- s.mu.Lock() |
|
| 140 |
+ s.Mu.Lock() |
|
| 141 | 141 |
s.CPUPercentage = cpuPercent |
| 142 | 142 |
s.Memory = mem |
| 143 | 143 |
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) |
| ... | ... |
@@ -148,7 +132,7 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 148 | 148 |
s.MemoryPercentage = memPercent |
| 149 | 149 |
s.PidsCurrent = v.PidsStats.Current |
| 150 | 150 |
} |
| 151 |
- s.mu.Unlock() |
|
| 151 |
+ s.Mu.Unlock() |
|
| 152 | 152 |
u <- nil |
| 153 | 153 |
if !streamStats {
|
| 154 | 154 |
return |
| ... | ... |
@@ -160,7 +144,7 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 160 | 160 |
case <-time.After(2 * time.Second): |
| 161 | 161 |
// zero out the values if we have not received an update within |
| 162 | 162 |
// the specified duration. |
| 163 |
- s.mu.Lock() |
|
| 163 |
+ s.Mu.Lock() |
|
| 164 | 164 |
s.CPUPercentage = 0 |
| 165 | 165 |
s.Memory = 0 |
| 166 | 166 |
s.MemoryPercentage = 0 |
| ... | ... |
@@ -170,8 +154,8 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 170 | 170 |
s.BlockRead = 0 |
| 171 | 171 |
s.BlockWrite = 0 |
| 172 | 172 |
s.PidsCurrent = 0 |
| 173 |
- s.err = errors.New("timeout waiting for stats")
|
|
| 174 |
- s.mu.Unlock() |
|
| 173 |
+ s.Err = errors.New("timeout waiting for stats")
|
|
| 174 |
+ s.Mu.Unlock() |
|
| 175 | 175 |
// if this is the first stat you get, release WaitGroup |
| 176 | 176 |
if !getFirst {
|
| 177 | 177 |
getFirst = true |
| ... | ... |
@@ -179,12 +163,12 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 179 | 179 |
} |
| 180 | 180 |
case err := <-u: |
| 181 | 181 |
if err != nil {
|
| 182 |
- s.mu.Lock() |
|
| 183 |
- s.err = err |
|
| 184 |
- s.mu.Unlock() |
|
| 182 |
+ s.Mu.Lock() |
|
| 183 |
+ s.Err = err |
|
| 184 |
+ s.Mu.Unlock() |
|
| 185 | 185 |
continue |
| 186 | 186 |
} |
| 187 |
- s.err = nil |
|
| 187 |
+ s.Err = nil |
|
| 188 | 188 |
// if this is the first stat you get, release WaitGroup |
| 189 | 189 |
if !getFirst {
|
| 190 | 190 |
getFirst = true |
| ... | ... |
@@ -197,51 +181,6 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 197 | 197 |
} |
| 198 | 198 |
} |
| 199 | 199 |
|
| 200 |
-func (s *containerStats) Display(w io.Writer) error {
|
|
| 201 |
- s.mu.Lock() |
|
| 202 |
- defer s.mu.Unlock() |
|
| 203 |
- if daemonOSType == "windows" {
|
|
| 204 |
- // NOTE: if you change this format, you must also change the err format below! |
|
| 205 |
- format := "%s\t%.2f%%\t%s\t%s / %s\t%s / %s\n" |
|
| 206 |
- if s.err != nil {
|
|
| 207 |
- format = "%s\t%s\t%s\t%s / %s\t%s / %s\n" |
|
| 208 |
- errStr := "--" |
|
| 209 |
- fmt.Fprintf(w, format, |
|
| 210 |
- s.Name, errStr, errStr, errStr, errStr, errStr, errStr, |
|
| 211 |
- ) |
|
| 212 |
- err := s.err |
|
| 213 |
- return err |
|
| 214 |
- } |
|
| 215 |
- fmt.Fprintf(w, format, |
|
| 216 |
- s.Name, |
|
| 217 |
- s.CPUPercentage, |
|
| 218 |
- units.BytesSize(s.Memory), |
|
| 219 |
- units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3), |
|
| 220 |
- units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3)) |
|
| 221 |
- } else {
|
|
| 222 |
- // NOTE: if you change this format, you must also change the err format below! |
|
| 223 |
- format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n" |
|
| 224 |
- if s.err != nil {
|
|
| 225 |
- format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n" |
|
| 226 |
- errStr := "--" |
|
| 227 |
- fmt.Fprintf(w, format, |
|
| 228 |
- s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, |
|
| 229 |
- ) |
|
| 230 |
- err := s.err |
|
| 231 |
- return err |
|
| 232 |
- } |
|
| 233 |
- fmt.Fprintf(w, format, |
|
| 234 |
- s.Name, |
|
| 235 |
- s.CPUPercentage, |
|
| 236 |
- units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), |
|
| 237 |
- s.MemoryPercentage, |
|
| 238 |
- units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3), |
|
| 239 |
- units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3), |
|
| 240 |
- s.PidsCurrent) |
|
| 241 |
- } |
|
| 242 |
- return nil |
|
| 243 |
-} |
|
| 244 |
- |
|
| 245 | 200 |
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
| 246 | 201 |
var ( |
| 247 | 202 |
cpuPercent = 0.0 |
| ... | ... |
@@ -1,36 +1,11 @@ |
| 1 | 1 |
package container |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bytes" |
|
| 5 | 4 |
"testing" |
| 6 | 5 |
|
| 7 | 6 |
"github.com/docker/docker/api/types" |
| 8 | 7 |
) |
| 9 | 8 |
|
| 10 |
-func TestDisplay(t *testing.T) {
|
|
| 11 |
- c := &containerStats{
|
|
| 12 |
- Name: "app", |
|
| 13 |
- CPUPercentage: 30.0, |
|
| 14 |
- Memory: 100 * 1024 * 1024.0, |
|
| 15 |
- MemoryLimit: 2048 * 1024 * 1024.0, |
|
| 16 |
- MemoryPercentage: 100.0 / 2048.0 * 100.0, |
|
| 17 |
- NetworkRx: 100 * 1024 * 1024, |
|
| 18 |
- NetworkTx: 800 * 1024 * 1024, |
|
| 19 |
- BlockRead: 100 * 1024 * 1024, |
|
| 20 |
- BlockWrite: 800 * 1024 * 1024, |
|
| 21 |
- PidsCurrent: 1, |
|
| 22 |
- } |
|
| 23 |
- var b bytes.Buffer |
|
| 24 |
- if err := c.Display(&b); err != nil {
|
|
| 25 |
- t.Fatalf("c.Display() gave error: %s", err)
|
|
| 26 |
- } |
|
| 27 |
- got := b.String() |
|
| 28 |
- want := "app\t30.00%\t100 MiB / 2 GiB\t4.88%\t105 MB / 839 MB\t105 MB / 839 MB\t1\n" |
|
| 29 |
- if got != want {
|
|
| 30 |
- t.Fatalf("c.Display() = %q, want %q", got, want)
|
|
| 31 |
- } |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 | 9 |
func TestCalculBlockIO(t *testing.T) {
|
| 35 | 10 |
blkio := types.BlkioStats{
|
| 36 | 11 |
IoServiceBytesRecursive: []types.BlkioStatEntry{{8, 0, "read", 1234}, {8, 1, "read", 4567}, {8, 0, "write", 123}, {8, 1, "write", 456}},
|
| 37 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,135 @@ |
| 0 |
+package formatter |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "sync" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/go-units" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+const ( |
|
| 10 |
+ defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPrec}}\t{{.MemUsage}}\t{{.MemPrec}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
|
|
| 11 |
+ winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPrec}}\t{{{.MemUsage}}\t{.NetIO}}\t{{.BlockIO}}"
|
|
| 12 |
+ emptyStatsTableFormat = "Waiting for statistics..." |
|
| 13 |
+ |
|
| 14 |
+ containerHeader = "CONTAINER" |
|
| 15 |
+ cpuPrecHeader = "CPU %" |
|
| 16 |
+ netIOHeader = "NET I/O" |
|
| 17 |
+ blockIOHeader = "BLOCK I/O" |
|
| 18 |
+ winMemPrecHeader = "PRIV WORKING SET" // Used only on Window |
|
| 19 |
+ memPrecHeader = "MEM %" // Used only on Linux |
|
| 20 |
+ memUseHeader = "MEM USAGE / LIMIT" // Used only on Linux |
|
| 21 |
+ pidsHeader = "PIDS" // Used only on Linux |
|
| 22 |
+) |
|
| 23 |
+ |
|
| 24 |
+// ContainerStatsAttrs represents the statistics data collected from a container. |
|
| 25 |
+type ContainerStatsAttrs struct {
|
|
| 26 |
+ Windows bool |
|
| 27 |
+ Name string |
|
| 28 |
+ CPUPercentage float64 |
|
| 29 |
+ Memory float64 // On Windows this is the private working set |
|
| 30 |
+ MemoryLimit float64 // Not used on Windows |
|
| 31 |
+ MemoryPercentage float64 // Not used on Windows |
|
| 32 |
+ NetworkRx float64 |
|
| 33 |
+ NetworkTx float64 |
|
| 34 |
+ BlockRead float64 |
|
| 35 |
+ BlockWrite float64 |
|
| 36 |
+ PidsCurrent uint64 // Not used on Windows |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+// ContainerStats represents the containers statistics data. |
|
| 40 |
+type ContainerStats struct {
|
|
| 41 |
+ Mu sync.RWMutex |
|
| 42 |
+ ContainerStatsAttrs |
|
| 43 |
+ Err error |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// NewStatsFormat returns a format for rendering an CStatsContext |
|
| 47 |
+func NewStatsFormat(source, osType string) Format {
|
|
| 48 |
+ if source == TableFormatKey {
|
|
| 49 |
+ if osType == "windows" {
|
|
| 50 |
+ return Format(winDefaultStatsTableFormat) |
|
| 51 |
+ } |
|
| 52 |
+ return Format(defaultStatsTableFormat) |
|
| 53 |
+ } |
|
| 54 |
+ return Format(source) |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+// NewContainerStats returns a new ContainerStats entity and sets in it the given name |
|
| 58 |
+func NewContainerStats(name, osType string) *ContainerStats {
|
|
| 59 |
+ return &ContainerStats{
|
|
| 60 |
+ ContainerStatsAttrs: ContainerStatsAttrs{
|
|
| 61 |
+ Name: name, |
|
| 62 |
+ Windows: (osType == "windows"), |
|
| 63 |
+ }, |
|
| 64 |
+ } |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+// ContainerStatsWrite renders the context for a list of containers statistics |
|
| 68 |
+func ContainerStatsWrite(ctx Context, containerStats []*ContainerStats) error {
|
|
| 69 |
+ render := func(format func(subContext subContext) error) error {
|
|
| 70 |
+ for _, cstats := range containerStats {
|
|
| 71 |
+ cstats.Mu.RLock() |
|
| 72 |
+ cstatsAttrs := cstats.ContainerStatsAttrs |
|
| 73 |
+ cstats.Mu.RUnlock() |
|
| 74 |
+ containerStatsCtx := &containerStatsContext{
|
|
| 75 |
+ s: cstatsAttrs, |
|
| 76 |
+ } |
|
| 77 |
+ if err := format(containerStatsCtx); err != nil {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ return nil |
|
| 82 |
+ } |
|
| 83 |
+ return ctx.Write(&containerStatsContext{}, render)
|
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+type containerStatsContext struct {
|
|
| 87 |
+ HeaderContext |
|
| 88 |
+ s ContainerStatsAttrs |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+func (c *containerStatsContext) Container() string {
|
|
| 92 |
+ c.AddHeader(containerHeader) |
|
| 93 |
+ return c.s.Name |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+func (c *containerStatsContext) CPUPrec() string {
|
|
| 97 |
+ c.AddHeader(cpuPrecHeader) |
|
| 98 |
+ return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
|
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func (c *containerStatsContext) MemUsage() string {
|
|
| 102 |
+ c.AddHeader(memUseHeader) |
|
| 103 |
+ if !c.s.Windows {
|
|
| 104 |
+ return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
|
|
| 105 |
+ } |
|
| 106 |
+ return fmt.Sprintf("-- / --")
|
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+func (c *containerStatsContext) MemPrec() string {
|
|
| 110 |
+ header := memPrecHeader |
|
| 111 |
+ if c.s.Windows {
|
|
| 112 |
+ header = winMemPrecHeader |
|
| 113 |
+ } |
|
| 114 |
+ c.AddHeader(header) |
|
| 115 |
+ return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
|
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+func (c *containerStatsContext) NetIO() string {
|
|
| 119 |
+ c.AddHeader(netIOHeader) |
|
| 120 |
+ return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3))
|
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func (c *containerStatsContext) BlockIO() string {
|
|
| 124 |
+ c.AddHeader(blockIOHeader) |
|
| 125 |
+ return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3))
|
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+func (c *containerStatsContext) PIDs() string {
|
|
| 129 |
+ c.AddHeader(pidsHeader) |
|
| 130 |
+ if !c.s.Windows {
|
|
| 131 |
+ return fmt.Sprintf("%d", c.s.PidsCurrent)
|
|
| 132 |
+ } |
|
| 133 |
+ return fmt.Sprintf("-")
|
|
| 134 |
+} |