github.com/olljanat/moby@v1.13.1/cli/command/formatter/stats.go (about)

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