2015-03-24 23:57:23 -04:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-02-13 11:45:04 -05:00
|
|
|
"net/url"
|
2015-03-24 23:57:23 -04:00
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"text/tabwriter"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
2015-05-05 00:18:28 -04:00
|
|
|
Cli "github.com/docker/docker/cli"
|
2015-03-24 23:57:23 -04:00
|
|
|
flag "github.com/docker/docker/pkg/mflag"
|
|
|
|
"github.com/docker/docker/pkg/units"
|
|
|
|
)
|
|
|
|
|
|
|
|
type containerStats struct {
|
|
|
|
Name string
|
2015-03-25 22:31:29 -04:00
|
|
|
CPUPercentage float64
|
2015-03-24 23:57:23 -04:00
|
|
|
Memory float64
|
|
|
|
MemoryLimit float64
|
|
|
|
MemoryPercentage float64
|
|
|
|
NetworkRx float64
|
|
|
|
NetworkTx float64
|
|
|
|
mu sync.RWMutex
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2015-02-13 11:45:04 -05:00
|
|
|
func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|
|
|
v := url.Values{}
|
|
|
|
if streamStats {
|
|
|
|
v.Set("stream", "1")
|
|
|
|
} else {
|
|
|
|
v.Set("stream", "0")
|
|
|
|
}
|
2015-07-09 22:05:50 -04:00
|
|
|
serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
|
2015-03-24 23:57:23 -04:00
|
|
|
if err != nil {
|
2015-05-24 16:26:50 -04:00
|
|
|
s.mu.Lock()
|
2015-03-24 23:57:23 -04:00
|
|
|
s.err = err
|
2015-05-24 16:26:50 -04:00
|
|
|
s.mu.Unlock()
|
2015-03-24 23:57:23 -04:00
|
|
|
return
|
|
|
|
}
|
2015-07-03 02:19:23 -04:00
|
|
|
|
2015-07-09 22:05:50 -04:00
|
|
|
defer serverResp.body.Close()
|
2015-07-03 02:19:23 -04:00
|
|
|
|
2015-03-24 23:57:23 -04:00
|
|
|
var (
|
2015-03-25 22:31:29 -04:00
|
|
|
previousCPU uint64
|
2015-03-24 23:57:23 -04:00
|
|
|
previousSystem uint64
|
2015-07-09 22:05:50 -04:00
|
|
|
dec = json.NewDecoder(serverResp.body)
|
2015-03-24 23:57:23 -04:00
|
|
|
u = make(chan error, 1)
|
|
|
|
)
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
var v *types.Stats
|
|
|
|
if err := dec.Decode(&v); err != nil {
|
|
|
|
u <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
|
|
|
cpuPercent = 0.0
|
|
|
|
)
|
2015-05-30 13:25:51 -04:00
|
|
|
previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
|
|
|
|
previousSystem = v.PreCpuStats.SystemUsage
|
|
|
|
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
2015-03-24 23:57:23 -04:00
|
|
|
s.mu.Lock()
|
2015-03-25 22:31:29 -04:00
|
|
|
s.CPUPercentage = cpuPercent
|
2015-03-24 23:57:23 -04:00
|
|
|
s.Memory = float64(v.MemoryStats.Usage)
|
|
|
|
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
|
|
|
s.MemoryPercentage = memPercent
|
|
|
|
s.NetworkRx = float64(v.Network.RxBytes)
|
|
|
|
s.NetworkTx = float64(v.Network.TxBytes)
|
|
|
|
s.mu.Unlock()
|
|
|
|
u <- nil
|
2015-02-13 11:45:04 -05:00
|
|
|
if !streamStats {
|
|
|
|
return
|
|
|
|
}
|
2015-03-24 23:57:23 -04:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(2 * time.Second):
|
|
|
|
// zero out the values if we have not received an update within
|
|
|
|
// the specified duration.
|
|
|
|
s.mu.Lock()
|
2015-03-25 22:31:29 -04:00
|
|
|
s.CPUPercentage = 0
|
2015-03-24 23:57:23 -04:00
|
|
|
s.Memory = 0
|
|
|
|
s.MemoryPercentage = 0
|
|
|
|
s.mu.Unlock()
|
|
|
|
case err := <-u:
|
|
|
|
if err != nil {
|
|
|
|
s.mu.Lock()
|
|
|
|
s.err = err
|
|
|
|
s.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2015-02-13 11:45:04 -05:00
|
|
|
if !streamStats {
|
|
|
|
return
|
|
|
|
}
|
2015-03-24 23:57:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *containerStats) Display(w io.Writer) error {
|
|
|
|
s.mu.RLock()
|
|
|
|
defer s.mu.RUnlock()
|
|
|
|
if s.err != nil {
|
|
|
|
return s.err
|
|
|
|
}
|
2015-07-29 07:12:57 -04:00
|
|
|
fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\n",
|
2015-03-24 23:57:23 -04:00
|
|
|
s.Name,
|
2015-03-25 22:31:29 -04:00
|
|
|
s.CPUPercentage,
|
2015-04-08 00:25:41 -04:00
|
|
|
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
2015-03-24 23:57:23 -04:00
|
|
|
s.MemoryPercentage,
|
2015-04-08 00:25:41 -04:00
|
|
|
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx))
|
2015-03-24 23:57:23 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-25 13:34:41 -04:00
|
|
|
// CmdStats 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.
|
|
|
|
//
|
|
|
|
// Usage: docker stats CONTAINER [CONTAINER...]
|
2015-03-24 23:57:23 -04:00
|
|
|
func (cli *DockerCli) CmdStats(args ...string) error {
|
2015-05-05 00:18:28 -04:00
|
|
|
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true)
|
2015-02-13 11:45:04 -05:00
|
|
|
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
2015-03-24 23:57:23 -04:00
|
|
|
cmd.Require(flag.Min, 1)
|
2015-07-03 05:26:09 -04:00
|
|
|
|
2015-03-28 21:22:46 -04:00
|
|
|
cmd.ParseFlags(args, true)
|
2015-03-24 23:57:23 -04:00
|
|
|
|
|
|
|
names := cmd.Args()
|
|
|
|
sort.Strings(names)
|
|
|
|
var (
|
|
|
|
cStats []*containerStats
|
|
|
|
w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
|
|
)
|
|
|
|
printHeader := func() {
|
2015-02-13 11:45:04 -05:00
|
|
|
if !*noStream {
|
|
|
|
fmt.Fprint(cli.out, "\033[2J")
|
|
|
|
fmt.Fprint(cli.out, "\033[H")
|
|
|
|
}
|
2015-07-29 07:12:57 -04:00
|
|
|
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\n")
|
2015-03-24 23:57:23 -04:00
|
|
|
}
|
|
|
|
for _, n := range names {
|
|
|
|
s := &containerStats{Name: n}
|
|
|
|
cStats = append(cStats, s)
|
2015-02-13 11:45:04 -05:00
|
|
|
go s.Collect(cli, !*noStream)
|
2015-03-24 23:57:23 -04:00
|
|
|
}
|
|
|
|
// do a quick pause so that any failed connections for containers that do not exist are able to be
|
|
|
|
// evicted before we display the initial or default values.
|
2015-05-30 13:25:51 -04:00
|
|
|
time.Sleep(1500 * time.Millisecond)
|
2015-03-24 23:57:23 -04:00
|
|
|
var errs []string
|
|
|
|
for _, c := range cStats {
|
|
|
|
c.mu.Lock()
|
|
|
|
if c.err != nil {
|
|
|
|
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
|
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
|
|
return fmt.Errorf("%s", strings.Join(errs, ", "))
|
|
|
|
}
|
2015-04-20 04:08:01 -04:00
|
|
|
for range time.Tick(500 * time.Millisecond) {
|
2015-03-24 23:57:23 -04:00
|
|
|
printHeader()
|
|
|
|
toRemove := []int{}
|
|
|
|
for i, s := range cStats {
|
2015-02-13 11:45:04 -05:00
|
|
|
if err := s.Display(w); err != nil && !*noStream {
|
2015-03-24 23:57:23 -04:00
|
|
|
toRemove = append(toRemove, i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for j := len(toRemove) - 1; j >= 0; j-- {
|
|
|
|
i := toRemove[j]
|
|
|
|
cStats = append(cStats[:i], cStats[i+1:]...)
|
|
|
|
}
|
|
|
|
if len(cStats) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
w.Flush()
|
2015-02-13 11:45:04 -05:00
|
|
|
if *noStream {
|
|
|
|
break
|
|
|
|
}
|
2015-03-24 23:57:23 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-25 22:31:29 -04:00
|
|
|
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
|
2015-03-24 23:57:23 -04:00
|
|
|
var (
|
|
|
|
cpuPercent = 0.0
|
|
|
|
// calculate the change for the cpu usage of the container in between readings
|
2015-03-25 22:31:29 -04:00
|
|
|
cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU)
|
2015-03-24 23:57:23 -04:00
|
|
|
// calculate the change for the entire system between readings
|
|
|
|
systemDelta = float64(v.CpuStats.SystemUsage - previousSystem)
|
|
|
|
)
|
|
|
|
|
|
|
|
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
|
|
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
|
|
|
|
}
|
|
|
|
return cpuPercent
|
|
|
|
}
|