github.com/circular-dark/docker@v1.7.0/api/client/stats.go (about)

     1  package client
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/url"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"text/tabwriter"
    12  	"time"
    13  
    14  	"github.com/docker/docker/api/types"
    15  	flag "github.com/docker/docker/pkg/mflag"
    16  	"github.com/docker/docker/pkg/units"
    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  	mu               sync.RWMutex
    28  	err              error
    29  }
    30  
    31  func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
    32  	v := url.Values{}
    33  	if streamStats {
    34  		v.Set("stream", "1")
    35  	} else {
    36  		v.Set("stream", "0")
    37  	}
    38  	stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
    39  	if err != nil {
    40  		s.mu.Lock()
    41  		s.err = err
    42  		s.mu.Unlock()
    43  		return
    44  	}
    45  	defer stream.Close()
    46  	var (
    47  		previousCPU    uint64
    48  		previousSystem uint64
    49  		dec            = json.NewDecoder(stream)
    50  		u              = make(chan error, 1)
    51  	)
    52  	go func() {
    53  		for {
    54  			var v *types.Stats
    55  			if err := dec.Decode(&v); err != nil {
    56  				u <- err
    57  				return
    58  			}
    59  			var (
    60  				memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
    61  				cpuPercent = 0.0
    62  			)
    63  			previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
    64  			previousSystem = v.PreCpuStats.SystemUsage
    65  			cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
    66  			s.mu.Lock()
    67  			s.CPUPercentage = cpuPercent
    68  			s.Memory = float64(v.MemoryStats.Usage)
    69  			s.MemoryLimit = float64(v.MemoryStats.Limit)
    70  			s.MemoryPercentage = memPercent
    71  			s.NetworkRx = float64(v.Network.RxBytes)
    72  			s.NetworkTx = float64(v.Network.TxBytes)
    73  			s.mu.Unlock()
    74  			u <- nil
    75  			if !streamStats {
    76  				return
    77  			}
    78  		}
    79  	}()
    80  	for {
    81  		select {
    82  		case <-time.After(2 * time.Second):
    83  			// zero out the values if we have not received an update within
    84  			// the specified duration.
    85  			s.mu.Lock()
    86  			s.CPUPercentage = 0
    87  			s.Memory = 0
    88  			s.MemoryPercentage = 0
    89  			s.mu.Unlock()
    90  		case err := <-u:
    91  			if err != nil {
    92  				s.mu.Lock()
    93  				s.err = err
    94  				s.mu.Unlock()
    95  				return
    96  			}
    97  		}
    98  		if !streamStats {
    99  			return
   100  		}
   101  	}
   102  }
   103  
   104  func (s *containerStats) Display(w io.Writer) error {
   105  	s.mu.RLock()
   106  	defer s.mu.RUnlock()
   107  	if s.err != nil {
   108  		return s.err
   109  	}
   110  	fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n",
   111  		s.Name,
   112  		s.CPUPercentage,
   113  		units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
   114  		s.MemoryPercentage,
   115  		units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx))
   116  	return nil
   117  }
   118  
   119  // CmdStats displays a live stream of resource usage statistics for one or more containers.
   120  //
   121  // This shows real-time information on CPU usage, memory usage, and network I/O.
   122  //
   123  // Usage: docker stats CONTAINER [CONTAINER...]
   124  func (cli *DockerCli) CmdStats(args ...string) error {
   125  	cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true)
   126  	noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
   127  	cmd.Require(flag.Min, 1)
   128  	cmd.ParseFlags(args, true)
   129  
   130  	names := cmd.Args()
   131  	sort.Strings(names)
   132  	var (
   133  		cStats []*containerStats
   134  		w      = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
   135  	)
   136  	printHeader := func() {
   137  		if !*noStream {
   138  			fmt.Fprint(cli.out, "\033[2J")
   139  			fmt.Fprint(cli.out, "\033[H")
   140  		}
   141  		io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n")
   142  	}
   143  	for _, n := range names {
   144  		s := &containerStats{Name: n}
   145  		cStats = append(cStats, s)
   146  		go s.Collect(cli, !*noStream)
   147  	}
   148  	// do a quick pause so that any failed connections for containers that do not exist are able to be
   149  	// evicted before we display the initial or default values.
   150  	time.Sleep(1500 * time.Millisecond)
   151  	var errs []string
   152  	for _, c := range cStats {
   153  		c.mu.Lock()
   154  		if c.err != nil {
   155  			errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
   156  		}
   157  		c.mu.Unlock()
   158  	}
   159  	if len(errs) > 0 {
   160  		return fmt.Errorf("%s", strings.Join(errs, ", "))
   161  	}
   162  	for range time.Tick(500 * time.Millisecond) {
   163  		printHeader()
   164  		toRemove := []int{}
   165  		for i, s := range cStats {
   166  			if err := s.Display(w); err != nil && !*noStream {
   167  				toRemove = append(toRemove, i)
   168  			}
   169  		}
   170  		for j := len(toRemove) - 1; j >= 0; j-- {
   171  			i := toRemove[j]
   172  			cStats = append(cStats[:i], cStats[i+1:]...)
   173  		}
   174  		if len(cStats) == 0 {
   175  			return nil
   176  		}
   177  		w.Flush()
   178  		if *noStream {
   179  			break
   180  		}
   181  	}
   182  	return nil
   183  }
   184  
   185  func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
   186  	var (
   187  		cpuPercent = 0.0
   188  		// calculate the change for the cpu usage of the container in between readings
   189  		cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU)
   190  		// calculate the change for the entire system between readings
   191  		systemDelta = float64(v.CpuStats.SystemUsage - previousSystem)
   192  	)
   193  
   194  	if systemDelta > 0.0 && cpuDelta > 0.0 {
   195  		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
   196  	}
   197  	return cpuPercent
   198  }