github.com/a4a881d4/docker@v1.9.0-rc2/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  	Cli "github.com/docker/docker/cli"
    16  	flag "github.com/docker/docker/pkg/mflag"
    17  	"github.com/docker/docker/pkg/units"
    18  )
    19  
    20  type containerStats struct {
    21  	Name             string
    22  	CPUPercentage    float64
    23  	Memory           float64
    24  	MemoryLimit      float64
    25  	MemoryPercentage float64
    26  	NetworkRx        float64
    27  	NetworkTx        float64
    28  	BlockRead        float64
    29  	BlockWrite       float64
    30  	mu               sync.RWMutex
    31  	err              error
    32  }
    33  
    34  func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
    35  	v := url.Values{}
    36  	if streamStats {
    37  		v.Set("stream", "1")
    38  	} else {
    39  		v.Set("stream", "0")
    40  	}
    41  	serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
    42  	if err != nil {
    43  		s.mu.Lock()
    44  		s.err = err
    45  		s.mu.Unlock()
    46  		return
    47  	}
    48  
    49  	defer serverResp.body.Close()
    50  
    51  	var (
    52  		previousCPU    uint64
    53  		previousSystem uint64
    54  		dec            = json.NewDecoder(serverResp.body)
    55  		u              = make(chan error, 1)
    56  	)
    57  	go func() {
    58  		for {
    59  			var v *types.StatsJSON
    60  			if err := dec.Decode(&v); err != nil {
    61  				u <- err
    62  				return
    63  			}
    64  
    65  			var memPercent = 0.0
    66  			var cpuPercent = 0.0
    67  
    68  			// MemoryStats.Limit will never be 0 unless the container is not running and we havn't
    69  			// got any data from cgroup
    70  			if v.MemoryStats.Limit != 0 {
    71  				memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
    72  			}
    73  
    74  			previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
    75  			previousSystem = v.PreCPUStats.SystemUsage
    76  			cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
    77  			blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
    78  			s.mu.Lock()
    79  			s.CPUPercentage = cpuPercent
    80  			s.Memory = float64(v.MemoryStats.Usage)
    81  			s.MemoryLimit = float64(v.MemoryStats.Limit)
    82  			s.MemoryPercentage = memPercent
    83  			s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
    84  			s.BlockRead = float64(blkRead)
    85  			s.BlockWrite = float64(blkWrite)
    86  			s.mu.Unlock()
    87  			u <- nil
    88  			if !streamStats {
    89  				return
    90  			}
    91  		}
    92  	}()
    93  	for {
    94  		select {
    95  		case <-time.After(2 * time.Second):
    96  			// zero out the values if we have not received an update within
    97  			// the specified duration.
    98  			s.mu.Lock()
    99  			s.CPUPercentage = 0
   100  			s.Memory = 0
   101  			s.MemoryPercentage = 0
   102  			s.MemoryLimit = 0
   103  			s.NetworkRx = 0
   104  			s.NetworkTx = 0
   105  			s.BlockRead = 0
   106  			s.BlockWrite = 0
   107  			s.mu.Unlock()
   108  		case err := <-u:
   109  			if err != nil {
   110  				s.mu.Lock()
   111  				s.err = err
   112  				s.mu.Unlock()
   113  				return
   114  			}
   115  		}
   116  		if !streamStats {
   117  			return
   118  		}
   119  	}
   120  }
   121  
   122  func (s *containerStats) Display(w io.Writer) error {
   123  	s.mu.RLock()
   124  	defer s.mu.RUnlock()
   125  	if s.err != nil {
   126  		return s.err
   127  	}
   128  	fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n",
   129  		s.Name,
   130  		s.CPUPercentage,
   131  		units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
   132  		s.MemoryPercentage,
   133  		units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
   134  		units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
   135  	return nil
   136  }
   137  
   138  // CmdStats displays a live stream of resource usage statistics for one or more containers.
   139  //
   140  // This shows real-time information on CPU usage, memory usage, and network I/O.
   141  //
   142  // Usage: docker stats CONTAINER [CONTAINER...]
   143  func (cli *DockerCli) CmdStats(args ...string) error {
   144  	cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
   145  	noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
   146  	cmd.Require(flag.Min, 1)
   147  
   148  	cmd.ParseFlags(args, true)
   149  
   150  	names := cmd.Args()
   151  	sort.Strings(names)
   152  	var (
   153  		cStats []*containerStats
   154  		w      = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
   155  	)
   156  	printHeader := func() {
   157  		if !*noStream {
   158  			fmt.Fprint(cli.out, "\033[2J")
   159  			fmt.Fprint(cli.out, "\033[H")
   160  		}
   161  		io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n")
   162  	}
   163  	for _, n := range names {
   164  		s := &containerStats{Name: n}
   165  		cStats = append(cStats, s)
   166  		go s.Collect(cli, !*noStream)
   167  	}
   168  	// do a quick pause so that any failed connections for containers that do not exist are able to be
   169  	// evicted before we display the initial or default values.
   170  	time.Sleep(1500 * time.Millisecond)
   171  	var errs []string
   172  	for _, c := range cStats {
   173  		c.mu.Lock()
   174  		if c.err != nil {
   175  			errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
   176  		}
   177  		c.mu.Unlock()
   178  	}
   179  	if len(errs) > 0 {
   180  		return fmt.Errorf("%s", strings.Join(errs, ", "))
   181  	}
   182  	for range time.Tick(500 * time.Millisecond) {
   183  		printHeader()
   184  		toRemove := []int{}
   185  		for i, s := range cStats {
   186  			if err := s.Display(w); err != nil && !*noStream {
   187  				toRemove = append(toRemove, i)
   188  			}
   189  		}
   190  		for j := len(toRemove) - 1; j >= 0; j-- {
   191  			i := toRemove[j]
   192  			cStats = append(cStats[:i], cStats[i+1:]...)
   193  		}
   194  		if len(cStats) == 0 {
   195  			return nil
   196  		}
   197  		w.Flush()
   198  		if *noStream {
   199  			break
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
   206  	var (
   207  		cpuPercent = 0.0
   208  		// calculate the change for the cpu usage of the container in between readings
   209  		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU)
   210  		// calculate the change for the entire system between readings
   211  		systemDelta = float64(v.CPUStats.SystemUsage - previousSystem)
   212  	)
   213  
   214  	if systemDelta > 0.0 && cpuDelta > 0.0 {
   215  		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
   216  	}
   217  	return cpuPercent
   218  }
   219  
   220  func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
   221  	for _, bioEntry := range blkio.IoServiceBytesRecursive {
   222  		switch strings.ToLower(bioEntry.Op) {
   223  		case "read":
   224  			blkRead = blkRead + bioEntry.Value
   225  		case "write":
   226  			blkWrite = blkWrite + bioEntry.Value
   227  		}
   228  	}
   229  	return
   230  }
   231  
   232  func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
   233  	var rx, tx float64
   234  
   235  	for _, v := range network {
   236  		rx += float64(v.RxBytes)
   237  		tx += float64(v.TxBytes)
   238  	}
   239  	return rx, tx
   240  }