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