github.com/portworx/docker@v1.12.1/api/client/container/stats.go (about)

     1  package container
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strings"
     7  	"sync"
     8  	"text/tabwriter"
     9  	"time"
    10  
    11  	"golang.org/x/net/context"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/api/client"
    15  	"github.com/docker/docker/api/client/system"
    16  	"github.com/docker/docker/cli"
    17  	"github.com/docker/engine-api/types"
    18  	"github.com/docker/engine-api/types/events"
    19  	"github.com/docker/engine-api/types/filters"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  type statsOptions struct {
    24  	all      bool
    25  	noStream bool
    26  
    27  	containers []string
    28  }
    29  
    30  // NewStatsCommand creats a new cobra.Command for `docker stats`
    31  func NewStatsCommand(dockerCli *client.DockerCli) *cobra.Command {
    32  	var opts statsOptions
    33  
    34  	cmd := &cobra.Command{
    35  		Use:   "stats [OPTIONS] [CONTAINER...]",
    36  		Short: "Display a live stream of container(s) resource usage statistics",
    37  		Args:  cli.RequiresMinArgs(0),
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			opts.containers = args
    40  			return runStats(dockerCli, &opts)
    41  		},
    42  	}
    43  
    44  	flags := cmd.Flags()
    45  	flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
    46  	flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
    47  	return cmd
    48  }
    49  
    50  // runStats displays a live stream of resource usage statistics for one or more containers.
    51  // This shows real-time information on CPU usage, memory usage, and network I/O.
    52  func runStats(dockerCli *client.DockerCli, opts *statsOptions) error {
    53  	showAll := len(opts.containers) == 0
    54  	closeChan := make(chan error)
    55  
    56  	ctx := context.Background()
    57  
    58  	// monitorContainerEvents watches for container creation and removal (only
    59  	// used when calling `docker stats` without arguments).
    60  	monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
    61  		f := filters.NewArgs()
    62  		f.Add("type", "container")
    63  		options := types.EventsOptions{
    64  			Filters: f,
    65  		}
    66  		resBody, err := dockerCli.Client().Events(ctx, options)
    67  		// Whether we successfully subscribed to events or not, we can now
    68  		// unblock the main goroutine.
    69  		close(started)
    70  		if err != nil {
    71  			closeChan <- err
    72  			return
    73  		}
    74  		defer resBody.Close()
    75  
    76  		system.DecodeEvents(resBody, func(event events.Message, err error) error {
    77  			if err != nil {
    78  				closeChan <- err
    79  				return nil
    80  			}
    81  			c <- event
    82  			return nil
    83  		})
    84  	}
    85  
    86  	// waitFirst is a WaitGroup to wait first stat data's reach for each container
    87  	waitFirst := &sync.WaitGroup{}
    88  
    89  	cStats := stats{}
    90  	// getContainerList simulates creation event for all previously existing
    91  	// containers (only used when calling `docker stats` without arguments).
    92  	getContainerList := func() {
    93  		options := types.ContainerListOptions{
    94  			All: opts.all,
    95  		}
    96  		cs, err := dockerCli.Client().ContainerList(ctx, options)
    97  		if err != nil {
    98  			closeChan <- err
    99  		}
   100  		for _, container := range cs {
   101  			s := &containerStats{Name: container.ID[:12]}
   102  			if cStats.add(s) {
   103  				waitFirst.Add(1)
   104  				go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
   105  			}
   106  		}
   107  	}
   108  
   109  	if showAll {
   110  		// If no names were specified, start a long running goroutine which
   111  		// monitors container events. We make sure we're subscribed before
   112  		// retrieving the list of running containers to avoid a race where we
   113  		// would "miss" a creation.
   114  		started := make(chan struct{})
   115  		eh := system.InitEventHandler()
   116  		eh.Handle("create", func(e events.Message) {
   117  			if opts.all {
   118  				s := &containerStats{Name: e.ID[:12]}
   119  				if cStats.add(s) {
   120  					waitFirst.Add(1)
   121  					go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
   122  				}
   123  			}
   124  		})
   125  
   126  		eh.Handle("start", func(e events.Message) {
   127  			s := &containerStats{Name: e.ID[:12]}
   128  			if cStats.add(s) {
   129  				waitFirst.Add(1)
   130  				go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
   131  			}
   132  		})
   133  
   134  		eh.Handle("die", func(e events.Message) {
   135  			if !opts.all {
   136  				cStats.remove(e.ID[:12])
   137  			}
   138  		})
   139  
   140  		eventChan := make(chan events.Message)
   141  		go eh.Watch(eventChan)
   142  		go monitorContainerEvents(started, eventChan)
   143  		defer close(eventChan)
   144  		<-started
   145  
   146  		// Start a short-lived goroutine to retrieve the initial list of
   147  		// containers.
   148  		getContainerList()
   149  	} else {
   150  		// Artificially send creation events for the containers we were asked to
   151  		// monitor (same code path than we use when monitoring all containers).
   152  		for _, name := range opts.containers {
   153  			s := &containerStats{Name: name}
   154  			if cStats.add(s) {
   155  				waitFirst.Add(1)
   156  				go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
   157  			}
   158  		}
   159  
   160  		// We don't expect any asynchronous errors: closeChan can be closed.
   161  		close(closeChan)
   162  
   163  		// Do a quick pause to detect any error with the provided list of
   164  		// container names.
   165  		time.Sleep(1500 * time.Millisecond)
   166  		var errs []string
   167  		cStats.mu.Lock()
   168  		for _, c := range cStats.cs {
   169  			c.mu.Lock()
   170  			if c.err != nil {
   171  				errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
   172  			}
   173  			c.mu.Unlock()
   174  		}
   175  		cStats.mu.Unlock()
   176  		if len(errs) > 0 {
   177  			return fmt.Errorf("%s", strings.Join(errs, ", "))
   178  		}
   179  	}
   180  
   181  	// before print to screen, make sure each container get at least one valid stat data
   182  	waitFirst.Wait()
   183  
   184  	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
   185  	printHeader := func() {
   186  		if !opts.noStream {
   187  			fmt.Fprint(dockerCli.Out(), "\033[2J")
   188  			fmt.Fprint(dockerCli.Out(), "\033[H")
   189  		}
   190  		io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
   191  	}
   192  
   193  	for range time.Tick(500 * time.Millisecond) {
   194  		printHeader()
   195  		toRemove := []string{}
   196  		cStats.mu.Lock()
   197  		for _, s := range cStats.cs {
   198  			if err := s.Display(w); err != nil && !opts.noStream {
   199  				logrus.Debugf("stats: got error for %s: %v", s.Name, err)
   200  				if err == io.EOF {
   201  					toRemove = append(toRemove, s.Name)
   202  				}
   203  			}
   204  		}
   205  		cStats.mu.Unlock()
   206  		for _, name := range toRemove {
   207  			cStats.remove(name)
   208  		}
   209  		if len(cStats.cs) == 0 && !showAll {
   210  			return nil
   211  		}
   212  		w.Flush()
   213  		if opts.noStream {
   214  			break
   215  		}
   216  		select {
   217  		case err, ok := <-closeChan:
   218  			if ok {
   219  				if err != nil {
   220  					// this is suppressing "unexpected EOF" in the cli when the
   221  					// daemon restarts so it shutdowns cleanly
   222  					if err == io.ErrUnexpectedEOF {
   223  						return nil
   224  					}
   225  					return err
   226  				}
   227  			}
   228  		default:
   229  			// just skip
   230  		}
   231  	}
   232  	return nil
   233  }