github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/cli/command/container/stats.go (about)

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