Signed-off-by: John Howard <jhoward@microsoft.com>
John Howard authored on 2016/09/08 08:08:51... | ... |
@@ -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 |
+} |