Browse code

Windows: stats support

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2016/09/08 08:08:51
Showing 22 changed files
... ...
@@ -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 {
... ...
@@ -222,7 +222,7 @@ func TestGetDockerOS(t *testing.T) {
222 222
 		"Foo/v1.22 (bar)":        "",
223 223
 	}
224 224
 	for header, os := range cases {
225
-		g := getDockerOS(header)
225
+		g := GetDockerOS(header)
226 226
 		if g != os {
227 227
 			t.Fatalf("Expected %s, got %s", os, g)
228 228
 		}
... ...
@@ -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
+}