DockerCLI/cli/command/container/stats.go

254 lines
6.4 KiB
Go
Raw Normal View History

package container
import (
"context"
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type statsOptions struct {
all bool
noStream bool
Add --no-trunc option to docker container stats This patch adds a `--no-trunc` option to `docker container stats`; With this patch applied, the default output is: CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS b95a83497c91 awesome_brattain 0.28% 5.629MiB / 1.952GiB 0.28% 916B / 0B 147kB / 0B 9 67b2525d8ad1 foobar 0.00% 1.727MiB / 1.952GiB 0.09% 2.48kB / 0B 4.11MB / 0B 2 e5c383697914 test-1951.1.kay7x1lh1twk9c0oig50sd5tr 0.00% 196KiB / 1.952GiB 0.01% 71.2kB / 0B 770kB / 0B 1 4bda148efbc0 random.1.vnc8on831idyr42slu578u3cr 0.00% 1.672MiB / 1.952GiB 0.08% 110kB / 0B 578kB / 0B 2 84e3deaa45b2 registry 0.01% 3.402MiB / 1.952GiB 0.17% 127kB / 378B 233kB / 0B 10 2ed915778ceb foo.1.lsmxrefn5yp9c9ijz1hzgdq4u 0.00% 1.727MiB / 1.952GiB 0.09% 166kB / 7.76kB 614kB / 0B 2 Addin the `--no-trunc` option, changes the output to: CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc awesome_brattain 0.25% 5.75MiB / 1.952GiB 0.29% 648B / 0B 147kB / 0B 10 67b2525d8ad10bb236a49960e93c09993b0baabeef12c2d46cd5f4fbb6f4808c foobar 0.00% 1.727MiB / 1.952GiB 0.09% 2.35kB / 0B 4.11MB / 0B 2 e5c383697914b98b10cbbc9d0bd324b7b927099ac584f031057b8208d2fba9b1 test-1951.1.kay7x1lh1twk9c0oig50sd5tr 0.00% 196KiB / 1.952GiB 0.01% 71.1kB / 0B 770kB / 0B 1 4bda148efbc006b0063373c3678083159af89f8cc83a6a28def14cb0dd171f70 random.1.vnc8on831idyr42slu578u3cr 0.00% 1.672MiB / 1.952GiB 0.08% 110kB / 0B 578kB / 0B 2 84e3deaa45b2fc363e06167df9b90ab59f88d4f101e3f9b8df03a62a8f6783e1 registry 0.00% 3.387MiB / 1.952GiB 0.17% 127kB / 378B 233kB / 0B 10 2ed915778cebddf9ec69263a75cfdcf00962a5198d94d42cda75d5cd45bb82f2 foo.1.lsmxrefn5yp9c9ijz1hzgdq4u 0.00% 1.727MiB / 1.952GiB 0.09% 166kB / 7.76kB 614kB / 0B 2 Which is the same as the default before this patch was applied. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-09-27 12:24:26 -04:00
noTrunc bool
format string
containers []string
}
// NewStatsCommand creates a new cobra.Command for `docker stats`
func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
var opts statsOptions
cmd := &cobra.Command{
Use: "stats [OPTIONS] [CONTAINER...]",
Short: "Display a live stream of container(s) resource usage statistics",
Args: cli.RequiresMinArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runStats(dockerCli, &opts)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
Add --no-trunc option to docker container stats This patch adds a `--no-trunc` option to `docker container stats`; With this patch applied, the default output is: CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS b95a83497c91 awesome_brattain 0.28% 5.629MiB / 1.952GiB 0.28% 916B / 0B 147kB / 0B 9 67b2525d8ad1 foobar 0.00% 1.727MiB / 1.952GiB 0.09% 2.48kB / 0B 4.11MB / 0B 2 e5c383697914 test-1951.1.kay7x1lh1twk9c0oig50sd5tr 0.00% 196KiB / 1.952GiB 0.01% 71.2kB / 0B 770kB / 0B 1 4bda148efbc0 random.1.vnc8on831idyr42slu578u3cr 0.00% 1.672MiB / 1.952GiB 0.08% 110kB / 0B 578kB / 0B 2 84e3deaa45b2 registry 0.01% 3.402MiB / 1.952GiB 0.17% 127kB / 378B 233kB / 0B 10 2ed915778ceb foo.1.lsmxrefn5yp9c9ijz1hzgdq4u 0.00% 1.727MiB / 1.952GiB 0.09% 166kB / 7.76kB 614kB / 0B 2 Addin the `--no-trunc` option, changes the output to: CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc awesome_brattain 0.25% 5.75MiB / 1.952GiB 0.29% 648B / 0B 147kB / 0B 10 67b2525d8ad10bb236a49960e93c09993b0baabeef12c2d46cd5f4fbb6f4808c foobar 0.00% 1.727MiB / 1.952GiB 0.09% 2.35kB / 0B 4.11MB / 0B 2 e5c383697914b98b10cbbc9d0bd324b7b927099ac584f031057b8208d2fba9b1 test-1951.1.kay7x1lh1twk9c0oig50sd5tr 0.00% 196KiB / 1.952GiB 0.01% 71.1kB / 0B 770kB / 0B 1 4bda148efbc006b0063373c3678083159af89f8cc83a6a28def14cb0dd171f70 random.1.vnc8on831idyr42slu578u3cr 0.00% 1.672MiB / 1.952GiB 0.08% 110kB / 0B 578kB / 0B 2 84e3deaa45b2fc363e06167df9b90ab59f88d4f101e3f9b8df03a62a8f6783e1 registry 0.00% 3.387MiB / 1.952GiB 0.17% 127kB / 378B 233kB / 0B 10 2ed915778cebddf9ec69263a75cfdcf00962a5198d94d42cda75d5cd45bb82f2 foo.1.lsmxrefn5yp9c9ijz1hzgdq4u 0.00% 1.727MiB / 1.952GiB 0.09% 166kB / 7.76kB 614kB / 0B 2 Which is the same as the default before this patch was applied. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-09-27 12:24:26 -04:00
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.StringVar(&opts.format, "format", "", flagsHelper.FormatHelp)
return cmd
}
// runStats displays a live stream of resource usage statistics for one or more containers.
// This shows real-time information on CPU usage, memory usage, and network I/O.
// nolint: gocyclo
func runStats(dockerCli command.Cli, opts *statsOptions) error {
showAll := len(opts.containers) == 0
closeChan := make(chan error)
ctx := context.Background()
// monitorContainerEvents watches for container creation and removal (only
// used when calling `docker stats` without arguments).
monitorContainerEvents := func(started chan<- struct{}, c chan events.Message, stopped <-chan struct{}) {
f := filters.NewArgs()
f.Add("type", "container")
options := types.EventsOptions{
Filters: f,
}
eventq, errq := dockerCli.Client().Events(ctx, options)
// Whether we successfully subscribed to eventq or not, we can now
// unblock the main goroutine.
close(started)
defer close(c)
for {
select {
case <-stopped:
return
case event := <-eventq:
c <- event
case err := <-errq:
closeChan <- err
return
}
}
}
// Get the daemonOSType if not set already
if daemonOSType == "" {
svctx := context.Background()
sv, err := dockerCli.Client().ServerVersion(svctx)
if err != nil {
return err
}
daemonOSType = sv.Os
}
// waitFirst is a WaitGroup to wait first stat data's reach for each container
waitFirst := &sync.WaitGroup{}
cStats := stats{}
// getContainerList simulates creation event for all previously existing
// containers (only used when calling `docker stats` without arguments).
getContainerList := func() {
options := types.ContainerListOptions{
All: opts.all,
}
cs, err := dockerCli.Client().ContainerList(ctx, options)
if err != nil {
closeChan <- err
}
for _, container := range cs {
s := NewStats(container.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
}
}
if showAll {
// If no names were specified, start a long running goroutine which
// monitors container events. We make sure we're subscribed before
// retrieving the list of running containers to avoid a race where we
// would "miss" a creation.
started := make(chan struct{})
eh := command.InitEventHandler()
eh.Handle("create", func(e events.Message) {
if opts.all {
s := NewStats(e.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
}
})
eh.Handle("start", func(e events.Message) {
s := NewStats(e.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
})
eh.Handle("die", func(e events.Message) {
if !opts.all {
cStats.remove(e.ID[:12])
}
})
eventChan := make(chan events.Message)
go eh.Watch(eventChan)
stopped := make(chan struct{})
go monitorContainerEvents(started, eventChan, stopped)
defer close(stopped)
<-started
// Start a short-lived goroutine to retrieve the initial list of
// containers.
getContainerList()
// make sure each container get at least one valid stat data
waitFirst.Wait()
} else {
// Artificially send creation events for the containers we were asked to
// monitor (same code path than we use when monitoring all containers).
for _, name := range opts.containers {
s := NewStats(name)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
}
// We don't expect any asynchronous errors: closeChan can be closed.
close(closeChan)
// make sure each container get at least one valid stat data
waitFirst.Wait()
var errs []string
cStats.mu.Lock()
for _, c := range cStats.cs {
if err := c.GetError(); err != nil {
errs = append(errs, err.Error())
}
}
cStats.mu.Unlock()
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
}
format := opts.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().StatsFormat) > 0 {
format = dockerCli.ConfigFile().StatsFormat
} else {
format = formatter.TableFormatKey
}
}
statsCtx := formatter.Context{
Output: dockerCli.Out(),
Format: NewStatsFormat(format, daemonOSType),
}
cleanScreen := func() {
if !opts.noStream {
fmt.Fprint(dockerCli.Out(), "\033[2J")
fmt.Fprint(dockerCli.Out(), "\033[H")
}
}
var err error
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
cleanScreen()
ccstats := []StatsEntry{}
cStats.mu.Lock()
for _, c := range cStats.cs {
ccstats = append(ccstats, c.GetStatistics())
}
cStats.mu.Unlock()
if err = statsFormatWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil {
break
}
if len(cStats.cs) == 0 && !showAll {
break
}
if opts.noStream {
break
}
select {
case err, ok := <-closeChan:
if ok {
if err != nil {
// this is suppressing "unexpected EOF" in the cli when the
// daemon restarts so it shutdowns cleanly
if err == io.ErrUnexpectedEOF {
return nil
}
return err
}
}
default:
// just skip
}
}
return err
}