github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/container/stats_helpers.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/docker/cli/cli/command/formatter"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/client"
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  type stats struct {
    19  	mu sync.Mutex
    20  	cs []*formatter.ContainerStats
    21  }
    22  
    23  // daemonOSType is set once we have at least one stat for a container
    24  // from the daemon. It is used to ensure we print the right header based
    25  // on the daemon platform.
    26  var daemonOSType string
    27  
    28  func (s *stats) add(cs *formatter.ContainerStats) bool {
    29  	s.mu.Lock()
    30  	defer s.mu.Unlock()
    31  	if _, exists := s.isKnownContainer(cs.Container); !exists {
    32  		s.cs = append(s.cs, cs)
    33  		return true
    34  	}
    35  	return false
    36  }
    37  
    38  func (s *stats) remove(id string) {
    39  	s.mu.Lock()
    40  	if i, exists := s.isKnownContainer(id); exists {
    41  		s.cs = append(s.cs[:i], s.cs[i+1:]...)
    42  	}
    43  	s.mu.Unlock()
    44  }
    45  
    46  func (s *stats) isKnownContainer(cid string) (int, bool) {
    47  	for i, c := range s.cs {
    48  		if c.Container == cid {
    49  			return i, true
    50  		}
    51  	}
    52  	return -1, false
    53  }
    54  
    55  func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
    56  	logrus.Debugf("collecting stats for %s", s.Container)
    57  	var (
    58  		getFirst       bool
    59  		previousCPU    uint64
    60  		previousSystem uint64
    61  		u              = make(chan error, 1)
    62  	)
    63  
    64  	defer func() {
    65  		// if error happens and we get nothing of stats, release wait group whatever
    66  		if !getFirst {
    67  			getFirst = true
    68  			waitFirst.Done()
    69  		}
    70  	}()
    71  
    72  	response, err := cli.ContainerStats(ctx, s.Container, streamStats)
    73  	if err != nil {
    74  		s.SetError(err)
    75  		return
    76  	}
    77  	defer response.Body.Close()
    78  
    79  	dec := json.NewDecoder(response.Body)
    80  	go func() {
    81  		for {
    82  			var (
    83  				v                      *types.StatsJSON
    84  				memPercent, cpuPercent float64
    85  				blkRead, blkWrite      uint64 // Only used on Linux
    86  				mem, memLimit          float64
    87  				pidsStatsCurrent       uint64
    88  			)
    89  
    90  			if err := dec.Decode(&v); err != nil {
    91  				dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body))
    92  				u <- err
    93  				if err == io.EOF {
    94  					break
    95  				}
    96  				time.Sleep(100 * time.Millisecond)
    97  				continue
    98  			}
    99  
   100  			daemonOSType = response.OSType
   101  
   102  			if daemonOSType != "windows" {
   103  				previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
   104  				previousSystem = v.PreCPUStats.SystemUsage
   105  				cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v)
   106  				blkRead, blkWrite = calculateBlockIO(v.BlkioStats)
   107  				mem = calculateMemUsageUnixNoCache(v.MemoryStats)
   108  				memLimit = float64(v.MemoryStats.Limit)
   109  				memPercent = calculateMemPercentUnixNoCache(memLimit, mem)
   110  				pidsStatsCurrent = v.PidsStats.Current
   111  			} else {
   112  				cpuPercent = calculateCPUPercentWindows(v)
   113  				blkRead = v.StorageStats.ReadSizeBytes
   114  				blkWrite = v.StorageStats.WriteSizeBytes
   115  				mem = float64(v.MemoryStats.PrivateWorkingSet)
   116  			}
   117  			netRx, netTx := calculateNetwork(v.Networks)
   118  			s.SetStatistics(formatter.StatsEntry{
   119  				Name:             v.Name,
   120  				ID:               v.ID,
   121  				CPUPercentage:    cpuPercent,
   122  				Memory:           mem,
   123  				MemoryPercentage: memPercent,
   124  				MemoryLimit:      memLimit,
   125  				NetworkRx:        netRx,
   126  				NetworkTx:        netTx,
   127  				BlockRead:        float64(blkRead),
   128  				BlockWrite:       float64(blkWrite),
   129  				PidsCurrent:      pidsStatsCurrent,
   130  			})
   131  			u <- nil
   132  			if !streamStats {
   133  				return
   134  			}
   135  		}
   136  	}()
   137  	for {
   138  		select {
   139  		case <-time.After(2 * time.Second):
   140  			// zero out the values if we have not received an update within
   141  			// the specified duration.
   142  			s.SetErrorAndReset(errors.New("timeout waiting for stats"))
   143  			// if this is the first stat you get, release WaitGroup
   144  			if !getFirst {
   145  				getFirst = true
   146  				waitFirst.Done()
   147  			}
   148  		case err := <-u:
   149  			s.SetError(err)
   150  			if err == io.EOF {
   151  				break
   152  			}
   153  			if err != nil {
   154  				continue
   155  			}
   156  			// if this is the first stat you get, release WaitGroup
   157  			if !getFirst {
   158  				getFirst = true
   159  				waitFirst.Done()
   160  			}
   161  		}
   162  		if !streamStats {
   163  			return
   164  		}
   165  	}
   166  }
   167  
   168  func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
   169  	var (
   170  		cpuPercent = 0.0
   171  		// calculate the change for the cpu usage of the container in between readings
   172  		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
   173  		// calculate the change for the entire system between readings
   174  		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
   175  		onlineCPUs  = float64(v.CPUStats.OnlineCPUs)
   176  	)
   177  
   178  	if onlineCPUs == 0.0 {
   179  		onlineCPUs = float64(len(v.CPUStats.CPUUsage.PercpuUsage))
   180  	}
   181  	if systemDelta > 0.0 && cpuDelta > 0.0 {
   182  		cpuPercent = (cpuDelta / systemDelta) * onlineCPUs * 100.0
   183  	}
   184  	return cpuPercent
   185  }
   186  
   187  func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
   188  	// Max number of 100ns intervals between the previous time read and now
   189  	possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals
   190  	possIntervals /= 100                                         // Convert to number of 100ns intervals
   191  	possIntervals *= uint64(v.NumProcs)                          // Multiple by the number of processors
   192  
   193  	// Intervals used
   194  	intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage
   195  
   196  	// Percentage avoiding divide-by-zero
   197  	if possIntervals > 0 {
   198  		return float64(intervalsUsed) / float64(possIntervals) * 100.0
   199  	}
   200  	return 0.00
   201  }
   202  
   203  func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
   204  	var blkRead, blkWrite uint64
   205  	for _, bioEntry := range blkio.IoServiceBytesRecursive {
   206  		switch strings.ToLower(bioEntry.Op) {
   207  		case "read":
   208  			blkRead = blkRead + bioEntry.Value
   209  		case "write":
   210  			blkWrite = blkWrite + bioEntry.Value
   211  		}
   212  	}
   213  	return blkRead, blkWrite
   214  }
   215  
   216  func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
   217  	var rx, tx float64
   218  
   219  	for _, v := range network {
   220  		rx += float64(v.RxBytes)
   221  		tx += float64(v.TxBytes)
   222  	}
   223  	return rx, tx
   224  }
   225  
   226  // calculateMemUsageUnixNoCache calculate memory usage of the container.
   227  // Page cache is intentionally excluded to avoid misinterpretation of the output.
   228  func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 {
   229  	return float64(mem.Usage - mem.Stats["cache"])
   230  }
   231  
   232  func calculateMemPercentUnixNoCache(limit float64, usedNoCache float64) float64 {
   233  	// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
   234  	// got any data from cgroup
   235  	if limit != 0 {
   236  		return usedNoCache / limit * 100.0
   237  	}
   238  	return 0
   239  }