github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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, cpuPercent float64
    86  				blkRead, blkWrite      uint64 // Only used on Linux
    87  				mem, memLimit, memPerc float64
    88  				pidsStatsCurrent       uint64
    89  			)
    90  
    91  			if err := dec.Decode(&v); err != nil {
    92  				dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body))
    93  				u <- err
    94  				if err == io.EOF {
    95  					break
    96  				}
    97  				time.Sleep(100 * time.Millisecond)
    98  				continue
    99  			}
   100  
   101  			daemonOSType = response.OSType
   102  
   103  			if daemonOSType != "windows" {
   104  				// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
   105  				// got any data from cgroup
   106  				if v.MemoryStats.Limit != 0 {
   107  					memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
   108  				}
   109  				previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
   110  				previousSystem = v.PreCPUStats.SystemUsage
   111  				cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v)
   112  				blkRead, blkWrite = calculateBlockIO(v.BlkioStats)
   113  				mem = float64(v.MemoryStats.Usage)
   114  				memLimit = float64(v.MemoryStats.Limit)
   115  				memPerc = memPercent
   116  				pidsStatsCurrent = v.PidsStats.Current
   117  			} else {
   118  				cpuPercent = calculateCPUPercentWindows(v)
   119  				blkRead = v.StorageStats.ReadSizeBytes
   120  				blkWrite = v.StorageStats.WriteSizeBytes
   121  				mem = float64(v.MemoryStats.PrivateWorkingSet)
   122  			}
   123  			netRx, netTx := calculateNetwork(v.Networks)
   124  			s.SetStatistics(formatter.StatsEntry{
   125  				Name:             v.Name,
   126  				ID:               v.ID,
   127  				CPUPercentage:    cpuPercent,
   128  				Memory:           mem,
   129  				MemoryPercentage: memPerc,
   130  				MemoryLimit:      memLimit,
   131  				NetworkRx:        netRx,
   132  				NetworkTx:        netTx,
   133  				BlockRead:        float64(blkRead),
   134  				BlockWrite:       float64(blkWrite),
   135  				PidsCurrent:      pidsStatsCurrent,
   136  			})
   137  			u <- nil
   138  			if !streamStats {
   139  				return
   140  			}
   141  		}
   142  	}()
   143  	for {
   144  		select {
   145  		case <-time.After(2 * time.Second):
   146  			// zero out the values if we have not received an update within
   147  			// the specified duration.
   148  			s.SetErrorAndReset(errors.New("timeout waiting for stats"))
   149  			// if this is the first stat you get, release WaitGroup
   150  			if !getFirst {
   151  				getFirst = true
   152  				waitFirst.Done()
   153  			}
   154  		case err := <-u:
   155  			s.SetError(err)
   156  			if err == io.EOF {
   157  				break
   158  			}
   159  			if err != nil {
   160  				continue
   161  			}
   162  			// if this is the first stat you get, release WaitGroup
   163  			if !getFirst {
   164  				getFirst = true
   165  				waitFirst.Done()
   166  			}
   167  		}
   168  		if !streamStats {
   169  			return
   170  		}
   171  	}
   172  }
   173  
   174  func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
   175  	var (
   176  		cpuPercent = 0.0
   177  		// calculate the change for the cpu usage of the container in between readings
   178  		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
   179  		// calculate the change for the entire system between readings
   180  		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
   181  	)
   182  
   183  	if systemDelta > 0.0 && cpuDelta > 0.0 {
   184  		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
   185  	}
   186  	return cpuPercent
   187  }
   188  
   189  func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
   190  	// Max number of 100ns intervals between the previous time read and now
   191  	possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals
   192  	possIntervals /= 100                                         // Convert to number of 100ns intervals
   193  	possIntervals *= uint64(v.NumProcs)                          // Multiple by the number of processors
   194  
   195  	// Intervals used
   196  	intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage
   197  
   198  	// Percentage avoiding divide-by-zero
   199  	if possIntervals > 0 {
   200  		return float64(intervalsUsed) / float64(possIntervals) * 100.0
   201  	}
   202  	return 0.00
   203  }
   204  
   205  func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
   206  	for _, bioEntry := range blkio.IoServiceBytesRecursive {
   207  		switch strings.ToLower(bioEntry.Op) {
   208  		case "read":
   209  			blkRead = blkRead + bioEntry.Value
   210  		case "write":
   211  			blkWrite = blkWrite + bioEntry.Value
   212  		}
   213  	}
   214  	return
   215  }
   216  
   217  func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
   218  	var rx, tx float64
   219  
   220  	for _, v := range network {
   221  		rx += float64(v.RxBytes)
   222  		tx += float64(v.TxBytes)
   223  	}
   224  	return rx, tx
   225  }