mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #25737 from Microsoft/jjh-statistics
Windows: Add support for docker stats
This commit is contained in:
commit
4a0419f536
22 changed files with 585 additions and 323 deletions
|
@ -4,7 +4,8 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ThrottlingData stores CPU throttling stats of one running container
|
// ThrottlingData stores CPU throttling stats of one running container.
|
||||||
|
// Not used on Windows.
|
||||||
type ThrottlingData struct {
|
type ThrottlingData struct {
|
||||||
// Number of periods with throttling active
|
// Number of periods with throttling active
|
||||||
Periods uint64 `json:"periods"`
|
Periods uint64 `json:"periods"`
|
||||||
|
@ -17,42 +18,68 @@ type ThrottlingData struct {
|
||||||
// CPUUsage stores All CPU stats aggregated since container inception.
|
// CPUUsage stores All CPU stats aggregated since container inception.
|
||||||
type CPUUsage struct {
|
type CPUUsage struct {
|
||||||
// Total CPU time consumed.
|
// Total CPU time consumed.
|
||||||
// Units: nanoseconds.
|
// Units: nanoseconds (Linux)
|
||||||
|
// Units: 100's of nanoseconds (Windows)
|
||||||
TotalUsage uint64 `json:"total_usage"`
|
TotalUsage uint64 `json:"total_usage"`
|
||||||
// Total CPU time consumed per core.
|
|
||||||
// Units: nanoseconds.
|
// Total CPU time consumed per core (Linux). Not used on Windows.
|
||||||
PercpuUsage []uint64 `json:"percpu_usage"`
|
|
||||||
// Time spent by tasks of the cgroup in kernel mode.
|
|
||||||
// Units: nanoseconds.
|
// Units: nanoseconds.
|
||||||
|
PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
|
||||||
|
|
||||||
|
// Time spent by tasks of the cgroup in kernel mode (Linux).
|
||||||
|
// Time spent by all container processes in kernel mode (Windows).
|
||||||
|
// Units: nanoseconds (Linux).
|
||||||
|
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers.
|
||||||
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
|
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
|
||||||
// Time spent by tasks of the cgroup in user mode.
|
|
||||||
// Units: nanoseconds.
|
// Time spent by tasks of the cgroup in user mode (Linux).
|
||||||
|
// Time spent by all container processes in user mode (Windows).
|
||||||
|
// Units: nanoseconds (Linux).
|
||||||
|
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers
|
||||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPUStats aggregates and wraps all CPU related info of container
|
// CPUStats aggregates and wraps all CPU related info of container
|
||||||
type CPUStats struct {
|
type CPUStats struct {
|
||||||
CPUUsage CPUUsage `json:"cpu_usage"`
|
// CPU Usage. Linux and Windows.
|
||||||
SystemUsage uint64 `json:"system_cpu_usage"`
|
CPUUsage CPUUsage `json:"cpu_usage"`
|
||||||
|
|
||||||
|
// System Usage. Linux only.
|
||||||
|
SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
|
||||||
|
|
||||||
|
// Throttling Data. Linux only.
|
||||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryStats aggregates All memory stats since container inception
|
// MemoryStats aggregates all memory stats since container inception on Linux.
|
||||||
|
// Windows returns stats for commit and private working set only.
|
||||||
type MemoryStats struct {
|
type MemoryStats struct {
|
||||||
|
// Linux Memory Stats
|
||||||
|
|
||||||
// current res_counter usage for memory
|
// current res_counter usage for memory
|
||||||
Usage uint64 `json:"usage"`
|
Usage uint64 `json:"usage,omitempty"`
|
||||||
// maximum usage ever recorded.
|
// maximum usage ever recorded.
|
||||||
MaxUsage uint64 `json:"max_usage"`
|
MaxUsage uint64 `json:"max_usage,omitempty"`
|
||||||
// TODO(vishh): Export these as stronger types.
|
// TODO(vishh): Export these as stronger types.
|
||||||
// all the stats exported via memory.stat.
|
// all the stats exported via memory.stat.
|
||||||
Stats map[string]uint64 `json:"stats"`
|
Stats map[string]uint64 `json:"stats,omitempty"`
|
||||||
// number of times memory usage hits limits.
|
// number of times memory usage hits limits.
|
||||||
Failcnt uint64 `json:"failcnt"`
|
Failcnt uint64 `json:"failcnt,omitempty"`
|
||||||
Limit uint64 `json:"limit"`
|
Limit uint64 `json:"limit,omitempty"`
|
||||||
|
|
||||||
|
// Windows Memory Stats
|
||||||
|
// See https://technet.microsoft.com/en-us/magazine/ff382715.aspx
|
||||||
|
|
||||||
|
// committed bytes
|
||||||
|
Commit uint64 `json:"commitbytes,omitempty"`
|
||||||
|
// peak committed bytes
|
||||||
|
CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
|
||||||
|
// private working set
|
||||||
|
PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlkioStatEntry is one small entity to store a piece of Blkio stats
|
// BlkioStatEntry is one small entity to store a piece of Blkio stats
|
||||||
// TODO Windows: This can be factored out
|
// Not used on Windows.
|
||||||
type BlkioStatEntry struct {
|
type BlkioStatEntry struct {
|
||||||
Major uint64 `json:"major"`
|
Major uint64 `json:"major"`
|
||||||
Minor uint64 `json:"minor"`
|
Minor uint64 `json:"minor"`
|
||||||
|
@ -60,8 +87,10 @@ type BlkioStatEntry struct {
|
||||||
Value uint64 `json:"value"`
|
Value uint64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlkioStats stores All IO service stats for data read and write
|
// BlkioStats stores All IO service stats for data read and write.
|
||||||
// TODO Windows: This can be factored out
|
// This is a Linux specific structure as the differences between expressing
|
||||||
|
// block I/O on Windows and Linux are sufficiently significant to make
|
||||||
|
// little sense attempting to morph into a combined structure.
|
||||||
type BlkioStats struct {
|
type BlkioStats struct {
|
||||||
// number of bytes transferred to and from the block device
|
// number of bytes transferred to and from the block device
|
||||||
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
||||||
|
@ -74,17 +103,38 @@ type BlkioStats struct {
|
||||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkStats aggregates All network stats of one container
|
// StorageStats is the disk I/O stats for read/write on Windows.
|
||||||
// TODO Windows: This will require refactoring
|
type StorageStats struct {
|
||||||
|
ReadCountNormalized uint64 `json:"read_count_normalized,omitempty"`
|
||||||
|
ReadSizeBytes uint64 `json:"read_size_bytes,omitempty"`
|
||||||
|
WriteCountNormalized uint64 `json:"write_count_normalized,omitempty"`
|
||||||
|
WriteSizeBytes uint64 `json:"write_size_bytes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkStats aggregates the network stats of one container
|
||||||
type NetworkStats struct {
|
type NetworkStats struct {
|
||||||
RxBytes uint64 `json:"rx_bytes"`
|
// Bytes received. Windows and Linux.
|
||||||
|
RxBytes uint64 `json:"rx_bytes"`
|
||||||
|
// Packets received. Windows and Linux.
|
||||||
RxPackets uint64 `json:"rx_packets"`
|
RxPackets uint64 `json:"rx_packets"`
|
||||||
RxErrors uint64 `json:"rx_errors"`
|
// Received errors. Not used on Windows. Note that we dont `omitempty` this
|
||||||
|
// field as it is expected in the >=v1.21 API stats structure.
|
||||||
|
RxErrors uint64 `json:"rx_errors"`
|
||||||
|
// Incoming packets dropped. Windows and Linux.
|
||||||
RxDropped uint64 `json:"rx_dropped"`
|
RxDropped uint64 `json:"rx_dropped"`
|
||||||
TxBytes uint64 `json:"tx_bytes"`
|
// Bytes sent. Windows and Linux.
|
||||||
|
TxBytes uint64 `json:"tx_bytes"`
|
||||||
|
// Packets sent. Windows and Linux.
|
||||||
TxPackets uint64 `json:"tx_packets"`
|
TxPackets uint64 `json:"tx_packets"`
|
||||||
TxErrors uint64 `json:"tx_errors"`
|
// Sent errors. Not used on Windows. Note that we dont `omitempty` this
|
||||||
|
// field as it is expected in the >=v1.21 API stats structure.
|
||||||
|
TxErrors uint64 `json:"tx_errors"`
|
||||||
|
// Outgoing packets dropped. Windows and Linux.
|
||||||
TxDropped uint64 `json:"tx_dropped"`
|
TxDropped uint64 `json:"tx_dropped"`
|
||||||
|
// Endpoint ID. Not used on Linux.
|
||||||
|
EndpointID string `json:"endpoint_id,omitempty"`
|
||||||
|
// Instance ID. Not used on Linux.
|
||||||
|
InstanceID string `json:"instance_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PidsStats contains the stats of a container's pids
|
// PidsStats contains the stats of a container's pids
|
||||||
|
@ -98,12 +148,22 @@ type PidsStats struct {
|
||||||
|
|
||||||
// Stats is Ultimate struct aggregating all types of stats of one container
|
// Stats is Ultimate struct aggregating all types of stats of one container
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
Read time.Time `json:"read"`
|
// Common stats
|
||||||
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
Read time.Time `json:"read"`
|
||||||
|
PreRead time.Time `json:"preread"`
|
||||||
|
|
||||||
|
// Linux specific stats, not populated on Windows.
|
||||||
|
PidsStats PidsStats `json:"pids_stats,omitempty"`
|
||||||
|
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||||
|
|
||||||
|
// Windows specific stats, not populated on Linux.
|
||||||
|
NumProcs uint32 `json:"num_procs"`
|
||||||
|
StorageStats StorageStats `json:"storage_stats,omitempty"`
|
||||||
|
|
||||||
|
// Shared stats
|
||||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
||||||
|
PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
|
||||||
PidsStats PidsStats `json:"pids_stats,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatsJSON is newly used Networks
|
// StatsJSON is newly used Networks
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -182,6 +183,13 @@ type ContainerPathStat struct {
|
||||||
LinkTarget string `json:"linkTarget"`
|
LinkTarget string `json:"linkTarget"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerStats contains resonse of Remote API:
|
||||||
|
// GET "/stats"
|
||||||
|
type ContainerStats struct {
|
||||||
|
Body io.ReadCloser `json:"body"`
|
||||||
|
OSType string `json:"ostype"`
|
||||||
|
}
|
||||||
|
|
||||||
// ContainerProcessList contains response of Remote API:
|
// ContainerProcessList contains response of Remote API:
|
||||||
// GET "/containers/{name:.*}/top"
|
// GET "/containers/{name:.*}/top"
|
||||||
type ContainerProcessList struct {
|
type ContainerProcessList struct {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerStats returns near realtime stats for a given container.
|
// ContainerStats returns near realtime stats for a given container.
|
||||||
// It's up to the caller to close the io.ReadCloser returned.
|
// It's up to the caller to close the io.ReadCloser returned.
|
||||||
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) {
|
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Set("stream", "0")
|
query.Set("stream", "0")
|
||||||
if stream {
|
if stream {
|
||||||
|
@ -18,7 +18,9 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
|
||||||
|
|
||||||
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
|
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return types.ContainerStats{}, err
|
||||||
}
|
}
|
||||||
return resp.body, err
|
|
||||||
|
osType := GetDockerOS(resp.header.Get("Server"))
|
||||||
|
return types.ContainerStats{Body: resp.body, OSType: osType}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,12 +54,12 @@ func TestContainerStats(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
body, err := client.ContainerStats(context.Background(), "container_id", c.stream)
|
resp, err := client.ContainerStats(context.Background(), "container_id", c.stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer body.Close()
|
defer resp.Body.Close()
|
||||||
content, err := ioutil.ReadAll(body)
|
content, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
|
||||||
return types.ImageBuildResponse{}, err
|
return types.ImageBuildResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
osType := getDockerOS(serverResp.header.Get("Server"))
|
osType := GetDockerOS(serverResp.header.Get("Server"))
|
||||||
|
|
||||||
return types.ImageBuildResponse{
|
return types.ImageBuildResponse{
|
||||||
Body: serverResp.body,
|
Body: serverResp.body,
|
||||||
|
@ -113,7 +113,8 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro
|
||||||
return query, nil
|
return query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDockerOS(serverHeader string) string {
|
// GetDockerOS returns the operating system based on the server header from the daemon.
|
||||||
|
func GetDockerOS(serverHeader string) string {
|
||||||
var osType string
|
var osType string
|
||||||
matches := headerRegexp.FindStringSubmatch(serverHeader)
|
matches := headerRegexp.FindStringSubmatch(serverHeader)
|
||||||
if len(matches) > 0 {
|
if len(matches) > 0 {
|
||||||
|
|
|
@ -222,7 +222,7 @@ func TestGetDockerOS(t *testing.T) {
|
||||||
"Foo/v1.22 (bar)": "",
|
"Foo/v1.22 (bar)": "",
|
||||||
}
|
}
|
||||||
for header, os := range cases {
|
for header, os := range cases {
|
||||||
g := getDockerOS(header)
|
g := GetDockerOS(header)
|
||||||
if g != os {
|
if g != os {
|
||||||
t.Fatalf("Expected %s, got %s", os, g)
|
t.Fatalf("Expected %s, got %s", os, g)
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ type ContainerAPIClient interface {
|
||||||
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error
|
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error
|
||||||
ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
|
ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
|
||||||
ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
|
ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
|
||||||
ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error)
|
ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error)
|
||||||
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
|
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
|
||||||
ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
|
ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
|
||||||
ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
|
ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
|
"github.com/docker/docker/pkg/platform"
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
"github.com/docker/docker/pkg/sysinfo"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
|
@ -379,7 +380,62 @@ func driverOptions(config *Config) []nwconfig.Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
|
func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
|
||||||
return nil, nil
|
if !c.IsRunning() {
|
||||||
|
return nil, errNotRunning{c.ID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the stats from HCS via libcontainerd
|
||||||
|
stats, err := daemon.containerd.Stats(c.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with an empty structure
|
||||||
|
s := &types.StatsJSON{}
|
||||||
|
|
||||||
|
// Populate the CPU/processor statistics
|
||||||
|
s.CPUStats = types.CPUStats{
|
||||||
|
CPUUsage: types.CPUUsage{
|
||||||
|
TotalUsage: stats.Processor.TotalRuntime100ns,
|
||||||
|
UsageInKernelmode: stats.Processor.RuntimeKernel100ns,
|
||||||
|
UsageInUsermode: stats.Processor.RuntimeKernel100ns,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the memory statistics
|
||||||
|
s.MemoryStats = types.MemoryStats{
|
||||||
|
Commit: stats.Memory.UsageCommitBytes,
|
||||||
|
CommitPeak: stats.Memory.UsageCommitPeakBytes,
|
||||||
|
PrivateWorkingSet: stats.Memory.UsagePrivateWorkingSetBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the storage statistics
|
||||||
|
s.StorageStats = types.StorageStats{
|
||||||
|
ReadCountNormalized: stats.Storage.ReadCountNormalized,
|
||||||
|
ReadSizeBytes: stats.Storage.ReadSizeBytes,
|
||||||
|
WriteCountNormalized: stats.Storage.WriteCountNormalized,
|
||||||
|
WriteSizeBytes: stats.Storage.WriteSizeBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the network statistics
|
||||||
|
s.Networks = make(map[string]types.NetworkStats)
|
||||||
|
|
||||||
|
for _, nstats := range stats.Network {
|
||||||
|
s.Networks[nstats.EndpointId] = types.NetworkStats{
|
||||||
|
RxBytes: nstats.BytesReceived,
|
||||||
|
RxPackets: nstats.PacketsReceived,
|
||||||
|
RxDropped: nstats.DroppedPacketsIncoming,
|
||||||
|
TxBytes: nstats.BytesSent,
|
||||||
|
TxPackets: nstats.PacketsSent,
|
||||||
|
TxDropped: nstats.DroppedPacketsOutgoing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the timestamp
|
||||||
|
s.Stats.Read = stats.Timestamp
|
||||||
|
s.Stats.NumProcs = platform.NumProcs()
|
||||||
|
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaultIsolation determine the default isolation mode for the
|
// setDefaultIsolation determine the default isolation mode for the
|
||||||
|
|
|
@ -3,8 +3,8 @@ package daemon
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@ import (
|
||||||
// ContainerStats writes information about the container to the stream
|
// ContainerStats writes information about the container to the stream
|
||||||
// given in the config object.
|
// given in the config object.
|
||||||
func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
|
func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return errors.New("Windows does not support stats")
|
|
||||||
}
|
|
||||||
// Remote API version (used for backwards compatibility)
|
// Remote API version (used for backwards compatibility)
|
||||||
apiVersion := config.Version
|
apiVersion := config.Version
|
||||||
|
|
||||||
|
@ -44,10 +41,13 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
|
||||||
}
|
}
|
||||||
|
|
||||||
var preCPUStats types.CPUStats
|
var preCPUStats types.CPUStats
|
||||||
|
var preRead time.Time
|
||||||
getStatJSON := func(v interface{}) *types.StatsJSON {
|
getStatJSON := func(v interface{}) *types.StatsJSON {
|
||||||
ss := v.(types.StatsJSON)
|
ss := v.(types.StatsJSON)
|
||||||
ss.PreCPUStats = preCPUStats
|
ss.PreCPUStats = preCPUStats
|
||||||
|
ss.PreRead = preRead
|
||||||
preCPUStats = ss.CPUStats
|
preCPUStats = ss.CPUStats
|
||||||
|
preRead = ss.Read
|
||||||
return &ss
|
return &ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,9 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
|
||||||
var statsJSON interface{}
|
var statsJSON interface{}
|
||||||
statsJSONPost120 := getStatJSON(v)
|
statsJSONPost120 := getStatJSON(v)
|
||||||
if versions.LessThan(apiVersion, "1.21") {
|
if versions.LessThan(apiVersion, "1.21") {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return errors.New("API versions pre v1.21 do not support stats on Windows")
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
rxBytes uint64
|
rxBytes uint64
|
||||||
rxPackets uint64
|
rxPackets uint64
|
||||||
|
@ -138,7 +141,8 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !container.Config.NetworkDisabled {
|
// We already have the network stats on Windows directly from HCS.
|
||||||
|
if !container.Config.NetworkDisabled && runtime.GOOS != "windows" {
|
||||||
if stats.Networks, err = daemon.getNetworkStats(container); err != nil {
|
if stats.Networks, err = daemon.getNetworkStats(container); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -146,51 +150,3 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve Network SandboxID in case the container reuse another container's network stack
|
|
||||||
func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
|
|
||||||
curr := c
|
|
||||||
for curr.HostConfig.NetworkMode.IsContainer() {
|
|
||||||
containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
|
|
||||||
connected, err := daemon.GetContainer(containerID)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Could not get container for %s", containerID)
|
|
||||||
}
|
|
||||||
curr = connected
|
|
||||||
}
|
|
||||||
return curr.NetworkSettings.SandboxID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
|
|
||||||
sandboxID, err := daemon.getNetworkSandboxID(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sb, err := daemon.netController.SandboxByID(sandboxID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lnstats, err := sb.Statistics()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := make(map[string]types.NetworkStats)
|
|
||||||
// Convert libnetwork nw stats into engine-api stats
|
|
||||||
for ifName, ifStats := range lnstats {
|
|
||||||
stats[ifName] = types.NetworkStats{
|
|
||||||
RxBytes: ifStats.RxBytes,
|
|
||||||
RxPackets: ifStats.RxPackets,
|
|
||||||
RxErrors: ifStats.RxErrors,
|
|
||||||
RxDropped: ifStats.RxDropped,
|
|
||||||
TxBytes: ifStats.TxBytes,
|
|
||||||
TxPackets: ifStats.TxPackets,
|
|
||||||
TxErrors: ifStats.TxErrors,
|
|
||||||
TxDropped: ifStats.TxDropped,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
|
132
daemon/stats_collector.go
Normal file
132
daemon/stats_collector.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// +build !solaris
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/container"
|
||||||
|
"github.com/docker/docker/pkg/pubsub"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statsSupervisor interface {
|
||||||
|
// GetContainerStats collects all the stats related to a container
|
||||||
|
GetContainerStats(container *container.Container) (*types.StatsJSON, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStatsCollector returns a new statsCollector that collections
|
||||||
|
// stats for a registered container at the specified interval.
|
||||||
|
// The collector allows non-running containers to be added
|
||||||
|
// and will start processing stats when they are started.
|
||||||
|
func (daemon *Daemon) newStatsCollector(interval time.Duration) *statsCollector {
|
||||||
|
s := &statsCollector{
|
||||||
|
interval: interval,
|
||||||
|
supervisor: daemon,
|
||||||
|
publishers: make(map[*container.Container]*pubsub.Publisher),
|
||||||
|
bufReader: bufio.NewReaderSize(nil, 128),
|
||||||
|
}
|
||||||
|
platformNewStatsCollector(s)
|
||||||
|
go s.run()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsCollector manages and provides container resource stats
|
||||||
|
type statsCollector struct {
|
||||||
|
m sync.Mutex
|
||||||
|
supervisor statsSupervisor
|
||||||
|
interval time.Duration
|
||||||
|
publishers map[*container.Container]*pubsub.Publisher
|
||||||
|
bufReader *bufio.Reader
|
||||||
|
|
||||||
|
// The following fields are not set on Windows currently.
|
||||||
|
clockTicksPerSecond uint64
|
||||||
|
machineMemory uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect registers the container with the collector and adds it to
|
||||||
|
// the event loop for collection on the specified interval returning
|
||||||
|
// a channel for the subscriber to receive on.
|
||||||
|
func (s *statsCollector) collect(c *container.Container) chan interface{} {
|
||||||
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
publisher, exists := s.publishers[c]
|
||||||
|
if !exists {
|
||||||
|
publisher = pubsub.NewPublisher(100*time.Millisecond, 1024)
|
||||||
|
s.publishers[c] = publisher
|
||||||
|
}
|
||||||
|
return publisher.Subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopCollection closes the channels for all subscribers and removes
|
||||||
|
// the container from metrics collection.
|
||||||
|
func (s *statsCollector) stopCollection(c *container.Container) {
|
||||||
|
s.m.Lock()
|
||||||
|
if publisher, exists := s.publishers[c]; exists {
|
||||||
|
publisher.Close()
|
||||||
|
delete(s.publishers, c)
|
||||||
|
}
|
||||||
|
s.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsubscribe removes a specific subscriber from receiving updates for a container's stats.
|
||||||
|
func (s *statsCollector) unsubscribe(c *container.Container, ch chan interface{}) {
|
||||||
|
s.m.Lock()
|
||||||
|
publisher := s.publishers[c]
|
||||||
|
if publisher != nil {
|
||||||
|
publisher.Evict(ch)
|
||||||
|
if publisher.Len() == 0 {
|
||||||
|
delete(s.publishers, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.m.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *statsCollector) run() {
|
||||||
|
type publishersPair struct {
|
||||||
|
container *container.Container
|
||||||
|
publisher *pubsub.Publisher
|
||||||
|
}
|
||||||
|
// we cannot determine the capacity here.
|
||||||
|
// it will grow enough in first iteration
|
||||||
|
var pairs []publishersPair
|
||||||
|
|
||||||
|
for range time.Tick(s.interval) {
|
||||||
|
// it does not make sense in the first iteration,
|
||||||
|
// but saves allocations in further iterations
|
||||||
|
pairs = pairs[:0]
|
||||||
|
|
||||||
|
s.m.Lock()
|
||||||
|
for container, publisher := range s.publishers {
|
||||||
|
// copy pointers here to release the lock ASAP
|
||||||
|
pairs = append(pairs, publishersPair{container, publisher})
|
||||||
|
}
|
||||||
|
s.m.Unlock()
|
||||||
|
if len(pairs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
systemUsage, err := s.getSystemCPUUsage()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("collecting system cpu usage: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pair := range pairs {
|
||||||
|
stats, err := s.supervisor.GetContainerStats(pair.container)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(errNotRunning); !ok {
|
||||||
|
logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// FIXME: move to containerd on Linux (not Windows)
|
||||||
|
stats.CPUStats.SystemUsage = systemUsage
|
||||||
|
|
||||||
|
pair.publisher.Publish(*stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,141 +3,23 @@
|
||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/container"
|
|
||||||
"github.com/docker/docker/pkg/pubsub"
|
|
||||||
sysinfo "github.com/docker/docker/pkg/system"
|
sysinfo "github.com/docker/docker/pkg/system"
|
||||||
"github.com/opencontainers/runc/libcontainer/system"
|
"github.com/opencontainers/runc/libcontainer/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type statsSupervisor interface {
|
// platformNewStatsCollector performs platform specific initialisation of the
|
||||||
// GetContainerStats collects all the stats related to a container
|
// statsCollector structure.
|
||||||
GetContainerStats(container *container.Container) (*types.StatsJSON, error)
|
func platformNewStatsCollector(s *statsCollector) {
|
||||||
}
|
s.clockTicksPerSecond = uint64(system.GetClockTicks())
|
||||||
|
|
||||||
// newStatsCollector returns a new statsCollector that collections
|
|
||||||
// network and cgroup stats for a registered container at the specified
|
|
||||||
// interval. The collector allows non-running containers to be added
|
|
||||||
// and will start processing stats when they are started.
|
|
||||||
func (daemon *Daemon) newStatsCollector(interval time.Duration) *statsCollector {
|
|
||||||
s := &statsCollector{
|
|
||||||
interval: interval,
|
|
||||||
supervisor: daemon,
|
|
||||||
publishers: make(map[*container.Container]*pubsub.Publisher),
|
|
||||||
clockTicksPerSecond: uint64(system.GetClockTicks()),
|
|
||||||
bufReader: bufio.NewReaderSize(nil, 128),
|
|
||||||
}
|
|
||||||
meminfo, err := sysinfo.ReadMemInfo()
|
meminfo, err := sysinfo.ReadMemInfo()
|
||||||
if err == nil && meminfo.MemTotal > 0 {
|
if err == nil && meminfo.MemTotal > 0 {
|
||||||
s.machineMemory = uint64(meminfo.MemTotal)
|
s.machineMemory = uint64(meminfo.MemTotal)
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.run()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// statsCollector manages and provides container resource stats
|
|
||||||
type statsCollector struct {
|
|
||||||
m sync.Mutex
|
|
||||||
supervisor statsSupervisor
|
|
||||||
interval time.Duration
|
|
||||||
clockTicksPerSecond uint64
|
|
||||||
publishers map[*container.Container]*pubsub.Publisher
|
|
||||||
bufReader *bufio.Reader
|
|
||||||
machineMemory uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect registers the container with the collector and adds it to
|
|
||||||
// the event loop for collection on the specified interval returning
|
|
||||||
// a channel for the subscriber to receive on.
|
|
||||||
func (s *statsCollector) collect(c *container.Container) chan interface{} {
|
|
||||||
s.m.Lock()
|
|
||||||
defer s.m.Unlock()
|
|
||||||
publisher, exists := s.publishers[c]
|
|
||||||
if !exists {
|
|
||||||
publisher = pubsub.NewPublisher(100*time.Millisecond, 1024)
|
|
||||||
s.publishers[c] = publisher
|
|
||||||
}
|
|
||||||
return publisher.Subscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopCollection closes the channels for all subscribers and removes
|
|
||||||
// the container from metrics collection.
|
|
||||||
func (s *statsCollector) stopCollection(c *container.Container) {
|
|
||||||
s.m.Lock()
|
|
||||||
if publisher, exists := s.publishers[c]; exists {
|
|
||||||
publisher.Close()
|
|
||||||
delete(s.publishers, c)
|
|
||||||
}
|
|
||||||
s.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsubscribe removes a specific subscriber from receiving updates for a container's stats.
|
|
||||||
func (s *statsCollector) unsubscribe(c *container.Container, ch chan interface{}) {
|
|
||||||
s.m.Lock()
|
|
||||||
publisher := s.publishers[c]
|
|
||||||
if publisher != nil {
|
|
||||||
publisher.Evict(ch)
|
|
||||||
if publisher.Len() == 0 {
|
|
||||||
delete(s.publishers, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statsCollector) run() {
|
|
||||||
type publishersPair struct {
|
|
||||||
container *container.Container
|
|
||||||
publisher *pubsub.Publisher
|
|
||||||
}
|
|
||||||
// we cannot determine the capacity here.
|
|
||||||
// it will grow enough in first iteration
|
|
||||||
var pairs []publishersPair
|
|
||||||
|
|
||||||
for range time.Tick(s.interval) {
|
|
||||||
// it does not make sense in the first iteration,
|
|
||||||
// but saves allocations in further iterations
|
|
||||||
pairs = pairs[:0]
|
|
||||||
|
|
||||||
s.m.Lock()
|
|
||||||
for container, publisher := range s.publishers {
|
|
||||||
// copy pointers here to release the lock ASAP
|
|
||||||
pairs = append(pairs, publishersPair{container, publisher})
|
|
||||||
}
|
|
||||||
s.m.Unlock()
|
|
||||||
if len(pairs) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
systemUsage, err := s.getSystemCPUUsage()
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("collecting system cpu usage: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pair := range pairs {
|
|
||||||
stats, err := s.supervisor.GetContainerStats(pair.container)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(errNotRunning); !ok {
|
|
||||||
logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// FIXME: move to containerd
|
|
||||||
stats.CPUStats.SystemUsage = systemUsage
|
|
||||||
|
|
||||||
pair.publisher.Publish(*stats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nanoSecondsPerSecond = 1e9
|
const nanoSecondsPerSecond = 1e9
|
||||||
|
|
|
@ -1,35 +1,15 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
// platformNewStatsCollector performs platform specific initialisation of the
|
||||||
"time"
|
// statsCollector structure. This is a no-op on Windows.
|
||||||
|
func platformNewStatsCollector(s *statsCollector) {
|
||||||
"github.com/docker/docker/container"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newStatsCollector returns a new statsCollector for collection stats
|
|
||||||
// for a registered container at the specified interval. The collector allows
|
|
||||||
// non-running containers to be added and will start processing stats when
|
|
||||||
// they are started.
|
|
||||||
func (daemon *Daemon) newStatsCollector(interval time.Duration) *statsCollector {
|
|
||||||
return &statsCollector{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// statsCollector manages and provides container resource stats
|
// getSystemCPUUsage returns the host system's cpu usage in
|
||||||
type statsCollector struct {
|
// nanoseconds. An error is returned if the format of the underlying
|
||||||
}
|
// file does not match. This is a no-op on Windows.
|
||||||
|
func (s *statsCollector) getSystemCPUUsage() (uint64, error) {
|
||||||
// collect registers the container with the collector and adds it to
|
return 0, nil
|
||||||
// the event loop for collection on the specified interval returning
|
|
||||||
// a channel for the subscriber to receive on.
|
|
||||||
func (s *statsCollector) collect(c *container.Container) chan interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopCollection closes the channels for all subscribers and removes
|
|
||||||
// the container from metrics collection.
|
|
||||||
func (s *statsCollector) stopCollection(c *container.Container) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsubscribe removes a specific subscriber from receiving updates for a container's stats.
|
|
||||||
func (s *statsCollector) unsubscribe(c *container.Container, ch chan interface{}) {
|
|
||||||
}
|
}
|
||||||
|
|
58
daemon/stats_unix.go
Normal file
58
daemon/stats_unix.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resolve Network SandboxID in case the container reuse another container's network stack
|
||||||
|
func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
|
||||||
|
curr := c
|
||||||
|
for curr.HostConfig.NetworkMode.IsContainer() {
|
||||||
|
containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
|
||||||
|
connected, err := daemon.GetContainer(containerID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Could not get container for %s", containerID)
|
||||||
|
}
|
||||||
|
curr = connected
|
||||||
|
}
|
||||||
|
return curr.NetworkSettings.SandboxID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
|
||||||
|
sandboxID, err := daemon.getNetworkSandboxID(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sb, err := daemon.netController.SandboxByID(sandboxID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lnstats, err := sb.Statistics()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stats := make(map[string]types.NetworkStats)
|
||||||
|
// Convert libnetwork nw stats into engine-api stats
|
||||||
|
for ifName, ifStats := range lnstats {
|
||||||
|
stats[ifName] = types.NetworkStats{
|
||||||
|
RxBytes: ifStats.RxBytes,
|
||||||
|
RxPackets: ifStats.RxPackets,
|
||||||
|
RxErrors: ifStats.RxErrors,
|
||||||
|
RxDropped: ifStats.RxDropped,
|
||||||
|
TxBytes: ifStats.TxBytes,
|
||||||
|
TxPackets: ifStats.TxPackets,
|
||||||
|
TxErrors: ifStats.TxErrors,
|
||||||
|
TxDropped: ifStats.TxDropped,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
11
daemon/stats_windows.go
Normal file
11
daemon/stats_windows.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows network stats are obtained directly through HCS, hence this is a no-op.
|
||||||
|
func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
|
||||||
|
return make(map[string]types.NetworkStats), nil
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ If you want more detailed information about a container's resource usage, use th
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Running `docker stats` on all running containers
|
Running `docker stats` on all running containers against a Linux daemon.
|
||||||
|
|
||||||
$ docker stats
|
$ docker stats
|
||||||
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
|
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
|
||||||
|
@ -35,9 +35,30 @@ Running `docker stats` on all running containers
|
||||||
9c76f7834ae2 0.07% 2.746 MiB / 64 MiB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B
|
9c76f7834ae2 0.07% 2.746 MiB / 64 MiB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B
|
||||||
d1ea048f04e4 0.03% 4.583 MiB / 64 MiB 6.30% 2.854 KB / 648 B 27.7 MB / 0 B
|
d1ea048f04e4 0.03% 4.583 MiB / 64 MiB 6.30% 2.854 KB / 648 B 27.7 MB / 0 B
|
||||||
|
|
||||||
Running `docker stats` on multiple containers by name and id.
|
Running `docker stats` on multiple containers by name and id against a Linux daemon.
|
||||||
|
|
||||||
$ docker stats fervent_panini 5acfcb1b4fd1
|
$ docker stats fervent_panini 5acfcb1b4fd1
|
||||||
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
|
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
|
||||||
5acfcb1b4fd1 0.00% 115.2 MiB/1.045 GiB 11.03% 1.422 kB/648 B
|
5acfcb1b4fd1 0.00% 115.2 MiB/1.045 GiB 11.03% 1.422 kB/648 B
|
||||||
fervent_panini 0.02% 11.08 MiB/1.045 GiB 1.06% 648 B/648 B
|
fervent_panini 0.02% 11.08 MiB/1.045 GiB 1.06% 648 B/648 B
|
||||||
|
|
||||||
|
Running `docker stats` on all running containers against a Windows daemon.
|
||||||
|
|
||||||
|
PS E:\> docker stats
|
||||||
|
CONTAINER CPU % PRIV WORKING SET NET I/O BLOCK I/O
|
||||||
|
09d3bb5b1604 6.61% 38.21 MiB 17.1 kB / 7.73 kB 10.7 MB / 3.57 MB
|
||||||
|
9db7aa4d986d 9.19% 38.26 MiB 15.2 kB / 7.65 kB 10.6 MB / 3.3 MB
|
||||||
|
3f214c61ad1d 0.00% 28.64 MiB 64 kB / 6.84 kB 4.42 MB / 6.93 MB
|
||||||
|
|
||||||
|
Running `docker stats` on multiple containers by name and id against a Windows daemon.
|
||||||
|
|
||||||
|
PS E:\> docker ps -a
|
||||||
|
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
|
3f214c61ad1d nanoserver "cmd" 2 minutes ago Up 2 minutes big_minsky
|
||||||
|
9db7aa4d986d windowsservercore "cmd" 2 minutes ago Up 2 minutes mad_wilson
|
||||||
|
09d3bb5b1604 windowsservercore "cmd" 2 minutes ago Up 2 minutes affectionate_easley
|
||||||
|
|
||||||
|
PS E:\> docker stats 3f214c61ad1d mad_wilson
|
||||||
|
CONTAINER CPU % PRIV WORKING SET NET I/O BLOCK I/O
|
||||||
|
3f214c61ad1d 0.00% 46.25 MiB 76.3 kB / 7.92 kB 10.3 MB / 14.7 MB
|
||||||
|
mad_wilson 9.59% 40.09 MiB 27.6 kB / 8.81 kB 17 MB / 20.1 MB
|
|
@ -175,12 +175,10 @@ func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestGetContainerStats(c *check.C) {
|
func (s *DockerSuite) TestGetContainerStats(c *check.C) {
|
||||||
// Problematic on Windows as Windows does not support stats
|
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
var (
|
var (
|
||||||
name = "statscontainer"
|
name = "statscontainer"
|
||||||
)
|
)
|
||||||
dockerCmd(c, "run", "-d", "--name", name, "busybox", "top")
|
runSleepingContainer(c, "--name", name)
|
||||||
|
|
||||||
type b struct {
|
type b struct {
|
||||||
status int
|
status int
|
||||||
|
@ -214,9 +212,7 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
|
func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
|
||||||
// Problematic on Windows as Windows does not support stats
|
out, _ := runSleepingContainer(c)
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
|
|
||||||
id := strings.TrimSpace(out)
|
id := strings.TrimSpace(out)
|
||||||
|
|
||||||
buf := &integration.ChannelBuffer{make(chan []byte, 1)}
|
buf := &integration.ChannelBuffer{make(chan []byte, 1)}
|
||||||
|
@ -251,10 +247,8 @@ func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
|
||||||
// previous test was just checking one stat entry so it didn't fail (stats with
|
// previous test was just checking one stat entry so it didn't fail (stats with
|
||||||
// stream false always return one stat)
|
// stream false always return one stat)
|
||||||
func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
|
func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
|
||||||
// Problematic on Windows as Windows does not support stats
|
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
name := "statscontainer"
|
name := "statscontainer"
|
||||||
dockerCmd(c, "run", "-d", "--name", name, "busybox", "top")
|
runSleepingContainer(c, "--name", name)
|
||||||
|
|
||||||
type b struct {
|
type b struct {
|
||||||
status int
|
status int
|
||||||
|
@ -289,10 +283,8 @@ func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
|
func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
|
||||||
// Problematic on Windows as Windows does not support stats
|
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
name := "statscontainer"
|
name := "statscontainer"
|
||||||
dockerCmd(c, "run", "-d", "--name", name, "busybox", "top")
|
runSleepingContainer(c, "--name", name)
|
||||||
|
|
||||||
type b struct {
|
type b struct {
|
||||||
status int
|
status int
|
||||||
|
@ -319,16 +311,14 @@ func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
|
||||||
c.Assert(sr.status, checker.Equals, http.StatusOK)
|
c.Assert(sr.status, checker.Equals, http.StatusOK)
|
||||||
|
|
||||||
s := string(sr.body)
|
s := string(sr.body)
|
||||||
// count occurrences of "read" of types.Stats
|
// count occurrences of `"read"` of types.Stats
|
||||||
c.Assert(strings.Count(s, "read"), checker.Equals, 1, check.Commentf("Expected only one stat streamed, got %d", strings.Count(s, "read")))
|
c.Assert(strings.Count(s, `"read"`), checker.Equals, 1, check.Commentf("Expected only one stat streamed, got %d", strings.Count(s, `"read"`)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) {
|
func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) {
|
||||||
// Problematic on Windows as Windows does not support stats
|
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
name := "statscontainer"
|
name := "statscontainer"
|
||||||
dockerCmd(c, "create", "--name", name, "busybox", "top")
|
dockerCmd(c, "create", "--name", name, "busybox", "ps")
|
||||||
|
|
||||||
type stats struct {
|
type stats struct {
|
||||||
status int
|
status int
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
var expectedNetworkInterfaceStats = strings.Split("rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors tx_packets", " ")
|
var expectedNetworkInterfaceStats = strings.Split("rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors tx_packets", " ")
|
||||||
|
|
||||||
func (s *DockerSuite) TestApiStatsNoStreamGetCpu(c *check.C) {
|
func (s *DockerSuite) TestApiStatsNoStreamGetCpu(c *check.C) {
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello'; usleep 100000; done")
|
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello'; usleep 100000; done")
|
||||||
|
|
||||||
id := strings.TrimSpace(out)
|
id := strings.TrimSpace(out)
|
||||||
|
@ -37,15 +36,30 @@ func (s *DockerSuite) TestApiStatsNoStreamGetCpu(c *check.C) {
|
||||||
body.Close()
|
body.Close()
|
||||||
|
|
||||||
var cpuPercent = 0.0
|
var cpuPercent = 0.0
|
||||||
cpuDelta := float64(v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage)
|
|
||||||
systemDelta := float64(v.CPUStats.SystemUsage - v.PreCPUStats.SystemUsage)
|
if daemonPlatform != "windows" {
|
||||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
cpuDelta := float64(v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage)
|
||||||
|
systemDelta := float64(v.CPUStats.SystemUsage - v.PreCPUStats.SystemUsage)
|
||||||
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||||
|
} else {
|
||||||
|
// 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 {
|
||||||
|
cpuPercent = float64(intervalsUsed) / float64(possIntervals) * 100.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.Assert(cpuPercent, check.Not(checker.Equals), 0.0, check.Commentf("docker stats with no-stream get cpu usage failed: was %v", cpuPercent))
|
c.Assert(cpuPercent, check.Not(checker.Equals), 0.0, check.Commentf("docker stats with no-stream get cpu usage failed: was %v", cpuPercent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestApiStatsStoppedContainerInGoroutines(c *check.C) {
|
func (s *DockerSuite) TestApiStatsStoppedContainerInGoroutines(c *check.C) {
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo 1")
|
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo 1")
|
||||||
id := strings.TrimSpace(out)
|
id := strings.TrimSpace(out)
|
||||||
|
|
||||||
|
@ -82,14 +96,17 @@ func (s *DockerSuite) TestApiStatsStoppedContainerInGoroutines(c *check.C) {
|
||||||
|
|
||||||
func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
||||||
testRequires(c, SameHostDaemon)
|
testRequires(c, SameHostDaemon)
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
|
|
||||||
out, _ := runSleepingContainer(c)
|
out, _ := runSleepingContainer(c)
|
||||||
id := strings.TrimSpace(out)
|
id := strings.TrimSpace(out)
|
||||||
c.Assert(waitRun(id), checker.IsNil)
|
c.Assert(waitRun(id), checker.IsNil)
|
||||||
|
|
||||||
// Retrieve the container address
|
// Retrieve the container address
|
||||||
contIP := findContainerIP(c, id, "bridge")
|
net := "bridge"
|
||||||
|
if daemonPlatform == "windows" {
|
||||||
|
net = "nat"
|
||||||
|
}
|
||||||
|
contIP := findContainerIP(c, id, net)
|
||||||
numPings := 1
|
numPings := 1
|
||||||
|
|
||||||
var preRxPackets uint64
|
var preRxPackets uint64
|
||||||
|
@ -130,9 +147,14 @@ func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
||||||
postTxPackets += v.TxPackets
|
postTxPackets += v.TxPackets
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the stats contain at least the expected number of packets (account for ARP)
|
// Verify the stats contain at least the expected number of packets
|
||||||
expRxPkts := 1 + preRxPackets + uint64(numPings)
|
// On Linux, account for ARP.
|
||||||
expTxPkts := 1 + preTxPackets + uint64(numPings)
|
expRxPkts := preRxPackets + uint64(numPings)
|
||||||
|
expTxPkts := preTxPackets + uint64(numPings)
|
||||||
|
if daemonPlatform != "windows" {
|
||||||
|
expRxPkts++
|
||||||
|
expTxPkts++
|
||||||
|
}
|
||||||
c.Assert(postTxPackets, checker.GreaterOrEqualThan, expTxPkts,
|
c.Assert(postTxPackets, checker.GreaterOrEqualThan, expTxPkts,
|
||||||
check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
|
check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
|
||||||
c.Assert(postRxPackets, checker.GreaterOrEqualThan, expRxPkts,
|
c.Assert(postRxPackets, checker.GreaterOrEqualThan, expRxPkts,
|
||||||
|
@ -141,7 +163,6 @@ func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
||||||
|
|
||||||
func (s *DockerSuite) TestApiStatsNetworkStatsVersioning(c *check.C) {
|
func (s *DockerSuite) TestApiStatsNetworkStatsVersioning(c *check.C) {
|
||||||
testRequires(c, SameHostDaemon)
|
testRequires(c, SameHostDaemon)
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
|
|
||||||
out, _ := runSleepingContainer(c)
|
out, _ := runSleepingContainer(c)
|
||||||
id := strings.TrimSpace(out)
|
id := strings.TrimSpace(out)
|
||||||
|
|
|
@ -369,7 +369,19 @@ func (clnt *client) Resume(containerID string) error {
|
||||||
|
|
||||||
// Stats handles stats requests for containers
|
// Stats handles stats requests for containers
|
||||||
func (clnt *client) Stats(containerID string) (*Stats, error) {
|
func (clnt *client) Stats(containerID string) (*Stats, error) {
|
||||||
return nil, errors.New("Windows: Stats not implemented")
|
// Get the libcontainerd container object
|
||||||
|
clnt.lock(containerID)
|
||||||
|
defer clnt.unlock(containerID)
|
||||||
|
container, err := clnt.getContainer(containerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s, err := container.hcsContainer.Statistics()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
st := Stats(s)
|
||||||
|
return &st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore is the handler for restoring a container
|
// Restore is the handler for restoring a container
|
||||||
|
|
|
@ -26,8 +26,8 @@ type StateInfo struct {
|
||||||
UpdatePending bool // Indicates that there are some update operations pending that should be completed by a servicing container.
|
UpdatePending bool // Indicates that there are some update operations pending that should be completed by a servicing container.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats contains a stats properties from containerd.
|
// Stats contains statics from HCS
|
||||||
type Stats struct{}
|
type Stats hcsshim.Statistics
|
||||||
|
|
||||||
// Resources defines updatable container resource values.
|
// Resources defines updatable container resource values.
|
||||||
type Resources struct{}
|
type Resources struct{}
|
||||||
|
|
|
@ -50,3 +50,9 @@ func runtimeArchitecture() (string, error) {
|
||||||
return "", fmt.Errorf("Unknown processor architecture")
|
return "", fmt.Errorf("Unknown processor architecture")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumProcs returns the number of processors on the system
|
||||||
|
func NumProcs() uint32 {
|
||||||
|
syscall.Syscall(procGetSystemInfo.Addr(), 1, uintptr(unsafe.Pointer(&sysinfo)), 0, 0)
|
||||||
|
return sysinfo.dwNumberOfProcessors
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue