Signed-off-by: John Howard <jhoward@microsoft.com>
| ... | ... |
@@ -4,7 +4,8 @@ package types |
| 4 | 4 |
|
| 5 | 5 |
import "time" |
| 6 | 6 |
|
| 7 |
-// ThrottlingData stores CPU throttling stats of one running container |
|
| 7 |
+// ThrottlingData stores CPU throttling stats of one running container. |
|
| 8 |
+// Not used on Windows. |
|
| 8 | 9 |
type ThrottlingData struct {
|
| 9 | 10 |
// Number of periods with throttling active |
| 10 | 11 |
Periods uint64 `json:"periods"` |
| ... | ... |
@@ -17,42 +18,68 @@ type ThrottlingData struct {
|
| 17 | 17 |
// CPUUsage stores All CPU stats aggregated since container inception. |
| 18 | 18 |
type CPUUsage struct {
|
| 19 | 19 |
// Total CPU time consumed. |
| 20 |
- // Units: nanoseconds. |
|
| 20 |
+ // Units: nanoseconds (Linux) |
|
| 21 |
+ // Units: 100's of nanoseconds (Windows) |
|
| 21 | 22 |
TotalUsage uint64 `json:"total_usage"` |
| 22 |
- // Total CPU time consumed per core. |
|
| 23 |
- // Units: nanoseconds. |
|
| 24 |
- PercpuUsage []uint64 `json:"percpu_usage"` |
|
| 25 |
- // Time spent by tasks of the cgroup in kernel mode. |
|
| 23 |
+ |
|
| 24 |
+ // Total CPU time consumed per core (Linux). Not used on Windows. |
|
| 26 | 25 |
// Units: nanoseconds. |
| 26 |
+ PercpuUsage []uint64 `json:"percpu_usage,omitempty"` |
|
| 27 |
+ |
|
| 28 |
+ // Time spent by tasks of the cgroup in kernel mode (Linux). |
|
| 29 |
+ // Time spent by all container processes in kernel mode (Windows). |
|
| 30 |
+ // Units: nanoseconds (Linux). |
|
| 31 |
+ // Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers. |
|
| 27 | 32 |
UsageInKernelmode uint64 `json:"usage_in_kernelmode"` |
| 28 |
- // Time spent by tasks of the cgroup in user mode. |
|
| 29 |
- // Units: nanoseconds. |
|
| 33 |
+ |
|
| 34 |
+ // Time spent by tasks of the cgroup in user mode (Linux). |
|
| 35 |
+ // Time spent by all container processes in user mode (Windows). |
|
| 36 |
+ // Units: nanoseconds (Linux). |
|
| 37 |
+ // Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers |
|
| 30 | 38 |
UsageInUsermode uint64 `json:"usage_in_usermode"` |
| 31 | 39 |
} |
| 32 | 40 |
|
| 33 | 41 |
// CPUStats aggregates and wraps all CPU related info of container |
| 34 | 42 |
type CPUStats struct {
|
| 35 |
- CPUUsage CPUUsage `json:"cpu_usage"` |
|
| 36 |
- SystemUsage uint64 `json:"system_cpu_usage"` |
|
| 43 |
+ // CPU Usage. Linux and Windows. |
|
| 44 |
+ CPUUsage CPUUsage `json:"cpu_usage"` |
|
| 45 |
+ |
|
| 46 |
+ // System Usage. Linux only. |
|
| 47 |
+ SystemUsage uint64 `json:"system_cpu_usage,omitempty"` |
|
| 48 |
+ |
|
| 49 |
+ // Throttling Data. Linux only. |
|
| 37 | 50 |
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` |
| 38 | 51 |
} |
| 39 | 52 |
|
| 40 |
-// MemoryStats aggregates All memory stats since container inception |
|
| 53 |
+// MemoryStats aggregates all memory stats since container inception on Linux. |
|
| 54 |
+// Windows returns stats for commit and private working set only. |
|
| 41 | 55 |
type MemoryStats struct {
|
| 56 |
+ // Linux Memory Stats |
|
| 57 |
+ |
|
| 42 | 58 |
// current res_counter usage for memory |
| 43 |
- Usage uint64 `json:"usage"` |
|
| 59 |
+ Usage uint64 `json:"usage,omitempty"` |
|
| 44 | 60 |
// maximum usage ever recorded. |
| 45 |
- MaxUsage uint64 `json:"max_usage"` |
|
| 61 |
+ MaxUsage uint64 `json:"max_usage,omitempty"` |
|
| 46 | 62 |
// TODO(vishh): Export these as stronger types. |
| 47 | 63 |
// all the stats exported via memory.stat. |
| 48 |
- Stats map[string]uint64 `json:"stats"` |
|
| 64 |
+ Stats map[string]uint64 `json:"stats,omitempty"` |
|
| 49 | 65 |
// number of times memory usage hits limits. |
| 50 |
- Failcnt uint64 `json:"failcnt"` |
|
| 51 |
- Limit uint64 `json:"limit"` |
|
| 66 |
+ Failcnt uint64 `json:"failcnt,omitempty"` |
|
| 67 |
+ Limit uint64 `json:"limit,omitempty"` |
|
| 68 |
+ |
|
| 69 |
+ // Windows Memory Stats |
|
| 70 |
+ // See https://technet.microsoft.com/en-us/magazine/ff382715.aspx |
|
| 71 |
+ |
|
| 72 |
+ // committed bytes |
|
| 73 |
+ Commit uint64 `json:"commitbytes,omitempty"` |
|
| 74 |
+ // peak committed bytes |
|
| 75 |
+ CommitPeak uint64 `json:"commitpeakbytes,omitempty"` |
|
| 76 |
+ // private working set |
|
| 77 |
+ PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"` |
|
| 52 | 78 |
} |
| 53 | 79 |
|
| 54 | 80 |
// BlkioStatEntry is one small entity to store a piece of Blkio stats |
| 55 |
-// TODO Windows: This can be factored out |
|
| 81 |
+// Not used on Windows. |
|
| 56 | 82 |
type BlkioStatEntry struct {
|
| 57 | 83 |
Major uint64 `json:"major"` |
| 58 | 84 |
Minor uint64 `json:"minor"` |
| ... | ... |
@@ -60,8 +87,10 @@ type BlkioStatEntry struct {
|
| 60 | 60 |
Value uint64 `json:"value"` |
| 61 | 61 |
} |
| 62 | 62 |
|
| 63 |
-// BlkioStats stores All IO service stats for data read and write |
|
| 64 |
-// TODO Windows: This can be factored out |
|
| 63 |
+// BlkioStats stores All IO service stats for data read and write. |
|
| 64 |
+// This is a Linux specific structure as the differences between expressing |
|
| 65 |
+// block I/O on Windows and Linux are sufficiently significant to make |
|
| 66 |
+// little sense attempting to morph into a combined structure. |
|
| 65 | 67 |
type BlkioStats struct {
|
| 66 | 68 |
// number of bytes transferred to and from the block device |
| 67 | 69 |
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"` |
| ... | ... |
@@ -74,17 +103,38 @@ type BlkioStats struct {
|
| 74 | 74 |
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"` |
| 75 | 75 |
} |
| 76 | 76 |
|
| 77 |
-// NetworkStats aggregates All network stats of one container |
|
| 78 |
-// TODO Windows: This will require refactoring |
|
| 77 |
+// StorageStats is the disk I/O stats for read/write on Windows. |
|
| 78 |
+type StorageStats struct {
|
|
| 79 |
+ ReadCountNormalized uint64 `json:"read_count_normalized,omitempty"` |
|
| 80 |
+ ReadSizeBytes uint64 `json:"read_size_bytes,omitempty"` |
|
| 81 |
+ WriteCountNormalized uint64 `json:"write_count_normalized,omitempty"` |
|
| 82 |
+ WriteSizeBytes uint64 `json:"write_size_bytes,omitempty"` |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+// NetworkStats aggregates the network stats of one container |
|
| 79 | 86 |
type NetworkStats struct {
|
| 80 |
- RxBytes uint64 `json:"rx_bytes"` |
|
| 87 |
+ // Bytes received. Windows and Linux. |
|
| 88 |
+ RxBytes uint64 `json:"rx_bytes"` |
|
| 89 |
+ // Packets received. Windows and Linux. |
|
| 81 | 90 |
RxPackets uint64 `json:"rx_packets"` |
| 82 |
- RxErrors uint64 `json:"rx_errors"` |
|
| 91 |
+ // Received errors. Not used on Windows. Note that we dont `omitempty` this |
|
| 92 |
+ // field as it is expected in the >=v1.21 API stats structure. |
|
| 93 |
+ RxErrors uint64 `json:"rx_errors"` |
|
| 94 |
+ // Incoming packets dropped. Windows and Linux. |
|
| 83 | 95 |
RxDropped uint64 `json:"rx_dropped"` |
| 84 |
- TxBytes uint64 `json:"tx_bytes"` |
|
| 96 |
+ // Bytes sent. Windows and Linux. |
|
| 97 |
+ TxBytes uint64 `json:"tx_bytes"` |
|
| 98 |
+ // Packets sent. Windows and Linux. |
|
| 85 | 99 |
TxPackets uint64 `json:"tx_packets"` |
| 86 |
- TxErrors uint64 `json:"tx_errors"` |
|
| 100 |
+ // Sent errors. Not used on Windows. Note that we dont `omitempty` this |
|
| 101 |
+ // field as it is expected in the >=v1.21 API stats structure. |
|
| 102 |
+ TxErrors uint64 `json:"tx_errors"` |
|
| 103 |
+ // Outgoing packets dropped. Windows and Linux. |
|
| 87 | 104 |
TxDropped uint64 `json:"tx_dropped"` |
| 105 |
+ // Endpoint ID. Not used on Linux. |
|
| 106 |
+ EndpointID string `json:"endpoint_id,omitempty"` |
|
| 107 |
+ // Instance ID. Not used on Linux. |
|
| 108 |
+ InstanceID string `json:"instance_id,omitempty"` |
|
| 88 | 109 |
} |
| 89 | 110 |
|
| 90 | 111 |
// PidsStats contains the stats of a container's pids |
| ... | ... |
@@ -98,12 +148,22 @@ type PidsStats struct {
|
| 98 | 98 |
|
| 99 | 99 |
// Stats is Ultimate struct aggregating all types of stats of one container |
| 100 | 100 |
type Stats struct {
|
| 101 |
- Read time.Time `json:"read"` |
|
| 102 |
- PreCPUStats CPUStats `json:"precpu_stats,omitempty"` |
|
| 101 |
+ // Common stats |
|
| 102 |
+ Read time.Time `json:"read"` |
|
| 103 |
+ PreRead time.Time `json:"preread"` |
|
| 104 |
+ |
|
| 105 |
+ // Linux specific stats, not populated on Windows. |
|
| 106 |
+ PidsStats PidsStats `json:"pids_stats,omitempty"` |
|
| 107 |
+ BlkioStats BlkioStats `json:"blkio_stats,omitempty"` |
|
| 108 |
+ |
|
| 109 |
+ // Windows specific stats, not populated on Linux. |
|
| 110 |
+ NumProcs uint32 `json:"num_procs"` |
|
| 111 |
+ StorageStats StorageStats `json:"storage_stats,omitempty"` |
|
| 112 |
+ |
|
| 113 |
+ // Shared stats |
|
| 103 | 114 |
CPUStats CPUStats `json:"cpu_stats,omitempty"` |
| 115 |
+ PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous" |
|
| 104 | 116 |
MemoryStats MemoryStats `json:"memory_stats,omitempty"` |
| 105 |
- BlkioStats BlkioStats `json:"blkio_stats,omitempty"` |
|
| 106 |
- PidsStats PidsStats `json:"pids_stats,omitempty"` |
|
| 107 | 117 |
} |
| 108 | 118 |
|
| 109 | 119 |
// StatsJSON is newly used Networks |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package types |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "io" |
|
| 4 | 5 |
"os" |
| 5 | 6 |
"time" |
| 6 | 7 |
|
| ... | ... |
@@ -182,6 +183,13 @@ type ContainerPathStat struct {
|
| 182 | 182 |
LinkTarget string `json:"linkTarget"` |
| 183 | 183 |
} |
| 184 | 184 |
|
| 185 |
+// ContainerStats contains resonse of Remote API: |
|
| 186 |
+// GET "/stats" |
|
| 187 |
+type ContainerStats struct {
|
|
| 188 |
+ Body io.ReadCloser `json:"body"` |
|
| 189 |
+ OSType string `json:"ostype"` |
|
| 190 |
+} |
|
| 191 |
+ |
|
| 185 | 192 |
// ContainerProcessList contains response of Remote API: |
| 186 | 193 |
// GET "/containers/{name:.*}/top"
|
| 187 | 194 |
type ContainerProcessList struct {
|
| ... | ... |
@@ -187,7 +187,15 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
| 187 | 187 |
fmt.Fprint(dockerCli.Out(), "\033[2J") |
| 188 | 188 |
fmt.Fprint(dockerCli.Out(), "\033[H") |
| 189 | 189 |
} |
| 190 |
- io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n") |
|
| 190 |
+ switch daemonOSType {
|
|
| 191 |
+ case "": |
|
| 192 |
+ // Before we have any stats from the daemon, we don't know the platform... |
|
| 193 |
+ io.WriteString(w, "Waiting for statistics...\n") |
|
| 194 |
+ case "windows": |
|
| 195 |
+ io.WriteString(w, "CONTAINER\tCPU %\tPRIV WORKING SET\tNET I/O\tBLOCK I/O\n") |
|
| 196 |
+ default: |
|
| 197 |
+ io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n") |
|
| 198 |
+ } |
|
| 191 | 199 |
} |
| 192 | 200 |
|
| 193 | 201 |
for range time.Tick(500 * time.Millisecond) {
|
| ... | ... |
@@ -19,23 +19,29 @@ import ( |
| 19 | 19 |
type containerStats struct {
|
| 20 | 20 |
Name string |
| 21 | 21 |
CPUPercentage float64 |
| 22 |
- Memory float64 |
|
| 23 |
- MemoryLimit float64 |
|
| 24 |
- MemoryPercentage float64 |
|
| 22 |
+ Memory float64 // On Windows this is the private working set |
|
| 23 |
+ MemoryLimit float64 // Not used on Windows |
|
| 24 |
+ MemoryPercentage float64 // Not used on Windows |
|
| 25 | 25 |
NetworkRx float64 |
| 26 | 26 |
NetworkTx float64 |
| 27 | 27 |
BlockRead float64 |
| 28 | 28 |
BlockWrite float64 |
| 29 |
- PidsCurrent uint64 |
|
| 29 |
+ PidsCurrent uint64 // Not used on Windows |
|
| 30 | 30 |
mu sync.Mutex |
| 31 | 31 |
err error |
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 | 34 |
type stats struct {
|
| 35 |
- mu sync.Mutex |
|
| 36 |
- cs []*containerStats |
|
| 35 |
+ mu sync.Mutex |
|
| 36 |
+ ostype string |
|
| 37 |
+ cs []*containerStats |
|
| 37 | 38 |
} |
| 38 | 39 |
|
| 40 |
+// daemonOSType is set once we have at least one stat for a container |
|
| 41 |
+// from the daemon. It is used to ensure we print the right header based |
|
| 42 |
+// on the daemon platform. |
|
| 43 |
+var daemonOSType string |
|
| 44 |
+ |
|
| 39 | 45 |
func (s *stats) add(cs *containerStats) bool {
|
| 40 | 46 |
s.mu.Lock() |
| 41 | 47 |
defer s.mu.Unlock() |
| ... | ... |
@@ -80,22 +86,28 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 80 | 80 |
} |
| 81 | 81 |
}() |
| 82 | 82 |
|
| 83 |
- responseBody, err := cli.ContainerStats(ctx, s.Name, streamStats) |
|
| 83 |
+ response, err := cli.ContainerStats(ctx, s.Name, streamStats) |
|
| 84 | 84 |
if err != nil {
|
| 85 | 85 |
s.mu.Lock() |
| 86 | 86 |
s.err = err |
| 87 | 87 |
s.mu.Unlock() |
| 88 | 88 |
return |
| 89 | 89 |
} |
| 90 |
- defer responseBody.Close() |
|
| 90 |
+ defer response.Body.Close() |
|
| 91 | 91 |
|
| 92 |
- dec := json.NewDecoder(responseBody) |
|
| 92 |
+ dec := json.NewDecoder(response.Body) |
|
| 93 | 93 |
go func() {
|
| 94 | 94 |
for {
|
| 95 |
- var v *types.StatsJSON |
|
| 95 |
+ var ( |
|
| 96 |
+ v *types.StatsJSON |
|
| 97 |
+ memPercent = 0.0 |
|
| 98 |
+ cpuPercent = 0.0 |
|
| 99 |
+ blkRead, blkWrite uint64 // Only used on Linux |
|
| 100 |
+ mem = 0.0 |
|
| 101 |
+ ) |
|
| 96 | 102 |
|
| 97 | 103 |
if err := dec.Decode(&v); err != nil {
|
| 98 |
- dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody)) |
|
| 104 |
+ dec = json.NewDecoder(io.MultiReader(dec.Buffered(), response.Body)) |
|
| 99 | 105 |
u <- err |
| 100 | 106 |
if err == io.EOF {
|
| 101 | 107 |
break |
| ... | ... |
@@ -104,28 +116,38 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 104 | 104 |
continue |
| 105 | 105 |
} |
| 106 | 106 |
|
| 107 |
- var memPercent = 0.0 |
|
| 108 |
- var cpuPercent = 0.0 |
|
| 107 |
+ daemonOSType = response.OSType |
|
| 108 |
+ |
|
| 109 |
+ if daemonOSType != "windows" {
|
|
| 110 |
+ // MemoryStats.Limit will never be 0 unless the container is not running and we haven't |
|
| 111 |
+ // got any data from cgroup |
|
| 112 |
+ if v.MemoryStats.Limit != 0 {
|
|
| 113 |
+ memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 |
|
| 114 |
+ } |
|
| 115 |
+ previousCPU = v.PreCPUStats.CPUUsage.TotalUsage |
|
| 116 |
+ previousSystem = v.PreCPUStats.SystemUsage |
|
| 117 |
+ cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v) |
|
| 118 |
+ blkRead, blkWrite = calculateBlockIO(v.BlkioStats) |
|
| 119 |
+ mem = float64(v.MemoryStats.Usage) |
|
| 109 | 120 |
|
| 110 |
- // MemoryStats.Limit will never be 0 unless the container is not running and we haven't |
|
| 111 |
- // got any data from cgroup |
|
| 112 |
- if v.MemoryStats.Limit != 0 {
|
|
| 113 |
- memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 |
|
| 121 |
+ } else {
|
|
| 122 |
+ cpuPercent = calculateCPUPercentWindows(v) |
|
| 123 |
+ blkRead = v.StorageStats.ReadSizeBytes |
|
| 124 |
+ blkWrite = v.StorageStats.WriteSizeBytes |
|
| 125 |
+ mem = float64(v.MemoryStats.PrivateWorkingSet) |
|
| 114 | 126 |
} |
| 115 | 127 |
|
| 116 |
- previousCPU = v.PreCPUStats.CPUUsage.TotalUsage |
|
| 117 |
- previousSystem = v.PreCPUStats.SystemUsage |
|
| 118 |
- cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) |
|
| 119 |
- blkRead, blkWrite := calculateBlockIO(v.BlkioStats) |
|
| 120 | 128 |
s.mu.Lock() |
| 121 | 129 |
s.CPUPercentage = cpuPercent |
| 122 |
- s.Memory = float64(v.MemoryStats.Usage) |
|
| 123 |
- s.MemoryLimit = float64(v.MemoryStats.Limit) |
|
| 124 |
- s.MemoryPercentage = memPercent |
|
| 130 |
+ s.Memory = mem |
|
| 125 | 131 |
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) |
| 126 | 132 |
s.BlockRead = float64(blkRead) |
| 127 | 133 |
s.BlockWrite = float64(blkWrite) |
| 128 |
- s.PidsCurrent = v.PidsStats.Current |
|
| 134 |
+ if daemonOSType != "windows" {
|
|
| 135 |
+ s.MemoryLimit = float64(v.MemoryStats.Limit) |
|
| 136 |
+ s.MemoryPercentage = memPercent |
|
| 137 |
+ s.PidsCurrent = v.PidsStats.Current |
|
| 138 |
+ } |
|
| 129 | 139 |
s.mu.Unlock() |
| 130 | 140 |
u <- nil |
| 131 | 141 |
if !streamStats {
|
| ... | ... |
@@ -178,29 +200,49 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre |
| 178 | 178 |
func (s *containerStats) Display(w io.Writer) error {
|
| 179 | 179 |
s.mu.Lock() |
| 180 | 180 |
defer s.mu.Unlock() |
| 181 |
- // NOTE: if you change this format, you must also change the err format below! |
|
| 182 |
- format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n" |
|
| 183 |
- if s.err != nil {
|
|
| 184 |
- format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n" |
|
| 185 |
- errStr := "--" |
|
| 181 |
+ if daemonOSType == "windows" {
|
|
| 182 |
+ // NOTE: if you change this format, you must also change the err format below! |
|
| 183 |
+ format := "%s\t%.2f%%\t%s\t%s / %s\t%s / %s\n" |
|
| 184 |
+ if s.err != nil {
|
|
| 185 |
+ format = "%s\t%s\t%s\t%s / %s\t%s / %s\n" |
|
| 186 |
+ errStr := "--" |
|
| 187 |
+ fmt.Fprintf(w, format, |
|
| 188 |
+ s.Name, errStr, errStr, errStr, errStr, errStr, errStr, |
|
| 189 |
+ ) |
|
| 190 |
+ err := s.err |
|
| 191 |
+ return err |
|
| 192 |
+ } |
|
| 193 |
+ fmt.Fprintf(w, format, |
|
| 194 |
+ s.Name, |
|
| 195 |
+ s.CPUPercentage, |
|
| 196 |
+ units.BytesSize(s.Memory), |
|
| 197 |
+ units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3), |
|
| 198 |
+ units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3)) |
|
| 199 |
+ } else {
|
|
| 200 |
+ // NOTE: if you change this format, you must also change the err format below! |
|
| 201 |
+ format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n" |
|
| 202 |
+ if s.err != nil {
|
|
| 203 |
+ format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n" |
|
| 204 |
+ errStr := "--" |
|
| 205 |
+ fmt.Fprintf(w, format, |
|
| 206 |
+ s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, |
|
| 207 |
+ ) |
|
| 208 |
+ err := s.err |
|
| 209 |
+ return err |
|
| 210 |
+ } |
|
| 186 | 211 |
fmt.Fprintf(w, format, |
| 187 |
- s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, |
|
| 188 |
- ) |
|
| 189 |
- err := s.err |
|
| 190 |
- return err |
|
| 212 |
+ s.Name, |
|
| 213 |
+ s.CPUPercentage, |
|
| 214 |
+ units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), |
|
| 215 |
+ s.MemoryPercentage, |
|
| 216 |
+ units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3), |
|
| 217 |
+ units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3), |
|
| 218 |
+ s.PidsCurrent) |
|
| 191 | 219 |
} |
| 192 |
- fmt.Fprintf(w, format, |
|
| 193 |
- s.Name, |
|
| 194 |
- s.CPUPercentage, |
|
| 195 |
- units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), |
|
| 196 |
- s.MemoryPercentage, |
|
| 197 |
- units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3), |
|
| 198 |
- units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3), |
|
| 199 |
- s.PidsCurrent) |
|
| 200 | 220 |
return nil |
| 201 | 221 |
} |
| 202 | 222 |
|
| 203 |
-func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
|
| 223 |
+func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
|
| 204 | 224 |
var ( |
| 205 | 225 |
cpuPercent = 0.0 |
| 206 | 226 |
// 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) |
| 215 | 215 |
return cpuPercent |
| 216 | 216 |
} |
| 217 | 217 |
|
| 218 |
+func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
|
|
| 219 |
+ // Max number of 100ns intervals between the previous time read and now |
|
| 220 |
+ possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals |
|
| 221 |
+ possIntervals /= 100 // Convert to number of 100ns intervals |
|
| 222 |
+ possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors |
|
| 223 |
+ |
|
| 224 |
+ // Intervals used |
|
| 225 |
+ intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage |
|
| 226 |
+ |
|
| 227 |
+ // Percentage avoiding divide-by-zero |
|
| 228 |
+ if possIntervals > 0 {
|
|
| 229 |
+ return float64(intervalsUsed) / float64(possIntervals) * 100.0 |
|
| 230 |
+ } |
|
| 231 |
+ return 0.00 |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 218 | 234 |
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
| 219 | 235 |
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
| 220 | 236 |
switch strings.ToLower(bioEntry.Op) {
|
| ... | ... |
@@ -1,15 +1,15 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "io" |
|
| 5 | 4 |
"net/url" |
| 6 | 5 |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 | 7 |
"golang.org/x/net/context" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 | 10 |
// ContainerStats returns near realtime stats for a given container. |
| 11 | 11 |
// It's up to the caller to close the io.ReadCloser returned. |
| 12 |
-func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) {
|
|
| 12 |
+func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
|
| 13 | 13 |
query := url.Values{}
|
| 14 | 14 |
query.Set("stream", "0")
|
| 15 | 15 |
if stream {
|
| ... | ... |
@@ -18,7 +18,9 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea |
| 18 | 18 |
|
| 19 | 19 |
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil) |
| 20 | 20 |
if err != nil {
|
| 21 |
- return nil, err |
|
| 21 |
+ return types.ContainerStats{}, err
|
|
| 22 | 22 |
} |
| 23 |
- return resp.body, err |
|
| 23 |
+ |
|
| 24 |
+ osType := GetDockerOS(resp.header.Get("Server"))
|
|
| 25 |
+ return types.ContainerStats{Body: resp.body, OSType: osType}, err
|
|
| 24 | 26 |
} |
| ... | ... |
@@ -54,12 +54,12 @@ func TestContainerStats(t *testing.T) {
|
| 54 | 54 |
}, nil |
| 55 | 55 |
}), |
| 56 | 56 |
} |
| 57 |
- body, err := client.ContainerStats(context.Background(), "container_id", c.stream) |
|
| 57 |
+ resp, err := client.ContainerStats(context.Background(), "container_id", c.stream) |
|
| 58 | 58 |
if err != nil {
|
| 59 | 59 |
t.Fatal(err) |
| 60 | 60 |
} |
| 61 |
- defer body.Close() |
|
| 62 |
- content, err := ioutil.ReadAll(body) |
|
| 61 |
+ defer resp.Body.Close() |
|
| 62 |
+ content, err := ioutil.ReadAll(resp.Body) |
|
| 63 | 63 |
if err != nil {
|
| 64 | 64 |
t.Fatal(err) |
| 65 | 65 |
} |
| ... | ... |
@@ -39,7 +39,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio |
| 39 | 39 |
return types.ImageBuildResponse{}, err
|
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
- osType := getDockerOS(serverResp.header.Get("Server"))
|
|
| 42 |
+ osType := GetDockerOS(serverResp.header.Get("Server"))
|
|
| 43 | 43 |
|
| 44 | 44 |
return types.ImageBuildResponse{
|
| 45 | 45 |
Body: serverResp.body, |
| ... | ... |
@@ -113,7 +113,8 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro |
| 113 | 113 |
return query, nil |
| 114 | 114 |
} |
| 115 | 115 |
|
| 116 |
-func getDockerOS(serverHeader string) string {
|
|
| 116 |
+// GetDockerOS returns the operating system based on the server header from the daemon. |
|
| 117 |
+func GetDockerOS(serverHeader string) string {
|
|
| 117 | 118 |
var osType string |
| 118 | 119 |
matches := headerRegexp.FindStringSubmatch(serverHeader) |
| 119 | 120 |
if len(matches) > 0 {
|
| ... | ... |
@@ -51,7 +51,7 @@ type ContainerAPIClient interface {
|
| 51 | 51 |
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error |
| 52 | 52 |
ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error |
| 53 | 53 |
ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error) |
| 54 |
- ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error) |
|
| 54 |
+ ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error) |
|
| 55 | 55 |
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error |
| 56 | 56 |
ContainerStop(ctx context.Context, container string, timeout *time.Duration) error |
| 57 | 57 |
ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error) |
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/image" |
| 15 | 15 |
"github.com/docker/docker/pkg/idtools" |
| 16 | 16 |
"github.com/docker/docker/pkg/parsers" |
| 17 |
+ "github.com/docker/docker/pkg/platform" |
|
| 17 | 18 |
"github.com/docker/docker/pkg/sysinfo" |
| 18 | 19 |
"github.com/docker/docker/pkg/system" |
| 19 | 20 |
"github.com/docker/docker/runconfig" |
| ... | ... |
@@ -379,7 +380,62 @@ func driverOptions(config *Config) []nwconfig.Option {
|
| 379 | 379 |
} |
| 380 | 380 |
|
| 381 | 381 |
func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
|
| 382 |
- return nil, nil |
|
| 382 |
+ if !c.IsRunning() {
|
|
| 383 |
+ return nil, errNotRunning{c.ID}
|
|
| 384 |
+ } |
|
| 385 |
+ |
|
| 386 |
+ // Obtain the stats from HCS via libcontainerd |
|
| 387 |
+ stats, err := daemon.containerd.Stats(c.ID) |
|
| 388 |
+ if err != nil {
|
|
| 389 |
+ return nil, err |
|
| 390 |
+ } |
|
| 391 |
+ |
|
| 392 |
+ // Start with an empty structure |
|
| 393 |
+ s := &types.StatsJSON{}
|
|
| 394 |
+ |
|
| 395 |
+ // Populate the CPU/processor statistics |
|
| 396 |
+ s.CPUStats = types.CPUStats{
|
|
| 397 |
+ CPUUsage: types.CPUUsage{
|
|
| 398 |
+ TotalUsage: stats.Processor.TotalRuntime100ns, |
|
| 399 |
+ UsageInKernelmode: stats.Processor.RuntimeKernel100ns, |
|
| 400 |
+ UsageInUsermode: stats.Processor.RuntimeKernel100ns, |
|
| 401 |
+ }, |
|
| 402 |
+ } |
|
| 403 |
+ |
|
| 404 |
+ // Populate the memory statistics |
|
| 405 |
+ s.MemoryStats = types.MemoryStats{
|
|
| 406 |
+ Commit: stats.Memory.UsageCommitBytes, |
|
| 407 |
+ CommitPeak: stats.Memory.UsageCommitPeakBytes, |
|
| 408 |
+ PrivateWorkingSet: stats.Memory.UsagePrivateWorkingSetBytes, |
|
| 409 |
+ } |
|
| 410 |
+ |
|
| 411 |
+ // Populate the storage statistics |
|
| 412 |
+ s.StorageStats = types.StorageStats{
|
|
| 413 |
+ ReadCountNormalized: stats.Storage.ReadCountNormalized, |
|
| 414 |
+ ReadSizeBytes: stats.Storage.ReadSizeBytes, |
|
| 415 |
+ WriteCountNormalized: stats.Storage.WriteCountNormalized, |
|
| 416 |
+ WriteSizeBytes: stats.Storage.WriteSizeBytes, |
|
| 417 |
+ } |
|
| 418 |
+ |
|
| 419 |
+ // Populate the network statistics |
|
| 420 |
+ s.Networks = make(map[string]types.NetworkStats) |
|
| 421 |
+ |
|
| 422 |
+ for _, nstats := range stats.Network {
|
|
| 423 |
+ s.Networks[nstats.EndpointId] = types.NetworkStats{
|
|
| 424 |
+ RxBytes: nstats.BytesReceived, |
|
| 425 |
+ RxPackets: nstats.PacketsReceived, |
|
| 426 |
+ RxDropped: nstats.DroppedPacketsIncoming, |
|
| 427 |
+ TxBytes: nstats.BytesSent, |
|
| 428 |
+ TxPackets: nstats.PacketsSent, |
|
| 429 |
+ TxDropped: nstats.DroppedPacketsOutgoing, |
|
| 430 |
+ } |
|
| 431 |
+ } |
|
| 432 |
+ |
|
| 433 |
+ // Set the timestamp |
|
| 434 |
+ s.Stats.Read = stats.Timestamp |
|
| 435 |
+ s.Stats.NumProcs = platform.NumProcs() |
|
| 436 |
+ |
|
| 437 |
+ return s, nil |
|
| 383 | 438 |
} |
| 384 | 439 |
|
| 385 | 440 |
// setDefaultIsolation determine the default isolation mode for the |
| ... | ... |
@@ -3,8 +3,8 @@ package daemon |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"errors" |
| 6 |
- "fmt" |
|
| 7 | 6 |
"runtime" |
| 7 |
+ "time" |
|
| 8 | 8 |
|
| 9 | 9 |
"golang.org/x/net/context" |
| 10 | 10 |
|
| ... | ... |
@@ -19,9 +19,6 @@ import ( |
| 19 | 19 |
// ContainerStats writes information about the container to the stream |
| 20 | 20 |
// given in the config object. |
| 21 | 21 |
func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
|
| 22 |
- if runtime.GOOS == "windows" {
|
|
| 23 |
- return errors.New("Windows does not support stats")
|
|
| 24 |
- } |
|
| 25 | 22 |
// Remote API version (used for backwards compatibility) |
| 26 | 23 |
apiVersion := config.Version |
| 27 | 24 |
|
| ... | ... |
@@ -44,10 +41,13 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c |
| 44 | 44 |
} |
| 45 | 45 |
|
| 46 | 46 |
var preCPUStats types.CPUStats |
| 47 |
+ var preRead time.Time |
|
| 47 | 48 |
getStatJSON := func(v interface{}) *types.StatsJSON {
|
| 48 | 49 |
ss := v.(types.StatsJSON) |
| 49 | 50 |
ss.PreCPUStats = preCPUStats |
| 51 |
+ ss.PreRead = preRead |
|
| 50 | 52 |
preCPUStats = ss.CPUStats |
| 53 |
+ preRead = ss.Read |
|
| 51 | 54 |
return &ss |
| 52 | 55 |
} |
| 53 | 56 |
|
| ... | ... |
@@ -67,6 +67,9 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c |
| 67 | 67 |
var statsJSON interface{}
|
| 68 | 68 |
statsJSONPost120 := getStatJSON(v) |
| 69 | 69 |
if versions.LessThan(apiVersion, "1.21") {
|
| 70 |
+ if runtime.GOOS == "windows" {
|
|
| 71 |
+ return errors.New("API versions pre v1.21 do not support stats on Windows")
|
|
| 72 |
+ } |
|
| 70 | 73 |
var ( |
| 71 | 74 |
rxBytes uint64 |
| 72 | 75 |
rxPackets uint64 |
| ... | ... |
@@ -138,7 +141,8 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*types. |
| 138 | 138 |
return nil, err |
| 139 | 139 |
} |
| 140 | 140 |
|
| 141 |
- if !container.Config.NetworkDisabled {
|
|
| 141 |
+ // We already have the network stats on Windows directly from HCS. |
|
| 142 |
+ if !container.Config.NetworkDisabled && runtime.GOOS != "windows" {
|
|
| 142 | 143 |
if stats.Networks, err = daemon.getNetworkStats(container); err != nil {
|
| 143 | 144 |
return nil, err |
| 144 | 145 |
} |
| ... | ... |
@@ -146,51 +150,3 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*types. |
| 146 | 146 |
|
| 147 | 147 |
return stats, nil |
| 148 | 148 |
} |
| 149 |
- |
|
| 150 |
-// Resolve Network SandboxID in case the container reuse another container's network stack |
|
| 151 |
-func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
|
|
| 152 |
- curr := c |
|
| 153 |
- for curr.HostConfig.NetworkMode.IsContainer() {
|
|
| 154 |
- containerID := curr.HostConfig.NetworkMode.ConnectedContainer() |
|
| 155 |
- connected, err := daemon.GetContainer(containerID) |
|
| 156 |
- if err != nil {
|
|
| 157 |
- return "", fmt.Errorf("Could not get container for %s", containerID)
|
|
| 158 |
- } |
|
| 159 |
- curr = connected |
|
| 160 |
- } |
|
| 161 |
- return curr.NetworkSettings.SandboxID, nil |
|
| 162 |
-} |
|
| 163 |
- |
|
| 164 |
-func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
|
|
| 165 |
- sandboxID, err := daemon.getNetworkSandboxID(c) |
|
| 166 |
- if err != nil {
|
|
| 167 |
- return nil, err |
|
| 168 |
- } |
|
| 169 |
- |
|
| 170 |
- sb, err := daemon.netController.SandboxByID(sandboxID) |
|
| 171 |
- if err != nil {
|
|
| 172 |
- return nil, err |
|
| 173 |
- } |
|
| 174 |
- |
|
| 175 |
- lnstats, err := sb.Statistics() |
|
| 176 |
- if err != nil {
|
|
| 177 |
- return nil, err |
|
| 178 |
- } |
|
| 179 |
- |
|
| 180 |
- stats := make(map[string]types.NetworkStats) |
|
| 181 |
- // Convert libnetwork nw stats into engine-api stats |
|
| 182 |
- for ifName, ifStats := range lnstats {
|
|
| 183 |
- stats[ifName] = types.NetworkStats{
|
|
| 184 |
- RxBytes: ifStats.RxBytes, |
|
| 185 |
- RxPackets: ifStats.RxPackets, |
|
| 186 |
- RxErrors: ifStats.RxErrors, |
|
| 187 |
- RxDropped: ifStats.RxDropped, |
|
| 188 |
- TxBytes: ifStats.TxBytes, |
|
| 189 |
- TxPackets: ifStats.TxPackets, |
|
| 190 |
- TxErrors: ifStats.TxErrors, |
|
| 191 |
- TxDropped: ifStats.TxDropped, |
|
| 192 |
- } |
|
| 193 |
- } |
|
| 194 |
- |
|
| 195 |
- return stats, nil |
|
| 196 |
-} |
| 197 | 149 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,132 @@ |
| 0 |
+// +build !solaris |
|
| 1 |
+ |
|
| 2 |
+package daemon |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "bufio" |
|
| 6 |
+ "sync" |
|
| 7 |
+ "time" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/Sirupsen/logrus" |
|
| 10 |
+ "github.com/docker/docker/api/types" |
|
| 11 |
+ "github.com/docker/docker/container" |
|
| 12 |
+ "github.com/docker/docker/pkg/pubsub" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+type statsSupervisor interface {
|
|
| 16 |
+ // GetContainerStats collects all the stats related to a container |
|
| 17 |
+ GetContainerStats(container *container.Container) (*types.StatsJSON, error) |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// newStatsCollector returns a new statsCollector that collections |
|
| 21 |
+// stats for a registered container at the specified interval. |
|
| 22 |
+// The collector allows non-running containers to be added |
|
| 23 |
+// and will start processing stats when they are started. |
|
| 24 |
+func (daemon *Daemon) newStatsCollector(interval time.Duration) *statsCollector {
|
|
| 25 |
+ s := &statsCollector{
|
|
| 26 |
+ interval: interval, |
|
| 27 |
+ supervisor: daemon, |
|
| 28 |
+ publishers: make(map[*container.Container]*pubsub.Publisher), |
|
| 29 |
+ bufReader: bufio.NewReaderSize(nil, 128), |
|
| 30 |
+ } |
|
| 31 |
+ platformNewStatsCollector(s) |
|
| 32 |
+ go s.run() |
|
| 33 |
+ return s |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+// statsCollector manages and provides container resource stats |
|
| 37 |
+type statsCollector struct {
|
|
| 38 |
+ m sync.Mutex |
|
| 39 |
+ supervisor statsSupervisor |
|
| 40 |
+ interval time.Duration |
|
| 41 |
+ publishers map[*container.Container]*pubsub.Publisher |
|
| 42 |
+ bufReader *bufio.Reader |
|
| 43 |
+ |
|
| 44 |
+ // The following fields are not set on Windows currently. |
|
| 45 |
+ clockTicksPerSecond uint64 |
|
| 46 |
+ machineMemory uint64 |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+// collect registers the container with the collector and adds it to |
|
| 50 |
+// the event loop for collection on the specified interval returning |
|
| 51 |
+// a channel for the subscriber to receive on. |
|
| 52 |
+func (s *statsCollector) collect(c *container.Container) chan interface{} {
|
|
| 53 |
+ s.m.Lock() |
|
| 54 |
+ defer s.m.Unlock() |
|
| 55 |
+ publisher, exists := s.publishers[c] |
|
| 56 |
+ if !exists {
|
|
| 57 |
+ publisher = pubsub.NewPublisher(100*time.Millisecond, 1024) |
|
| 58 |
+ s.publishers[c] = publisher |
|
| 59 |
+ } |
|
| 60 |
+ return publisher.Subscribe() |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// stopCollection closes the channels for all subscribers and removes |
|
| 64 |
+// the container from metrics collection. |
|
| 65 |
+func (s *statsCollector) stopCollection(c *container.Container) {
|
|
| 66 |
+ s.m.Lock() |
|
| 67 |
+ if publisher, exists := s.publishers[c]; exists {
|
|
| 68 |
+ publisher.Close() |
|
| 69 |
+ delete(s.publishers, c) |
|
| 70 |
+ } |
|
| 71 |
+ s.m.Unlock() |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// unsubscribe removes a specific subscriber from receiving updates for a container's stats. |
|
| 75 |
+func (s *statsCollector) unsubscribe(c *container.Container, ch chan interface{}) {
|
|
| 76 |
+ s.m.Lock() |
|
| 77 |
+ publisher := s.publishers[c] |
|
| 78 |
+ if publisher != nil {
|
|
| 79 |
+ publisher.Evict(ch) |
|
| 80 |
+ if publisher.Len() == 0 {
|
|
| 81 |
+ delete(s.publishers, c) |
|
| 82 |
+ } |
|
| 83 |
+ } |
|
| 84 |
+ s.m.Unlock() |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func (s *statsCollector) run() {
|
|
| 88 |
+ type publishersPair struct {
|
|
| 89 |
+ container *container.Container |
|
| 90 |
+ publisher *pubsub.Publisher |
|
| 91 |
+ } |
|
| 92 |
+ // we cannot determine the capacity here. |
|
| 93 |
+ // it will grow enough in first iteration |
|
| 94 |
+ var pairs []publishersPair |
|
| 95 |
+ |
|
| 96 |
+ for range time.Tick(s.interval) {
|
|
| 97 |
+ // it does not make sense in the first iteration, |
|
| 98 |
+ // but saves allocations in further iterations |
|
| 99 |
+ pairs = pairs[:0] |
|
| 100 |
+ |
|
| 101 |
+ s.m.Lock() |
|
| 102 |
+ for container, publisher := range s.publishers {
|
|
| 103 |
+ // copy pointers here to release the lock ASAP |
|
| 104 |
+ pairs = append(pairs, publishersPair{container, publisher})
|
|
| 105 |
+ } |
|
| 106 |
+ s.m.Unlock() |
|
| 107 |
+ if len(pairs) == 0 {
|
|
| 108 |
+ continue |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ systemUsage, err := s.getSystemCPUUsage() |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ logrus.Errorf("collecting system cpu usage: %v", err)
|
|
| 114 |
+ continue |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ for _, pair := range pairs {
|
|
| 118 |
+ stats, err := s.supervisor.GetContainerStats(pair.container) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ if _, ok := err.(errNotRunning); !ok {
|
|
| 121 |
+ logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
|
|
| 122 |
+ } |
|
| 123 |
+ continue |
|
| 124 |
+ } |
|
| 125 |
+ // FIXME: move to containerd on Linux (not Windows) |
|
| 126 |
+ stats.CPUStats.SystemUsage = systemUsage |
|
| 127 |
+ |
|
| 128 |
+ pair.publisher.Publish(*stats) |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+} |
| ... | ... |
@@ -3,141 +3,23 @@ |
| 3 | 3 |
package daemon |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
- "bufio" |
|
| 7 | 6 |
"fmt" |
| 8 | 7 |
"os" |
| 9 | 8 |
"strconv" |
| 10 | 9 |
"strings" |
| 11 |
- "sync" |
|
| 12 |
- "time" |
|
| 13 | 10 |
|
| 14 |
- "github.com/Sirupsen/logrus" |
|
| 15 |
- "github.com/docker/docker/api/types" |
|
| 16 |
- "github.com/docker/docker/container" |
|
| 17 |
- "github.com/docker/docker/pkg/pubsub" |
|
| 18 | 11 |
sysinfo "github.com/docker/docker/pkg/system" |
| 19 | 12 |
"github.com/opencontainers/runc/libcontainer/system" |
| 20 | 13 |
) |
| 21 | 14 |
|
| 22 |
-type statsSupervisor interface {
|
|
| 23 |
- // GetContainerStats collects all the stats related to a container |
|
| 24 |
- GetContainerStats(container *container.Container) (*types.StatsJSON, error) |
|
| 25 |
-} |
|
| 26 |
- |
|
| 27 |
-// newStatsCollector returns a new statsCollector that collections |
|
| 28 |
-// network and cgroup stats for a registered container at the specified |
|
| 29 |
-// interval. The collector allows non-running containers to be added |
|
| 30 |
-// and will start processing stats when they are started. |
|
| 31 |
-func (daemon *Daemon) newStatsCollector(interval time.Duration) *statsCollector {
|
|
| 32 |
- s := &statsCollector{
|
|
| 33 |
- interval: interval, |
|
| 34 |
- supervisor: daemon, |
|
| 35 |
- publishers: make(map[*container.Container]*pubsub.Publisher), |
|
| 36 |
- clockTicksPerSecond: uint64(system.GetClockTicks()), |
|
| 37 |
- bufReader: bufio.NewReaderSize(nil, 128), |
|
| 38 |
- } |
|
| 15 |
+// platformNewStatsCollector performs platform specific initialisation of the |
|
| 16 |
+// statsCollector structure. |
|
| 17 |
+func platformNewStatsCollector(s *statsCollector) {
|
|
| 18 |
+ s.clockTicksPerSecond = uint64(system.GetClockTicks()) |
|
| 39 | 19 |
meminfo, err := sysinfo.ReadMemInfo() |
| 40 | 20 |
if err == nil && meminfo.MemTotal > 0 {
|
| 41 | 21 |
s.machineMemory = uint64(meminfo.MemTotal) |
| 42 | 22 |
} |
| 43 |
- |
|
| 44 |
- go s.run() |
|
| 45 |
- return s |
|
| 46 |
-} |
|
| 47 |
- |
|
| 48 |
-// statsCollector manages and provides container resource stats |
|
| 49 |
-type statsCollector struct {
|
|
| 50 |
- m sync.Mutex |
|
| 51 |
- supervisor statsSupervisor |
|
| 52 |
- interval time.Duration |
|
| 53 |
- clockTicksPerSecond uint64 |
|
| 54 |
- publishers map[*container.Container]*pubsub.Publisher |
|
| 55 |
- bufReader *bufio.Reader |
|
| 56 |
- machineMemory uint64 |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-// collect registers the container with the collector and adds it to |
|
| 60 |
-// the event loop for collection on the specified interval returning |
|
| 61 |
-// a channel for the subscriber to receive on. |
|
| 62 |
-func (s *statsCollector) collect(c *container.Container) chan interface{} {
|
|
| 63 |
- s.m.Lock() |
|
| 64 |
- defer s.m.Unlock() |
|
| 65 |
- publisher, exists := s.publishers[c] |
|
| 66 |
- if !exists {
|
|
| 67 |
- publisher = pubsub.NewPublisher(100*time.Millisecond, 1024) |
|
| 68 |
- s.publishers[c] = publisher |
|
| 69 |
- } |
|
| 70 |
- return publisher.Subscribe() |
|
| 71 |
-} |
|
| 72 |
- |
|
| 73 |
-// stopCollection closes the channels for all subscribers and removes |
|
| 74 |
-// the container from metrics collection. |
|
| 75 |
-func (s *statsCollector) stopCollection(c *container.Container) {
|
|
| 76 |
- s.m.Lock() |
|
| 77 |
- if publisher, exists := s.publishers[c]; exists {
|
|
| 78 |
- publisher.Close() |
|
| 79 |
- delete(s.publishers, c) |
|
| 80 |
- } |
|
| 81 |
- s.m.Unlock() |
|
| 82 |
-} |
|
| 83 |
- |
|
| 84 |
-// unsubscribe removes a specific subscriber from receiving updates for a container's stats. |
|
| 85 |
-func (s *statsCollector) unsubscribe(c *container.Container, ch chan interface{}) {
|
|
| 86 |
- s.m.Lock() |
|
| 87 |
- publisher := s.publishers[c] |
|
| 88 |
- if publisher != nil {
|
|
| 89 |
- publisher.Evict(ch) |
|
| 90 |
- if publisher.Len() == 0 {
|
|
| 91 |
- delete(s.publishers, c) |
|
| 92 |
- } |
|
| 93 |
- } |
|
| 94 |
- s.m.Unlock() |
|
| 95 |
-} |
|
| 96 |
- |
|
| 97 |
-func (s *statsCollector) run() {
|
|
| 98 |
- type publishersPair struct {
|
|
| 99 |
- container *container.Container |
|
| 100 |
- publisher *pubsub.Publisher |
|
| 101 |
- } |
|
| 102 |
- // we cannot determine the capacity here. |
|
| 103 |
- // it will grow enough in first iteration |
|
| 104 |
- var pairs []publishersPair |
|
| 105 |
- |
|
| 106 |
- for range time.Tick(s.interval) {
|
|
| 107 |
- // it does not make sense in the first iteration, |
|
| 108 |
- // but saves allocations in further iterations |
|
| 109 |
- pairs = pairs[:0] |
|
| 110 |
- |
|
| 111 |
- s.m.Lock() |
|
| 112 |
- for container, publisher := range s.publishers {
|
|
| 113 |
- // copy pointers here to release the lock ASAP |
|
| 114 |
- pairs = append(pairs, publishersPair{container, publisher})
|
|
| 115 |
- } |
|
| 116 |
- s.m.Unlock() |
|
| 117 |
- if len(pairs) == 0 {
|
|
| 118 |
- continue |
|
| 119 |
- } |
|
| 120 |
- |
|
| 121 |
- systemUsage, err := s.getSystemCPUUsage() |
|
| 122 |
- if err != nil {
|
|
| 123 |
- logrus.Errorf("collecting system cpu usage: %v", err)
|
|
| 124 |
- continue |
|
| 125 |
- } |
|
| 126 |
- |
|
| 127 |
- for _, pair := range pairs {
|
|
| 128 |
- stats, err := s.supervisor.GetContainerStats(pair.container) |
|
| 129 |
- if err != nil {
|
|
| 130 |
- if _, ok := err.(errNotRunning); !ok {
|
|
| 131 |
- logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
|
|
| 132 |
- } |
|
| 133 |
- continue |
|
| 134 |
- } |
|
| 135 |
- // FIXME: move to containerd |
|
| 136 |
- stats.CPUStats.SystemUsage = systemUsage |
|
| 137 |
- |
|
| 138 |
- pair.publisher.Publish(*stats) |
|
| 139 |
- } |
|
| 140 |
- } |
|
| 141 | 23 |
} |
| 142 | 24 |
|
| 143 | 25 |
const nanoSecondsPerSecond = 1e9 |
| ... | ... |
@@ -1,35 +1,15 @@ |
| 1 |
-package daemon |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "time" |
|
| 5 |
- |
|
| 6 |
- "github.com/docker/docker/container" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// newStatsCollector returns a new statsCollector for collection stats |
|
| 10 |
-// for a registered container at the specified interval. The collector allows |
|
| 11 |
-// non-running containers to be added and will start processing stats when |
|
| 12 |
-// they are started. |
|
| 13 |
-func (daemon *Daemon) newStatsCollector(interval time.Duration) *statsCollector {
|
|
| 14 |
- return &statsCollector{}
|
|
| 15 |
-} |
|
| 1 |
+// +build windows |
|
| 16 | 2 |
|
| 17 |
-// statsCollector manages and provides container resource stats |
|
| 18 |
-type statsCollector struct {
|
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-// collect registers the container with the collector and adds it to |
|
| 22 |
-// the event loop for collection on the specified interval returning |
|
| 23 |
-// a channel for the subscriber to receive on. |
|
| 24 |
-func (s *statsCollector) collect(c *container.Container) chan interface{} {
|
|
| 25 |
- return nil |
|
| 26 |
-} |
|
| 3 |
+package daemon |
|
| 27 | 4 |
|
| 28 |
-// stopCollection closes the channels for all subscribers and removes |
|
| 29 |
-// the container from metrics collection. |
|
| 30 |
-func (s *statsCollector) stopCollection(c *container.Container) {
|
|
| 5 |
+// platformNewStatsCollector performs platform specific initialisation of the |
|
| 6 |
+// statsCollector structure. This is a no-op on Windows. |
|
| 7 |
+func platformNewStatsCollector(s *statsCollector) {
|
|
| 31 | 8 |
} |
| 32 | 9 |
|
| 33 |
-// unsubscribe removes a specific subscriber from receiving updates for a container's stats. |
|
| 34 |
-func (s *statsCollector) unsubscribe(c *container.Container, ch chan interface{}) {
|
|
| 10 |
+// getSystemCPUUsage returns the host system's cpu usage in |
|
| 11 |
+// nanoseconds. An error is returned if the format of the underlying |
|
| 12 |
+// file does not match. This is a no-op on Windows. |
|
| 13 |
+func (s *statsCollector) getSystemCPUUsage() (uint64, error) {
|
|
| 14 |
+ return 0, nil |
|
| 35 | 15 |
} |
| 36 | 16 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,58 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package daemon |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/types" |
|
| 8 |
+ "github.com/docker/docker/container" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// Resolve Network SandboxID in case the container reuse another container's network stack |
|
| 12 |
+func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
|
|
| 13 |
+ curr := c |
|
| 14 |
+ for curr.HostConfig.NetworkMode.IsContainer() {
|
|
| 15 |
+ containerID := curr.HostConfig.NetworkMode.ConnectedContainer() |
|
| 16 |
+ connected, err := daemon.GetContainer(containerID) |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ return "", fmt.Errorf("Could not get container for %s", containerID)
|
|
| 19 |
+ } |
|
| 20 |
+ curr = connected |
|
| 21 |
+ } |
|
| 22 |
+ return curr.NetworkSettings.SandboxID, nil |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
|
|
| 26 |
+ sandboxID, err := daemon.getNetworkSandboxID(c) |
|
| 27 |
+ if err != nil {
|
|
| 28 |
+ return nil, err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ sb, err := daemon.netController.SandboxByID(sandboxID) |
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ return nil, err |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ lnstats, err := sb.Statistics() |
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ return nil, err |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ stats := make(map[string]types.NetworkStats) |
|
| 42 |
+ // Convert libnetwork nw stats into engine-api stats |
|
| 43 |
+ for ifName, ifStats := range lnstats {
|
|
| 44 |
+ stats[ifName] = types.NetworkStats{
|
|
| 45 |
+ RxBytes: ifStats.RxBytes, |
|
| 46 |
+ RxPackets: ifStats.RxPackets, |
|
| 47 |
+ RxErrors: ifStats.RxErrors, |
|
| 48 |
+ RxDropped: ifStats.RxDropped, |
|
| 49 |
+ TxBytes: ifStats.TxBytes, |
|
| 50 |
+ TxPackets: ifStats.TxPackets, |
|
| 51 |
+ TxErrors: ifStats.TxErrors, |
|
| 52 |
+ TxDropped: ifStats.TxDropped, |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ |
|
| 56 |
+ return stats, nil |
|
| 57 |
+} |
| 0 | 58 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/types" |
|
| 4 |
+ "github.com/docker/docker/container" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// Windows network stats are obtained directly through HCS, hence this is a no-op. |
|
| 8 |
+func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
|
|
| 9 |
+ return make(map[string]types.NetworkStats), nil |
|
| 10 |
+} |
| ... | ... |
@@ -27,7 +27,7 @@ If you want more detailed information about a container's resource usage, use th |
| 27 | 27 |
|
| 28 | 28 |
## Examples |
| 29 | 29 |
|
| 30 |
-Running `docker stats` on all running containers |
|
| 30 |
+Running `docker stats` on all running containers against a Linux daemon. |
|
| 31 | 31 |
|
| 32 | 32 |
$ docker stats |
| 33 | 33 |
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O |
| ... | ... |
@@ -35,9 +35,30 @@ Running `docker stats` on all running containers |
| 35 | 35 |
9c76f7834ae2 0.07% 2.746 MiB / 64 MiB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B |
| 36 | 36 |
d1ea048f04e4 0.03% 4.583 MiB / 64 MiB 6.30% 2.854 KB / 648 B 27.7 MB / 0 B |
| 37 | 37 |
|
| 38 |
-Running `docker stats` on multiple containers by name and id. |
|
| 38 |
+Running `docker stats` on multiple containers by name and id against a Linux daemon. |
|
| 39 | 39 |
|
| 40 | 40 |
$ docker stats fervent_panini 5acfcb1b4fd1 |
| 41 | 41 |
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O |
| 42 | 42 |
5acfcb1b4fd1 0.00% 115.2 MiB/1.045 GiB 11.03% 1.422 kB/648 B |
| 43 | 43 |
fervent_panini 0.02% 11.08 MiB/1.045 GiB 1.06% 648 B/648 B |
| 44 |
+ |
|
| 45 |
+Running `docker stats` on all running containers against a Windows daemon. |
|
| 46 |
+ |
|
| 47 |
+ PS E:\> docker stats |
|
| 48 |
+ CONTAINER CPU % PRIV WORKING SET NET I/O BLOCK I/O |
|
| 49 |
+ 09d3bb5b1604 6.61% 38.21 MiB 17.1 kB / 7.73 kB 10.7 MB / 3.57 MB |
|
| 50 |
+ 9db7aa4d986d 9.19% 38.26 MiB 15.2 kB / 7.65 kB 10.6 MB / 3.3 MB |
|
| 51 |
+ 3f214c61ad1d 0.00% 28.64 MiB 64 kB / 6.84 kB 4.42 MB / 6.93 MB |
|
| 52 |
+ |
|
| 53 |
+Running `docker stats` on multiple containers by name and id against a Windows daemon. |
|
| 54 |
+ |
|
| 55 |
+ PS E:\> docker ps -a |
|
| 56 |
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
|
| 57 |
+ 3f214c61ad1d nanoserver "cmd" 2 minutes ago Up 2 minutes big_minsky |
|
| 58 |
+ 9db7aa4d986d windowsservercore "cmd" 2 minutes ago Up 2 minutes mad_wilson |
|
| 59 |
+ 09d3bb5b1604 windowsservercore "cmd" 2 minutes ago Up 2 minutes affectionate_easley |
|
| 60 |
+ |
|
| 61 |
+ PS E:\> docker stats 3f214c61ad1d mad_wilson |
|
| 62 |
+ CONTAINER CPU % PRIV WORKING SET NET I/O BLOCK I/O |
|
| 63 |
+ 3f214c61ad1d 0.00% 46.25 MiB 76.3 kB / 7.92 kB 10.3 MB / 14.7 MB |
|
| 64 |
+ mad_wilson 9.59% 40.09 MiB 27.6 kB / 8.81 kB 17 MB / 20.1 MB |
|
| 44 | 65 |
\ No newline at end of file |
| ... | ... |
@@ -175,12 +175,10 @@ func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) {
|
| 175 | 175 |
} |
| 176 | 176 |
|
| 177 | 177 |
func (s *DockerSuite) TestGetContainerStats(c *check.C) {
|
| 178 |
- // Problematic on Windows as Windows does not support stats |
|
| 179 |
- testRequires(c, DaemonIsLinux) |
|
| 180 | 178 |
var ( |
| 181 | 179 |
name = "statscontainer" |
| 182 | 180 |
) |
| 183 |
- dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") |
|
| 181 |
+ runSleepingContainer(c, "--name", name) |
|
| 184 | 182 |
|
| 185 | 183 |
type b struct {
|
| 186 | 184 |
status int |
| ... | ... |
@@ -214,9 +212,7 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) {
|
| 214 | 214 |
} |
| 215 | 215 |
|
| 216 | 216 |
func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
|
| 217 |
- // Problematic on Windows as Windows does not support stats |
|
| 218 |
- testRequires(c, DaemonIsLinux) |
|
| 219 |
- out, _ := dockerCmd(c, "run", "-d", "busybox", "top") |
|
| 217 |
+ out, _ := runSleepingContainer(c) |
|
| 220 | 218 |
id := strings.TrimSpace(out) |
| 221 | 219 |
|
| 222 | 220 |
buf := &integration.ChannelBuffer{make(chan []byte, 1)}
|
| ... | ... |
@@ -251,10 +247,8 @@ func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
|
| 251 | 251 |
// previous test was just checking one stat entry so it didn't fail (stats with |
| 252 | 252 |
// stream false always return one stat) |
| 253 | 253 |
func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
|
| 254 |
- // Problematic on Windows as Windows does not support stats |
|
| 255 |
- testRequires(c, DaemonIsLinux) |
|
| 256 | 254 |
name := "statscontainer" |
| 257 |
- dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") |
|
| 255 |
+ runSleepingContainer(c, "--name", name) |
|
| 258 | 256 |
|
| 259 | 257 |
type b struct {
|
| 260 | 258 |
status int |
| ... | ... |
@@ -289,10 +283,8 @@ func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
|
| 289 | 289 |
} |
| 290 | 290 |
|
| 291 | 291 |
func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
|
| 292 |
- // Problematic on Windows as Windows does not support stats |
|
| 293 |
- testRequires(c, DaemonIsLinux) |
|
| 294 | 292 |
name := "statscontainer" |
| 295 |
- dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") |
|
| 293 |
+ runSleepingContainer(c, "--name", name) |
|
| 296 | 294 |
|
| 297 | 295 |
type b struct {
|
| 298 | 296 |
status int |
| ... | ... |
@@ -319,16 +311,14 @@ func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
|
| 319 | 319 |
c.Assert(sr.status, checker.Equals, http.StatusOK) |
| 320 | 320 |
|
| 321 | 321 |
s := string(sr.body) |
| 322 |
- // count occurrences of "read" of types.Stats |
|
| 323 |
- c.Assert(strings.Count(s, "read"), checker.Equals, 1, check.Commentf("Expected only one stat streamed, got %d", strings.Count(s, "read")))
|
|
| 322 |
+ // count occurrences of `"read"` of types.Stats |
|
| 323 |
+ c.Assert(strings.Count(s, `"read"`), checker.Equals, 1, check.Commentf("Expected only one stat streamed, got %d", strings.Count(s, `"read"`)))
|
|
| 324 | 324 |
} |
| 325 | 325 |
} |
| 326 | 326 |
|
| 327 | 327 |
func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) {
|
| 328 |
- // Problematic on Windows as Windows does not support stats |
|
| 329 |
- testRequires(c, DaemonIsLinux) |
|
| 330 | 328 |
name := "statscontainer" |
| 331 |
- dockerCmd(c, "create", "--name", name, "busybox", "top") |
|
| 329 |
+ dockerCmd(c, "create", "--name", name, "busybox", "ps") |
|
| 332 | 330 |
|
| 333 | 331 |
type stats struct {
|
| 334 | 332 |
status int |
| ... | ... |
@@ -20,7 +20,6 @@ import ( |
| 20 | 20 |
var expectedNetworkInterfaceStats = strings.Split("rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors tx_packets", " ")
|
| 21 | 21 |
|
| 22 | 22 |
func (s *DockerSuite) TestApiStatsNoStreamGetCpu(c *check.C) {
|
| 23 |
- testRequires(c, DaemonIsLinux) |
|
| 24 | 23 |
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello'; usleep 100000; done") |
| 25 | 24 |
|
| 26 | 25 |
id := strings.TrimSpace(out) |
| ... | ... |
@@ -37,15 +36,30 @@ func (s *DockerSuite) TestApiStatsNoStreamGetCpu(c *check.C) {
|
| 37 | 37 |
body.Close() |
| 38 | 38 |
|
| 39 | 39 |
var cpuPercent = 0.0 |
| 40 |
- cpuDelta := float64(v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage) |
|
| 41 |
- systemDelta := float64(v.CPUStats.SystemUsage - v.PreCPUStats.SystemUsage) |
|
| 42 |
- cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 |
|
| 40 |
+ |
|
| 41 |
+ if daemonPlatform != "windows" {
|
|
| 42 |
+ cpuDelta := float64(v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage) |
|
| 43 |
+ systemDelta := float64(v.CPUStats.SystemUsage - v.PreCPUStats.SystemUsage) |
|
| 44 |
+ cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 |
|
| 45 |
+ } else {
|
|
| 46 |
+ // Max number of 100ns intervals between the previous time read and now |
|
| 47 |
+ possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals |
|
| 48 |
+ possIntervals /= 100 // Convert to number of 100ns intervals |
|
| 49 |
+ possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors |
|
| 50 |
+ |
|
| 51 |
+ // Intervals used |
|
| 52 |
+ intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage |
|
| 53 |
+ |
|
| 54 |
+ // Percentage avoiding divide-by-zero |
|
| 55 |
+ if possIntervals > 0 {
|
|
| 56 |
+ cpuPercent = float64(intervalsUsed) / float64(possIntervals) * 100.0 |
|
| 57 |
+ } |
|
| 58 |
+ } |
|
| 43 | 59 |
|
| 44 | 60 |
c.Assert(cpuPercent, check.Not(checker.Equals), 0.0, check.Commentf("docker stats with no-stream get cpu usage failed: was %v", cpuPercent))
|
| 45 | 61 |
} |
| 46 | 62 |
|
| 47 | 63 |
func (s *DockerSuite) TestApiStatsStoppedContainerInGoroutines(c *check.C) {
|
| 48 |
- testRequires(c, DaemonIsLinux) |
|
| 49 | 64 |
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo 1") |
| 50 | 65 |
id := strings.TrimSpace(out) |
| 51 | 66 |
|
| ... | ... |
@@ -82,14 +96,17 @@ func (s *DockerSuite) TestApiStatsStoppedContainerInGoroutines(c *check.C) {
|
| 82 | 82 |
|
| 83 | 83 |
func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
| 84 | 84 |
testRequires(c, SameHostDaemon) |
| 85 |
- testRequires(c, DaemonIsLinux) |
|
| 86 | 85 |
|
| 87 | 86 |
out, _ := runSleepingContainer(c) |
| 88 | 87 |
id := strings.TrimSpace(out) |
| 89 | 88 |
c.Assert(waitRun(id), checker.IsNil) |
| 90 | 89 |
|
| 91 | 90 |
// Retrieve the container address |
| 92 |
- contIP := findContainerIP(c, id, "bridge") |
|
| 91 |
+ net := "bridge" |
|
| 92 |
+ if daemonPlatform == "windows" {
|
|
| 93 |
+ net = "nat" |
|
| 94 |
+ } |
|
| 95 |
+ contIP := findContainerIP(c, id, net) |
|
| 93 | 96 |
numPings := 1 |
| 94 | 97 |
|
| 95 | 98 |
var preRxPackets uint64 |
| ... | ... |
@@ -130,9 +147,14 @@ func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
| 130 | 130 |
postTxPackets += v.TxPackets |
| 131 | 131 |
} |
| 132 | 132 |
|
| 133 |
- // Verify the stats contain at least the expected number of packets (account for ARP) |
|
| 134 |
- expRxPkts := 1 + preRxPackets + uint64(numPings) |
|
| 135 |
- expTxPkts := 1 + preTxPackets + uint64(numPings) |
|
| 133 |
+ // Verify the stats contain at least the expected number of packets |
|
| 134 |
+ // On Linux, account for ARP. |
|
| 135 |
+ expRxPkts := preRxPackets + uint64(numPings) |
|
| 136 |
+ expTxPkts := preTxPackets + uint64(numPings) |
|
| 137 |
+ if daemonPlatform != "windows" {
|
|
| 138 |
+ expRxPkts++ |
|
| 139 |
+ expTxPkts++ |
|
| 140 |
+ } |
|
| 136 | 141 |
c.Assert(postTxPackets, checker.GreaterOrEqualThan, expTxPkts, |
| 137 | 142 |
check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
|
| 138 | 143 |
c.Assert(postRxPackets, checker.GreaterOrEqualThan, expRxPkts, |
| ... | ... |
@@ -141,7 +163,6 @@ func (s *DockerSuite) TestApiStatsNetworkStats(c *check.C) {
|
| 141 | 141 |
|
| 142 | 142 |
func (s *DockerSuite) TestApiStatsNetworkStatsVersioning(c *check.C) {
|
| 143 | 143 |
testRequires(c, SameHostDaemon) |
| 144 |
- testRequires(c, DaemonIsLinux) |
|
| 145 | 144 |
|
| 146 | 145 |
out, _ := runSleepingContainer(c) |
| 147 | 146 |
id := strings.TrimSpace(out) |
| ... | ... |
@@ -369,7 +369,19 @@ func (clnt *client) Resume(containerID string) error {
|
| 369 | 369 |
|
| 370 | 370 |
// Stats handles stats requests for containers |
| 371 | 371 |
func (clnt *client) Stats(containerID string) (*Stats, error) {
|
| 372 |
- return nil, errors.New("Windows: Stats not implemented")
|
|
| 372 |
+ // Get the libcontainerd container object |
|
| 373 |
+ clnt.lock(containerID) |
|
| 374 |
+ defer clnt.unlock(containerID) |
|
| 375 |
+ container, err := clnt.getContainer(containerID) |
|
| 376 |
+ if err != nil {
|
|
| 377 |
+ return nil, err |
|
| 378 |
+ } |
|
| 379 |
+ s, err := container.hcsContainer.Statistics() |
|
| 380 |
+ if err != nil {
|
|
| 381 |
+ return nil, err |
|
| 382 |
+ } |
|
| 383 |
+ st := Stats(s) |
|
| 384 |
+ return &st, nil |
|
| 373 | 385 |
} |
| 374 | 386 |
|
| 375 | 387 |
// Restore is the handler for restoring a container |
| ... | ... |
@@ -26,8 +26,8 @@ type StateInfo struct {
|
| 26 | 26 |
UpdatePending bool // Indicates that there are some update operations pending that should be completed by a servicing container. |
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 |
-// Stats contains a stats properties from containerd. |
|
| 30 |
-type Stats struct{}
|
|
| 29 |
+// Stats contains statics from HCS |
|
| 30 |
+type Stats hcsshim.Statistics |
|
| 31 | 31 |
|
| 32 | 32 |
// Resources defines updatable container resource values. |
| 33 | 33 |
type Resources struct{}
|
| ... | ... |
@@ -50,3 +50,9 @@ func runtimeArchitecture() (string, error) {
|
| 50 | 50 |
return "", fmt.Errorf("Unknown processor architecture")
|
| 51 | 51 |
} |
| 52 | 52 |
} |
| 53 |
+ |
|
| 54 |
+// NumProcs returns the number of processors on the system |
|
| 55 |
+func NumProcs() uint32 {
|
|
| 56 |
+ syscall.Syscall(procGetSystemInfo.Addr(), 1, uintptr(unsafe.Pointer(&sysinfo)), 0, 0) |
|
| 57 |
+ return sysinfo.dwNumberOfProcessors |
|
| 58 |
+} |