github.com/portworx/docker@v1.12.1/api/client/container/stats_helpers.go (about)

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