Evict stopped containers
Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Upstream-commit: 4f174aa79276c12a1b2b98df2f02d6bee36b7a93 Component: engine
This commit is contained in:
@ -16,15 +16,16 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/stats"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/graph"
|
||||
@ -43,7 +44,6 @@ import (
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/stats"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
@ -2625,25 +2625,10 @@ type containerStats struct {
|
||||
Name string
|
||||
CpuPercentage float64
|
||||
Memory float64
|
||||
MemoryLimit float64
|
||||
MemoryPercentage float64
|
||||
NetworkRx int
|
||||
NetworkTx int
|
||||
}
|
||||
|
||||
type statSorter struct {
|
||||
stats []containerStats
|
||||
}
|
||||
|
||||
func (s *statSorter) Len() int {
|
||||
return len(s.stats)
|
||||
}
|
||||
|
||||
func (s *statSorter) Swap(i, j int) {
|
||||
s.stats[i], s.stats[j] = s.stats[j], s.stats[i]
|
||||
}
|
||||
|
||||
func (s *statSorter) Less(i, j int) bool {
|
||||
return s.stats[i].Name < s.stats[j].Name
|
||||
NetworkRx float64
|
||||
NetworkTx float64
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||
@ -2651,40 +2636,49 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||
cmd.Require(flag.Min, 1)
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
m := &sync.Mutex{}
|
||||
cStats := map[string]containerStats{}
|
||||
for _, name := range cmd.Args() {
|
||||
go cli.streamStats(name, cStats)
|
||||
go cli.streamStats(name, cStats, m)
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
for _ = range time.Tick(1000 * time.Millisecond) {
|
||||
for _ = range time.Tick(500 * time.Millisecond) {
|
||||
fmt.Fprint(cli.out, "\033[2J")
|
||||
fmt.Fprint(cli.out, "\033[H")
|
||||
fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM\tMEM %\tNET I/O")
|
||||
sStats := []containerStats{}
|
||||
for _, s := range cStats {
|
||||
sStats = append(sStats, s)
|
||||
}
|
||||
sorter := &statSorter{sStats}
|
||||
sort.Sort(sorter)
|
||||
for _, s := range sStats {
|
||||
fmt.Fprintf(w, "%s\t%f%%\t%s\t%f%%\t%d/%d\n",
|
||||
fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O")
|
||||
m.Lock()
|
||||
ss := sortStatsByName(cStats)
|
||||
m.Unlock()
|
||||
for _, s := range ss {
|
||||
fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n",
|
||||
s.Name,
|
||||
s.CpuPercentage,
|
||||
units.HumanSize(s.Memory),
|
||||
units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit),
|
||||
s.MemoryPercentage,
|
||||
s.NetworkRx, s.NetworkTx)
|
||||
units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx))
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamStats(name string, data map[string]containerStats) error {
|
||||
func (cli *DockerCli) streamStats(name string, data map[string]containerStats, m *sync.Mutex) error {
|
||||
m.Lock()
|
||||
data[name] = containerStats{
|
||||
Name: name,
|
||||
}
|
||||
m.Unlock()
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/stats", nil, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
stream.Close()
|
||||
m.Lock()
|
||||
delete(data, name)
|
||||
m.Unlock()
|
||||
}()
|
||||
var (
|
||||
previousCpu uint64
|
||||
previousSystem uint64
|
||||
@ -2696,30 +2690,37 @@ func (cli *DockerCli) streamStats(name string, data map[string]containerStats) e
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
memPercent := float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||
cpuPercent := 0.0
|
||||
|
||||
var (
|
||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||
cpuPercent = 0.0
|
||||
)
|
||||
if !start {
|
||||
cpuDelta := float64(v.CpuStats.CpuUsage.TotalUsage) - float64(previousCpu)
|
||||
systemDelta := float64(int(v.CpuStats.SystemUsage)/v.ClockTicks) - float64(int(previousSystem)/v.ClockTicks)
|
||||
|
||||
if systemDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(v.ClockTicks*len(v.CpuStats.CpuUsage.PercpuUsage))
|
||||
}
|
||||
cpuPercent = calcuateCpuPercent(previousCpu, previousSystem, v)
|
||||
}
|
||||
start = false
|
||||
m.Lock()
|
||||
d := data[name]
|
||||
d.Name = name
|
||||
d.CpuPercentage = cpuPercent
|
||||
d.Memory = float64(v.MemoryStats.Usage)
|
||||
d.MemoryLimit = float64(v.MemoryStats.Limit)
|
||||
d.MemoryPercentage = memPercent
|
||||
d.NetworkRx = int(v.Network.RxBytes)
|
||||
d.NetworkTx = int(v.Network.TxBytes)
|
||||
d.NetworkRx = float64(v.Network.RxBytes)
|
||||
d.NetworkTx = float64(v.Network.TxBytes)
|
||||
data[name] = d
|
||||
m.Unlock()
|
||||
|
||||
previousCpu = v.CpuStats.CpuUsage.TotalUsage
|
||||
previousSystem = v.CpuStats.SystemUsage
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func calcuateCpuPercent(previousCpu, previousSystem uint64, v *stats.Stats) float64 {
|
||||
cpuPercent := 0.0
|
||||
cpuDelta := float64(v.CpuStats.CpuUsage.TotalUsage) - float64(previousCpu)
|
||||
systemDelta := float64(int(v.CpuStats.SystemUsage)/v.ClockTicks) - float64(int(previousSystem)/v.ClockTicks)
|
||||
if systemDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(v.ClockTicks*len(v.CpuStats.CpuUsage.PercpuUsage))
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
|
||||
29
components/engine/api/client/sort.go
Normal file
29
components/engine/api/client/sort.go
Normal file
@ -0,0 +1,29 @@
|
||||
package client
|
||||
|
||||
import "sort"
|
||||
|
||||
func sortStatsByName(cStats map[string]containerStats) []containerStats {
|
||||
sStats := []containerStats{}
|
||||
for _, s := range cStats {
|
||||
sStats = append(sStats, s)
|
||||
}
|
||||
sorter := &statSorter{sStats}
|
||||
sort.Sort(sorter)
|
||||
return sStats
|
||||
}
|
||||
|
||||
type statSorter struct {
|
||||
stats []containerStats
|
||||
}
|
||||
|
||||
func (s *statSorter) Len() int {
|
||||
return len(s.stats)
|
||||
}
|
||||
|
||||
func (s *statSorter) Swap(i, j int) {
|
||||
s.stats[i], s.stats[j] = s.stats[j], s.stats[i]
|
||||
}
|
||||
|
||||
func (s *statSorter) Less(i, j int) bool {
|
||||
return s.stats[i].Name < s.stats[j].Name
|
||||
}
|
||||
158
components/engine/api/stats/stats.go
Normal file
158
components/engine/api/stats/stats.go
Normal file
@ -0,0 +1,158 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
)
|
||||
|
||||
type ThrottlingData struct {
|
||||
// Number of periods with throttling active
|
||||
Periods uint64 `json:"periods,omitempty"`
|
||||
// Number of periods when the container hit its throttling limit.
|
||||
ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
|
||||
// Aggregate time the container was throttled for in nanoseconds.
|
||||
ThrottledTime uint64 `json:"throttled_time,omitempty"`
|
||||
}
|
||||
|
||||
// All CPU stats are aggregated since container inception.
|
||||
type CpuUsage struct {
|
||||
// Total CPU time consumed.
|
||||
// Units: nanoseconds.
|
||||
TotalUsage uint64 `json:"total_usage,omitempty"`
|
||||
// Total CPU time consumed per core.
|
||||
// Units: nanoseconds.
|
||||
PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
|
||||
// Time spent by tasks of the cgroup in kernel mode.
|
||||
// Units: nanoseconds.
|
||||
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
|
||||
// Time spent by tasks of the cgroup in user mode.
|
||||
// Units: nanoseconds.
|
||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
||||
}
|
||||
|
||||
type CpuStats struct {
|
||||
CpuUsage CpuUsage `json:"cpu_usage,omitempty"`
|
||||
SystemUsage uint64 `json:"system_cpu_usage"`
|
||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
||||
}
|
||||
|
||||
type MemoryStats struct {
|
||||
// current res_counter usage for memory
|
||||
Usage uint64 `json:"usage,omitempty"`
|
||||
// maximum usage ever recorded.
|
||||
MaxUsage uint64 `json:"max_usage,omitempty"`
|
||||
// TODO(vishh): Export these as stronger types.
|
||||
// all the stats exported via memory.stat.
|
||||
Stats map[string]uint64 `json:"stats,omitempty"`
|
||||
// number of times memory usage hits limits.
|
||||
Failcnt uint64 `json:"failcnt"`
|
||||
Limit uint64 `json:"limit"`
|
||||
}
|
||||
|
||||
type BlkioStatEntry struct {
|
||||
Major uint64 `json:"major,omitempty"`
|
||||
Minor uint64 `json:"minor,omitempty"`
|
||||
Op string `json:"op,omitempty"`
|
||||
Value uint64 `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type BlkioStats struct {
|
||||
// number of bytes tranferred to and from the block device
|
||||
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
|
||||
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"`
|
||||
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
|
||||
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"`
|
||||
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"`
|
||||
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"`
|
||||
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"`
|
||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"`
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
RxBytes uint64 `json:"rx_bytes"`
|
||||
RxPackets uint64 `json:"rx_packets"`
|
||||
RxErrors uint64 `json:"rx_errors"`
|
||||
RxDropped uint64 `json:"rx_dropped"`
|
||||
TxBytes uint64 `json:"tx_bytes"`
|
||||
TxPackets uint64 `json:"tx_packets"`
|
||||
TxErrors uint64 `json:"tx_errors"`
|
||||
TxDropped uint64 `json:"tx_dropped"`
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Read time.Time `json:"read"`
|
||||
ClockTicks int `json:"clock_ticks"`
|
||||
Interval int `json:"interval"` // in ms
|
||||
Network Network `json:"network,omitempty"`
|
||||
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||
}
|
||||
|
||||
// ToStats converts the libcontainer.ContainerStats to the api specific
|
||||
// structs. This is done to preserve API compatibility and versioning.
|
||||
func ToStats(ls *libcontainer.ContainerStats) *Stats {
|
||||
s := &Stats{}
|
||||
if ls.NetworkStats != nil {
|
||||
s.Network = Network{
|
||||
RxBytes: ls.NetworkStats.RxBytes,
|
||||
RxPackets: ls.NetworkStats.RxPackets,
|
||||
RxErrors: ls.NetworkStats.RxErrors,
|
||||
RxDropped: ls.NetworkStats.RxDropped,
|
||||
TxBytes: ls.NetworkStats.TxBytes,
|
||||
TxPackets: ls.NetworkStats.TxPackets,
|
||||
TxErrors: ls.NetworkStats.TxErrors,
|
||||
TxDropped: ls.NetworkStats.TxDropped,
|
||||
}
|
||||
}
|
||||
cs := ls.CgroupStats
|
||||
if cs != nil {
|
||||
s.BlkioStats = BlkioStats{
|
||||
IoServiceBytesRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceBytesRecursive),
|
||||
IoServicedRecursive: copyBlkioEntry(cs.BlkioStats.IoServicedRecursive),
|
||||
IoQueuedRecursive: copyBlkioEntry(cs.BlkioStats.IoQueuedRecursive),
|
||||
IoServiceTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceTimeRecursive),
|
||||
IoWaitTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoWaitTimeRecursive),
|
||||
IoMergedRecursive: copyBlkioEntry(cs.BlkioStats.IoMergedRecursive),
|
||||
IoTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoTimeRecursive),
|
||||
SectorsRecursive: copyBlkioEntry(cs.BlkioStats.SectorsRecursive),
|
||||
}
|
||||
cpu := cs.CpuStats
|
||||
s.CpuStats = CpuStats{
|
||||
CpuUsage: CpuUsage{
|
||||
TotalUsage: cpu.CpuUsage.TotalUsage,
|
||||
PercpuUsage: cpu.CpuUsage.PercpuUsage,
|
||||
UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode,
|
||||
UsageInUsermode: cpu.CpuUsage.UsageInUsermode,
|
||||
},
|
||||
ThrottlingData: ThrottlingData{
|
||||
Periods: cpu.ThrottlingData.Periods,
|
||||
ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods,
|
||||
ThrottledTime: cpu.ThrottlingData.ThrottledTime,
|
||||
},
|
||||
}
|
||||
mem := cs.MemoryStats
|
||||
s.MemoryStats = MemoryStats{
|
||||
Usage: mem.Usage,
|
||||
MaxUsage: mem.MaxUsage,
|
||||
Stats: mem.Stats,
|
||||
Failcnt: mem.Failcnt,
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func copyBlkioEntry(entries []cgroups.BlkioStatEntry) []BlkioStatEntry {
|
||||
out := make([]BlkioStatEntry, len(entries))
|
||||
for i, re := range entries {
|
||||
out[i] = BlkioStatEntry{
|
||||
Major: re.Major,
|
||||
Minor: re.Minor,
|
||||
Op: re.Op,
|
||||
Value: re.Value,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user