mirror of https://github.com/docker/cli.git
Windows: stats support
Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
parent
2f8c4333fe
commit
c323878331
|
@ -187,7 +187,15 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
||||||
fmt.Fprint(dockerCli.Out(), "\033[2J")
|
fmt.Fprint(dockerCli.Out(), "\033[2J")
|
||||||
fmt.Fprint(dockerCli.Out(), "\033[H")
|
fmt.Fprint(dockerCli.Out(), "\033[H")
|
||||||
}
|
}
|
||||||
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
|
switch daemonOSType {
|
||||||
|
case "":
|
||||||
|
// Before we have any stats from the daemon, we don't know the platform...
|
||||||
|
io.WriteString(w, "Waiting for statistics...\n")
|
||||||
|
case "windows":
|
||||||
|
io.WriteString(w, "CONTAINER\tCPU %\tPRIV WORKING SET\tNET I/O\tBLOCK I/O\n")
|
||||||
|
default:
|
||||||
|
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for range time.Tick(500 * time.Millisecond) {
|
for range time.Tick(500 * time.Millisecond) {
|
||||||
|
|
|
@ -19,23 +19,29 @@ import (
|
||||||
type containerStats struct {
|
type containerStats struct {
|
||||||
Name string
|
Name string
|
||||||
CPUPercentage float64
|
CPUPercentage float64
|
||||||
Memory float64
|
Memory float64 // On Windows this is the private working set
|
||||||
MemoryLimit float64
|
MemoryLimit float64 // Not used on Windows
|
||||||
MemoryPercentage float64
|
MemoryPercentage float64 // Not used on Windows
|
||||||
NetworkRx float64
|
NetworkRx float64
|
||||||
NetworkTx float64
|
NetworkTx float64
|
||||||
BlockRead float64
|
BlockRead float64
|
||||||
BlockWrite float64
|
BlockWrite float64
|
||||||
PidsCurrent uint64
|
PidsCurrent uint64 // Not used on Windows
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type stats struct {
|
type stats struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
cs []*containerStats
|
ostype string
|
||||||
|
cs []*containerStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// daemonOSType is set once we have at least one stat for a container
|
||||||
|
// from the daemon. It is used to ensure we print the right header based
|
||||||
|
// on the daemon platform.
|
||||||
|
var daemonOSType string
|
||||||
|
|
||||||
func (s *stats) add(cs *containerStats) bool {
|
func (s *stats) add(cs *containerStats) bool {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
@ -80,22 +86,28 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
responseBody, err := cli.ContainerStats(ctx, s.Name, streamStats)
|
response, err := cli.ContainerStats(ctx, s.Name, streamStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.err = err
|
s.err = err
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer responseBody.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
dec := json.NewDecoder(responseBody)
|
dec := json.NewDecoder(response.Body)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var v *types.StatsJSON
|
var (
|
||||||
|
v *types.StatsJSON
|
||||||
|
memPercent = 0.0
|
||||||
|
cpuPercent = 0.0
|
||||||
|
blkRead, blkWrite uint64 // Only used on Linux
|
||||||
|
mem = 0.0
|
||||||
|
)
|
||||||
|
|
||||||
if err := dec.Decode(&v); err != nil {
|
if err := dec.Decode(&v); err != nil {
|
||||||
dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody))
|
dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body))
|
||||||
u <- err
|
u <- err
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
@ -104,28 +116,38 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var memPercent = 0.0
|
daemonOSType = response.OSType
|
||||||
var cpuPercent = 0.0
|
|
||||||
|
|
||||||
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
|
if daemonOSType != "windows" {
|
||||||
// got any data from cgroup
|
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
|
||||||
if v.MemoryStats.Limit != 0 {
|
// got any data from cgroup
|
||||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
if v.MemoryStats.Limit != 0 {
|
||||||
|
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||||
|
}
|
||||||
|
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
||||||
|
previousSystem = v.PreCPUStats.SystemUsage
|
||||||
|
cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v)
|
||||||
|
blkRead, blkWrite = calculateBlockIO(v.BlkioStats)
|
||||||
|
mem = float64(v.MemoryStats.Usage)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cpuPercent = calculateCPUPercentWindows(v)
|
||||||
|
blkRead = v.StorageStats.ReadSizeBytes
|
||||||
|
blkWrite = v.StorageStats.WriteSizeBytes
|
||||||
|
mem = float64(v.MemoryStats.PrivateWorkingSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
|
||||||
previousSystem = v.PreCPUStats.SystemUsage
|
|
||||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
|
||||||
blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.CPUPercentage = cpuPercent
|
s.CPUPercentage = cpuPercent
|
||||||
s.Memory = float64(v.MemoryStats.Usage)
|
s.Memory = mem
|
||||||
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
|
||||||
s.MemoryPercentage = memPercent
|
|
||||||
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
||||||
s.BlockRead = float64(blkRead)
|
s.BlockRead = float64(blkRead)
|
||||||
s.BlockWrite = float64(blkWrite)
|
s.BlockWrite = float64(blkWrite)
|
||||||
s.PidsCurrent = v.PidsStats.Current
|
if daemonOSType != "windows" {
|
||||||
|
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
||||||
|
s.MemoryPercentage = memPercent
|
||||||
|
s.PidsCurrent = v.PidsStats.Current
|
||||||
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
u <- nil
|
u <- nil
|
||||||
if !streamStats {
|
if !streamStats {
|
||||||
|
@ -178,29 +200,49 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre
|
||||||
func (s *containerStats) Display(w io.Writer) error {
|
func (s *containerStats) Display(w io.Writer) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
// NOTE: if you change this format, you must also change the err format below!
|
if daemonOSType == "windows" {
|
||||||
format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n"
|
// NOTE: if you change this format, you must also change the err format below!
|
||||||
if s.err != nil {
|
format := "%s\t%.2f%%\t%s\t%s / %s\t%s / %s\n"
|
||||||
format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n"
|
if s.err != nil {
|
||||||
errStr := "--"
|
format = "%s\t%s\t%s\t%s / %s\t%s / %s\n"
|
||||||
|
errStr := "--"
|
||||||
|
fmt.Fprintf(w, format,
|
||||||
|
s.Name, errStr, errStr, errStr, errStr, errStr, errStr,
|
||||||
|
)
|
||||||
|
err := s.err
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Fprintf(w, format,
|
fmt.Fprintf(w, format,
|
||||||
s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr,
|
s.Name,
|
||||||
)
|
s.CPUPercentage,
|
||||||
err := s.err
|
units.BytesSize(s.Memory),
|
||||||
return err
|
units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3),
|
||||||
|
units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3))
|
||||||
|
} else {
|
||||||
|
// NOTE: if you change this format, you must also change the err format below!
|
||||||
|
format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n"
|
||||||
|
if s.err != nil {
|
||||||
|
format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n"
|
||||||
|
errStr := "--"
|
||||||
|
fmt.Fprintf(w, format,
|
||||||
|
s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr,
|
||||||
|
)
|
||||||
|
err := s.err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, format,
|
||||||
|
s.Name,
|
||||||
|
s.CPUPercentage,
|
||||||
|
units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit),
|
||||||
|
s.MemoryPercentage,
|
||||||
|
units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3),
|
||||||
|
units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3),
|
||||||
|
s.PidsCurrent)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, format,
|
|
||||||
s.Name,
|
|
||||||
s.CPUPercentage,
|
|
||||||
units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit),
|
|
||||||
s.MemoryPercentage,
|
|
||||||
units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3),
|
|
||||||
units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3),
|
|
||||||
s.PidsCurrent)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||||
var (
|
var (
|
||||||
cpuPercent = 0.0
|
cpuPercent = 0.0
|
||||||
// calculate the change for the cpu usage of the container in between readings
|
// calculate the change for the cpu usage of the container in between readings
|
||||||
|
@ -215,6 +257,22 @@ func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON)
|
||||||
return cpuPercent
|
return cpuPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
|
||||||
|
// Max number of 100ns intervals between the previous time read and now
|
||||||
|
possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals
|
||||||
|
possIntervals /= 100 // Convert to number of 100ns intervals
|
||||||
|
possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors
|
||||||
|
|
||||||
|
// Intervals used
|
||||||
|
intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage
|
||||||
|
|
||||||
|
// Percentage avoiding divide-by-zero
|
||||||
|
if possIntervals > 0 {
|
||||||
|
return float64(intervalsUsed) / float64(possIntervals) * 100.0
|
||||||
|
}
|
||||||
|
return 0.00
|
||||||
|
}
|
||||||
|
|
||||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
||||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||||
switch strings.ToLower(bioEntry.Op) {
|
switch strings.ToLower(bioEntry.Op) {
|
||||||
|
|
Loading…
Reference in New Issue