github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/api/client/stats_helpers.go (about)

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