Windows: stats support

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2016-09-07 16:08:51 -07:00
parent 2f8c4333fe
commit c323878331
2 changed files with 110 additions and 44 deletions

View File

@ -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) {

View File

@ -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) {