github.com/olljanat/moby@v1.13.1/cli/command/container/stats_helpers.go (about)

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