github.com/kobeld/docker@v1.12.0-rc1/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  				continue
   101  			}
   102  
   103  			var memPercent = 0.0
   104  			var cpuPercent = 0.0
   105  
   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  
   112  			previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
   113  			previousSystem = v.PreCPUStats.SystemUsage
   114  			cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
   115  			blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
   116  			s.mu.Lock()
   117  			s.CPUPercentage = cpuPercent
   118  			s.Memory = float64(v.MemoryStats.Usage)
   119  			s.MemoryLimit = float64(v.MemoryStats.Limit)
   120  			s.MemoryPercentage = memPercent
   121  			s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
   122  			s.BlockRead = float64(blkRead)
   123  			s.BlockWrite = float64(blkWrite)
   124  			s.PidsCurrent = v.PidsStats.Current
   125  			s.mu.Unlock()
   126  			u <- nil
   127  			if !streamStats {
   128  				return
   129  			}
   130  		}
   131  	}()
   132  	for {
   133  		select {
   134  		case <-time.After(2 * time.Second):
   135  			// zero out the values if we have not received an update within
   136  			// the specified duration.
   137  			s.mu.Lock()
   138  			s.CPUPercentage = 0
   139  			s.Memory = 0
   140  			s.MemoryPercentage = 0
   141  			s.MemoryLimit = 0
   142  			s.NetworkRx = 0
   143  			s.NetworkTx = 0
   144  			s.BlockRead = 0
   145  			s.BlockWrite = 0
   146  			s.PidsCurrent = 0
   147  			s.err = errors.New("timeout waiting for stats")
   148  			s.mu.Unlock()
   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  			if err != nil {
   156  				s.mu.Lock()
   157  				s.err = err
   158  				s.mu.Unlock()
   159  				continue
   160  			}
   161  			s.err = nil
   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 (s *containerStats) Display(w io.Writer) error {
   175  	s.mu.Lock()
   176  	defer s.mu.Unlock()
   177  	// NOTE: if you change this format, you must also change the err format below!
   178  	format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n"
   179  	if s.err != nil {
   180  		format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n"
   181  		errStr := "--"
   182  		fmt.Fprintf(w, format,
   183  			s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr,
   184  		)
   185  		err := s.err
   186  		return err
   187  	}
   188  	fmt.Fprintf(w, format,
   189  		s.Name,
   190  		s.CPUPercentage,
   191  		units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit),
   192  		s.MemoryPercentage,
   193  		units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
   194  		units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite),
   195  		s.PidsCurrent)
   196  	return nil
   197  }
   198  
   199  func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
   200  	var (
   201  		cpuPercent = 0.0
   202  		// calculate the change for the cpu usage of the container in between readings
   203  		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
   204  		// calculate the change for the entire system between readings
   205  		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
   206  	)
   207  
   208  	if systemDelta > 0.0 && cpuDelta > 0.0 {
   209  		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
   210  	}
   211  	return cpuPercent
   212  }
   213  
   214  func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
   215  	for _, bioEntry := range blkio.IoServiceBytesRecursive {
   216  		switch strings.ToLower(bioEntry.Op) {
   217  		case "read":
   218  			blkRead = blkRead + bioEntry.Value
   219  		case "write":
   220  			blkWrite = blkWrite + bioEntry.Value
   221  		}
   222  	}
   223  	return
   224  }
   225  
   226  func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
   227  	var rx, tx float64
   228  
   229  	for _, v := range network {
   230  		rx += float64(v.RxBytes)
   231  		tx += float64(v.TxBytes)
   232  	}
   233  	return rx, tx
   234  }