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  }