diff --git a/command/container/stats.go b/command/container/stats.go index ffd3fcae9f..4c97883898 100644 --- a/command/container/stats.go +++ b/command/container/stats.go @@ -187,7 +187,15 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error { fmt.Fprint(dockerCli.Out(), "\033[2J") 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) { diff --git a/command/container/stats_helpers.go b/command/container/stats_helpers.go index 54cc5589c1..b48d9c7c60 100644 --- a/command/container/stats_helpers.go +++ b/command/container/stats_helpers.go @@ -19,23 +19,29 @@ import ( type containerStats struct { Name string CPUPercentage float64 - Memory float64 - MemoryLimit float64 - MemoryPercentage float64 + Memory float64 // On Windows this is the private working set + MemoryLimit float64 // Not used on Windows + MemoryPercentage float64 // Not used on Windows NetworkRx float64 NetworkTx float64 BlockRead float64 BlockWrite float64 - PidsCurrent uint64 + PidsCurrent uint64 // Not used on Windows mu sync.Mutex err error } type stats struct { - mu sync.Mutex - cs []*containerStats + mu sync.Mutex + 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 { s.mu.Lock() 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 { s.mu.Lock() s.err = err s.mu.Unlock() return } - defer responseBody.Close() + defer response.Body.Close() - dec := json.NewDecoder(responseBody) + dec := json.NewDecoder(response.Body) go func() { 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 { - dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody)) + dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body)) u <- err if err == io.EOF { break @@ -104,28 +116,38 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre continue } - var memPercent = 0.0 - var cpuPercent = 0.0 + daemonOSType = response.OSType - // MemoryStats.Limit will never be 0 unless the container is not running and we haven't - // got any data from cgroup - if v.MemoryStats.Limit != 0 { - memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 + if daemonOSType != "windows" { + // MemoryStats.Limit will never be 0 unless the container is not running and we haven't + // got any data from cgroup + 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.CPUPercentage = cpuPercent - s.Memory = float64(v.MemoryStats.Usage) - s.MemoryLimit = float64(v.MemoryStats.Limit) - s.MemoryPercentage = memPercent + s.Memory = mem s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) s.BlockRead = float64(blkRead) 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() u <- nil 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 { s.mu.Lock() defer s.mu.Unlock() - // 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 := "--" + if daemonOSType == "windows" { + // NOTE: if you change this format, you must also change the err format below! + format := "%s\t%.2f%%\t%s\t%s / %s\t%s / %s\n" + if s.err != nil { + 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, - s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, - ) - err := s.err - return err + s.Name, + s.CPUPercentage, + units.BytesSize(s.Memory), + 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 } -func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { +func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { var ( cpuPercent = 0.0 // 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 } +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) { for _, bioEntry := range blkio.IoServiceBytesRecursive { switch strings.ToLower(bioEntry.Op) {