github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/client/stats.go (about)

     1  package client
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"text/tabwriter"
    11  	"time"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	Cli "github.com/docker/docker/cli"
    15  	"github.com/docker/docker/pkg/jsonmessage"
    16  	"github.com/docker/go-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  	BlockRead        float64
    28  	BlockWrite       float64
    29  	mu               sync.RWMutex
    30  	err              error
    31  }
    32  
    33  type stats struct {
    34  	mu sync.Mutex
    35  	cs []*containerStats
    36  }
    37  
    38  func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
    39  	responseBody, err := cli.client.ContainerStats(s.Name, streamStats)
    40  	if err != nil {
    41  		s.mu.Lock()
    42  		s.err = err
    43  		s.mu.Unlock()
    44  		return
    45  	}
    46  	defer responseBody.Close()
    47  
    48  	var (
    49  		previousCPU    uint64
    50  		previousSystem uint64
    51  		dec            = json.NewDecoder(responseBody)
    52  		u              = make(chan error, 1)
    53  	)
    54  	go func() {
    55  		for {
    56  			var v *types.StatsJSON
    57  			if err := dec.Decode(&v); err != nil {
    58  				u <- err
    59  				return
    60  			}
    61  
    62  			var memPercent = 0.0
    63  			var cpuPercent = 0.0
    64  
    65  			// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
    66  			// got any data from cgroup
    67  			if v.MemoryStats.Limit != 0 {
    68  				memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
    69  			}
    70  
    71  			previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
    72  			previousSystem = v.PreCPUStats.SystemUsage
    73  			cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
    74  			blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
    75  			s.mu.Lock()
    76  			s.CPUPercentage = cpuPercent
    77  			s.Memory = float64(v.MemoryStats.Usage)
    78  			s.MemoryLimit = float64(v.MemoryStats.Limit)
    79  			s.MemoryPercentage = memPercent
    80  			s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
    81  			s.BlockRead = float64(blkRead)
    82  			s.BlockWrite = float64(blkWrite)
    83  			s.mu.Unlock()
    84  			u <- nil
    85  			if !streamStats {
    86  				return
    87  			}
    88  		}
    89  	}()
    90  	for {
    91  		select {
    92  		case <-time.After(2 * time.Second):
    93  			// zero out the values if we have not received an update within
    94  			// the specified duration.
    95  			s.mu.Lock()
    96  			s.CPUPercentage = 0
    97  			s.Memory = 0
    98  			s.MemoryPercentage = 0
    99  			s.MemoryLimit = 0
   100  			s.NetworkRx = 0
   101  			s.NetworkTx = 0
   102  			s.BlockRead = 0
   103  			s.BlockWrite = 0
   104  			s.mu.Unlock()
   105  		case err := <-u:
   106  			if err != nil {
   107  				s.mu.Lock()
   108  				s.err = err
   109  				s.mu.Unlock()
   110  				return
   111  			}
   112  		}
   113  		if !streamStats {
   114  			return
   115  		}
   116  	}
   117  }
   118  
   119  func (s *containerStats) Display(w io.Writer) error {
   120  	s.mu.RLock()
   121  	defer s.mu.RUnlock()
   122  	if s.err != nil {
   123  		return s.err
   124  	}
   125  	fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n",
   126  		s.Name,
   127  		s.CPUPercentage,
   128  		units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
   129  		s.MemoryPercentage,
   130  		units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
   131  		units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
   132  	return nil
   133  }
   134  
   135  // CmdStats displays a live stream of resource usage statistics for one or more containers.
   136  //
   137  // This shows real-time information on CPU usage, memory usage, and network I/O.
   138  //
   139  // Usage: docker stats [OPTIONS] [CONTAINER...]
   140  func (cli *DockerCli) CmdStats(args ...string) error {
   141  	cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
   142  	all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
   143  	noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
   144  
   145  	cmd.ParseFlags(args, true)
   146  
   147  	names := cmd.Args()
   148  	showAll := len(names) == 0
   149  
   150  	if showAll {
   151  		options := types.ContainerListOptions{
   152  			All: *all,
   153  		}
   154  		cs, err := cli.client.ContainerList(options)
   155  		if err != nil {
   156  			return err
   157  		}
   158  		for _, c := range cs {
   159  			names = append(names, c.ID[:12])
   160  		}
   161  	}
   162  	if len(names) == 0 && !showAll {
   163  		return fmt.Errorf("No containers found")
   164  	}
   165  	sort.Strings(names)
   166  
   167  	var (
   168  		cStats = stats{}
   169  		w      = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
   170  	)
   171  	printHeader := func() {
   172  		if !*noStream {
   173  			fmt.Fprint(cli.out, "\033[2J")
   174  			fmt.Fprint(cli.out, "\033[H")
   175  		}
   176  		io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n")
   177  	}
   178  	for _, n := range names {
   179  		s := &containerStats{Name: n}
   180  		// no need to lock here since only the main goroutine is running here
   181  		cStats.cs = append(cStats.cs, s)
   182  		go s.Collect(cli, !*noStream)
   183  	}
   184  	closeChan := make(chan error)
   185  	if showAll {
   186  		type watch struct {
   187  			cid   string
   188  			event string
   189  			err   error
   190  		}
   191  		getNewContainers := func(c chan<- watch) {
   192  			options := types.EventsOptions{}
   193  			resBody, err := cli.client.Events(options)
   194  			if err != nil {
   195  				c <- watch{err: err}
   196  				return
   197  			}
   198  			defer resBody.Close()
   199  
   200  			dec := json.NewDecoder(resBody)
   201  			for {
   202  				var j *jsonmessage.JSONMessage
   203  				if err := dec.Decode(&j); err != nil {
   204  					c <- watch{err: err}
   205  					return
   206  				}
   207  				c <- watch{j.ID[:12], j.Status, nil}
   208  			}
   209  		}
   210  		go func(stopChan chan<- error) {
   211  			cChan := make(chan watch)
   212  			go getNewContainers(cChan)
   213  			for {
   214  				c := <-cChan
   215  				if c.err != nil {
   216  					stopChan <- c.err
   217  					return
   218  				}
   219  				switch c.event {
   220  				case "create":
   221  					s := &containerStats{Name: c.cid}
   222  					cStats.mu.Lock()
   223  					cStats.cs = append(cStats.cs, s)
   224  					cStats.mu.Unlock()
   225  					go s.Collect(cli, !*noStream)
   226  				case "stop":
   227  				case "die":
   228  					if !*all {
   229  						var remove int
   230  						// cStats cannot be O(1) with a map cause ranging over it would cause
   231  						// containers in stats to move up and down in the list...:(
   232  						cStats.mu.Lock()
   233  						for i, s := range cStats.cs {
   234  							if s.Name == c.cid {
   235  								remove = i
   236  								break
   237  							}
   238  						}
   239  						cStats.cs = append(cStats.cs[:remove], cStats.cs[remove+1:]...)
   240  						cStats.mu.Unlock()
   241  					}
   242  				}
   243  			}
   244  		}(closeChan)
   245  	} else {
   246  		close(closeChan)
   247  	}
   248  	// do a quick pause so that any failed connections for containers that do not exist are able to be
   249  	// evicted before we display the initial or default values.
   250  	time.Sleep(1500 * time.Millisecond)
   251  	var errs []string
   252  	cStats.mu.Lock()
   253  	for _, c := range cStats.cs {
   254  		c.mu.Lock()
   255  		if c.err != nil {
   256  			errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
   257  		}
   258  		c.mu.Unlock()
   259  	}
   260  	cStats.mu.Unlock()
   261  	if len(errs) > 0 {
   262  		return fmt.Errorf("%s", strings.Join(errs, ", "))
   263  	}
   264  	for range time.Tick(500 * time.Millisecond) {
   265  		printHeader()
   266  		toRemove := []int{}
   267  		cStats.mu.Lock()
   268  		for i, s := range cStats.cs {
   269  			if err := s.Display(w); err != nil && !*noStream {
   270  				toRemove = append(toRemove, i)
   271  			}
   272  		}
   273  		for j := len(toRemove) - 1; j >= 0; j-- {
   274  			i := toRemove[j]
   275  			cStats.cs = append(cStats.cs[:i], cStats.cs[i+1:]...)
   276  		}
   277  		if len(cStats.cs) == 0 && !showAll {
   278  			return nil
   279  		}
   280  		cStats.mu.Unlock()
   281  		w.Flush()
   282  		if *noStream {
   283  			break
   284  		}
   285  		select {
   286  		case err, ok := <-closeChan:
   287  			if ok {
   288  				if err != nil {
   289  					// this is suppressing "unexpected EOF" in the cli when the
   290  					// daemon restarts so it shutdowns cleanly
   291  					if err == io.ErrUnexpectedEOF {
   292  						return nil
   293  					}
   294  					return err
   295  				}
   296  			}
   297  		default:
   298  			// just skip
   299  		}
   300  	}
   301  	return nil
   302  }
   303  
   304  func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
   305  	var (
   306  		cpuPercent = 0.0
   307  		// calculate the change for the cpu usage of the container in between readings
   308  		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
   309  		// calculate the change for the entire system between readings
   310  		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
   311  	)
   312  
   313  	if systemDelta > 0.0 && cpuDelta > 0.0 {
   314  		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
   315  	}
   316  	return cpuPercent
   317  }
   318  
   319  func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
   320  	for _, bioEntry := range blkio.IoServiceBytesRecursive {
   321  		switch strings.ToLower(bioEntry.Op) {
   322  		case "read":
   323  			blkRead = blkRead + bioEntry.Value
   324  		case "write":
   325  			blkWrite = blkWrite + bioEntry.Value
   326  		}
   327  	}
   328  	return
   329  }
   330  
   331  func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
   332  	var rx, tx float64
   333  
   334  	for _, v := range network {
   335  		rx += float64(v.RxBytes)
   336  		tx += float64(v.TxBytes)
   337  	}
   338  	return rx, tx
   339  }