Skip to content

Commit 4e15420

Browse files
committed
Windows: Add cpu count option
Signed-off-by: Darren Stahl <darst@microsoft.com>
1 parent d7d0bc1 commit 4e15420

File tree

12 files changed

+187
-20
lines changed

12 files changed

+187
-20
lines changed

daemon/daemon_windows.go

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ import (
2727
)
2828

2929
const (
30-
defaultNetworkSpace = "172.16.0.0/12"
31-
platformSupported = true
32-
windowsMinCPUShares = 1
33-
windowsMaxCPUShares = 10000
30+
defaultNetworkSpace = "172.16.0.0/12"
31+
platformSupported = true
32+
windowsMinCPUShares = 1
33+
windowsMaxCPUShares = 10000
34+
windowsMinCPUPercent = 1
35+
windowsMaxCPUPercent = 100
36+
windowsMinCPUCount = 1
3437
)
3538

3639
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.WeightDevice, error) {
@@ -80,6 +83,15 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
8083
return nil
8184
}
8285

86+
numCPU := int64(sysinfo.NumCPU())
87+
if hostConfig.CPUCount < 0 {
88+
logrus.Warnf("Changing requested CPUCount of %d to minimum allowed of %d", hostConfig.CPUCount, windowsMinCPUCount)
89+
hostConfig.CPUCount = windowsMinCPUCount
90+
} else if hostConfig.CPUCount > numCPU {
91+
logrus.Warnf("Changing requested CPUCount of %d to current number of processors, %d", hostConfig.CPUCount, numCPU)
92+
hostConfig.CPUCount = numCPU
93+
}
94+
8395
if hostConfig.CPUShares < 0 {
8496
logrus.Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, windowsMinCPUShares)
8597
hostConfig.CPUShares = windowsMinCPUShares
@@ -88,19 +100,42 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
88100
hostConfig.CPUShares = windowsMaxCPUShares
89101
}
90102

103+
if hostConfig.CPUPercent < 0 {
104+
logrus.Warnf("Changing requested CPUPercent of %d to minimum allowed of %d", hostConfig.CPUPercent, windowsMinCPUPercent)
105+
hostConfig.CPUPercent = windowsMinCPUPercent
106+
} else if hostConfig.CPUPercent > windowsMaxCPUPercent {
107+
logrus.Warnf("Changing requested CPUPercent of %d to maximum allowed of %d", hostConfig.CPUPercent, windowsMaxCPUPercent)
108+
hostConfig.CPUPercent = windowsMaxCPUPercent
109+
}
110+
91111
return nil
92112
}
93113

94-
func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysinfo.SysInfo) ([]string, error) {
114+
func verifyContainerResources(resources *containertypes.Resources, isHyperv bool) ([]string, error) {
95115
warnings := []string{}
96116

97-
// cpu subsystem checks and adjustments
98-
if resources.CPUPercent < 0 || resources.CPUPercent > 100 {
99-
return warnings, fmt.Errorf("Range of CPU percent is from 1 to 100")
100-
}
101-
102-
if resources.CPUPercent > 0 && resources.CPUShares > 0 {
103-
return warnings, fmt.Errorf("Conflicting options: CPU Shares and CPU Percent cannot both be set")
117+
if !isHyperv {
118+
// The processor resource controls are mutually exclusive on
119+
// Windows Server Containers, the order of precedence is
120+
// CPUCount first, then CPUShares, and CPUPercent last.
121+
if resources.CPUCount > 0 {
122+
if resources.CPUShares > 0 {
123+
warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
124+
logrus.Warn("Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
125+
resources.CPUShares = 0
126+
}
127+
if resources.CPUPercent > 0 {
128+
warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
129+
logrus.Warn("Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
130+
resources.CPUPercent = 0
131+
}
132+
} else if resources.CPUShares > 0 {
133+
if resources.CPUPercent > 0 {
134+
warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
135+
logrus.Warn("Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
136+
resources.CPUPercent = 0
137+
}
138+
}
104139
}
105140

106141
if resources.NanoCPUs > 0 && resources.CPUPercent > 0 {
@@ -154,7 +189,7 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi
154189
func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) {
155190
warnings := []string{}
156191

157-
w, err := verifyContainerResources(&hostConfig.Resources, nil)
192+
w, err := verifyContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig))
158193
warnings = append(warnings, w...)
159194
if err != nil {
160195
return warnings, err
@@ -388,22 +423,22 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
388423
}
389424

390425
// runasHyperVContainer returns true if we are going to run as a Hyper-V container
391-
func (daemon *Daemon) runAsHyperVContainer(container *container.Container) bool {
392-
if container.HostConfig.Isolation.IsDefault() {
426+
func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool {
427+
if hostConfig.Isolation.IsDefault() {
393428
// Container is set to use the default, so take the default from the daemon configuration
394429
return daemon.defaultIsolation.IsHyperV()
395430
}
396431

397432
// Container is requesting an isolation mode. Honour it.
398-
return container.HostConfig.Isolation.IsHyperV()
433+
return hostConfig.Isolation.IsHyperV()
399434

400435
}
401436

402437
// conditionalMountOnStart is a platform specific helper function during the
403438
// container start to call mount.
404439
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
405440
// We do not mount if a Hyper-V container
406-
if !daemon.runAsHyperVContainer(container) {
441+
if !daemon.runAsHyperVContainer(container.HostConfig) {
407442
return daemon.Mount(container)
408443
}
409444
return nil
@@ -413,7 +448,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
413448
// during the cleanup of a container to unmount.
414449
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
415450
// We do not unmount if a Hyper-V container
416-
if !daemon.runAsHyperVContainer(container) {
451+
if !daemon.runAsHyperVContainer(container.HostConfig) {
417452
return daemon.Unmount(container)
418453
}
419454
return nil

daemon/oci_windows.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,13 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
8686
if c.HostConfig.NanoCPUs > 0 {
8787
cpuPercent = uint8(c.HostConfig.NanoCPUs * 100 / int64(sysinfo.NumCPU()) / 1e9)
8888
}
89+
cpuCount := uint64(c.HostConfig.CPUCount)
8990
memoryLimit := uint64(c.HostConfig.Memory)
9091
s.Windows.Resources = &specs.WindowsResources{
9192
CPU: &specs.WindowsCPUResources{
9293
Percent: &cpuPercent,
9394
Shares: &cpuShares,
95+
Count: &cpuCount,
9496
},
9597
Memory: &specs.WindowsMemoryResources{
9698
Limit: &memoryLimit,

docs/reference/api/docker_remote_api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ This section lists each version from latest to oldest. Each listing includes a
166166
* `GET /nodes` and `GET /node/(id or name)` now return `Addr` as part of a node's `Status`, which is the address that that node connects to the manager from.
167167
* The `HostConfig` field now includes `NanoCPUs` that represents CPU quota in units of 10<sup>-9</sup> CPUs.
168168
* `GET /info` now returns more structured information about security options.
169+
* The `HostConfig` field now includes `CpuCount` that represents the number of CPUs available for execution by the container. Windows daemon only.
169170

170171
### v1.24 API changes
171172

docs/reference/api/docker_remote_api_v1.25.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ Create a container
303303
"MemoryReservation": 0,
304304
"KernelMemory": 0,
305305
"NanoCPUs": 500000,
306+
"CpuCount": 4,
306307
"CpuPercent": 80,
307308
"CpuShares": 512,
308309
"CpuPeriod": 100000,
@@ -427,7 +428,14 @@ Create a container
427428
- **MemoryReservation** - Memory soft limit in bytes.
428429
- **KernelMemory** - Kernel memory limit in bytes.
429430
- **NanoCPUs** - CPU quota in units of 10<sup>-9</sup> CPUs.
430-
- **CpuPercent** - An integer value containing the usable percentage of the available CPUs. (Windows daemon only)
431+
- **CpuCount** - An integer value containing the number of usable CPUs.
432+
Windows daemon only. On Windows Server containers,
433+
the processor resource controls are mutually exclusive, the order of precedence
434+
is CPUCount first, then CPUShares, and CPUPercent last.
435+
- **CpuPercent** - An integer value containing the usable percentage of
436+
the available CPUs. Windows daemon only. On Windows Server containers,
437+
the processor resource controls are mutually exclusive, the order of precedence
438+
is CPUCount first, then CPUShares, and CPUPercent last.
431439
- **CpuShares** - An integer value containing the container's CPU Shares
432440
(ie. the relative weight vs other containers).
433441
- **CpuPeriod** - The length of a CPU period in microseconds.
@@ -623,6 +631,7 @@ Return low-level information on the container `id`
623631
"ContainerIDFile": "",
624632
"CpusetCpus": "",
625633
"CpusetMems": "",
634+
"CpuCount": 4,
626635
"CpuPercent": 80,
627636
"CpuShares": 0,
628637
"CpuPeriod": 100000,

docs/reference/commandline/create.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Options:
3131
--cap-drop value Drop Linux capabilities (default [])
3232
--cgroup-parent string Optional parent cgroup for the container
3333
--cidfile string Write the container ID to the file
34+
--cpu-count int The number of CPUs available for execution by the container.
35+
Windows daemon only. On Windows Server containers, this is
36+
approximated as a percentage of total CPU usage.
3437
--cpu-percent int CPU percent (Windows only)
3538
--cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
3639
--cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota

docs/reference/commandline/run.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ Options:
2929
--cap-drop value Drop Linux capabilities (default [])
3030
--cgroup-parent string Optional parent cgroup for the container
3131
--cidfile string Write the container ID to the file
32-
--cpu-percent int CPU percent (Windows only)
32+
--cpu-count int The number of CPUs available for execution by the container.
33+
Windows daemon only. On Windows Server containers, this is
34+
approximated as a percentage of total CPU usage.
35+
--cpu-percent int Limit percentage of CPU available for execution
36+
by the container. Windows daemon only.
37+
The processor resource controls are mutually
38+
exclusive, the order of precedence is CPUCount
39+
first, then CPUShares, and CPUPercent last.
3340
--cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
3441
--cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota
3542
-c, --cpu-shares int CPU shares (relative weight)

integration-cli/docker_cli_run_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4766,3 +4766,67 @@ func (s *DockerSuite) TestRunMount(c *check.C) {
47664766
}
47674767
}
47684768
}
4769+
4770+
func (s *DockerSuite) TestRunWindowsWithCPUCount(c *check.C) {
4771+
testRequires(c, DaemonIsWindows)
4772+
4773+
out, _ := dockerCmd(c, "run", "--cpu-count=1", "--name", "test", "busybox", "echo", "testing")
4774+
c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
4775+
4776+
out = inspectField(c, "test", "HostConfig.CPUCount")
4777+
c.Assert(out, check.Equals, "1")
4778+
}
4779+
4780+
func (s *DockerSuite) TestRunWindowsWithCPUShares(c *check.C) {
4781+
testRequires(c, DaemonIsWindows)
4782+
4783+
out, _ := dockerCmd(c, "run", "--cpu-shares=1000", "--name", "test", "busybox", "echo", "testing")
4784+
c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
4785+
4786+
out = inspectField(c, "test", "HostConfig.CPUShares")
4787+
c.Assert(out, check.Equals, "1000")
4788+
}
4789+
4790+
func (s *DockerSuite) TestRunWindowsWithCPUPercent(c *check.C) {
4791+
testRequires(c, DaemonIsWindows)
4792+
4793+
out, _ := dockerCmd(c, "run", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
4794+
c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
4795+
4796+
out = inspectField(c, "test", "HostConfig.CPUPercent")
4797+
c.Assert(out, check.Equals, "80")
4798+
}
4799+
4800+
func (s *DockerSuite) TestRunProcessIsolationWithCPUCountCPUSharesAndCPUPercent(c *check.C) {
4801+
testRequires(c, DaemonIsWindows, IsolationIsProcess)
4802+
4803+
out, _ := dockerCmd(c, "run", "--cpu-count=1", "--cpu-shares=1000", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
4804+
c.Assert(strings.TrimSpace(out), checker.Contains, "WARNING: Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
4805+
c.Assert(strings.TrimSpace(out), checker.Contains, "WARNING: Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
4806+
c.Assert(strings.TrimSpace(out), checker.Contains, "testing")
4807+
4808+
out = inspectField(c, "test", "HostConfig.CPUCount")
4809+
c.Assert(out, check.Equals, "1")
4810+
4811+
out = inspectField(c, "test", "HostConfig.CPUShares")
4812+
c.Assert(out, check.Equals, "0")
4813+
4814+
out = inspectField(c, "test", "HostConfig.CPUPercent")
4815+
c.Assert(out, check.Equals, "0")
4816+
}
4817+
4818+
func (s *DockerSuite) TestRunHypervIsolationWithCPUCountCPUSharesAndCPUPercent(c *check.C) {
4819+
testRequires(c, DaemonIsWindows, IsolationIsHyperv)
4820+
4821+
out, _ := dockerCmd(c, "run", "--cpu-count=1", "--cpu-shares=1000", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
4822+
c.Assert(strings.TrimSpace(out), checker.Contains, "testing")
4823+
4824+
out = inspectField(c, "test", "HostConfig.CPUCount")
4825+
c.Assert(out, check.Equals, "1")
4826+
4827+
out = inspectField(c, "test", "HostConfig.CPUShares")
4828+
c.Assert(out, check.Equals, "1000")
4829+
4830+
out = inspectField(c, "test", "HostConfig.CPUPercent")
4831+
c.Assert(out, check.Equals, "80")
4832+
}

integration-cli/requirements.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,18 @@ var (
218218
},
219219
"Test requires containers are not pausable.",
220220
}
221+
IsolationIsHyperv = testRequirement{
222+
func() bool {
223+
return daemonPlatform == "windows" && isolation == "hyperv"
224+
},
225+
"Test requires a Windows daemon running default isolation mode of hyperv.",
226+
}
227+
IsolationIsProcess = testRequirement{
228+
func() bool {
229+
return daemonPlatform == "windows" && isolation == "process"
230+
},
231+
"Test requires a Windows daemon running default isolation mode of process.",
232+
}
221233
)
222234

223235
// testRequires checks if the environment satisfies the requirements

libcontainerd/client_windows.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
110110

111111
if spec.Windows.Resources != nil {
112112
if spec.Windows.Resources.CPU != nil {
113+
if spec.Windows.Resources.CPU.Count != nil {
114+
configuration.ProcessorCount = uint32(*spec.Windows.Resources.CPU.Count)
115+
}
113116
if spec.Windows.Resources.CPU.Shares != nil {
114117
configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
115118
}

man/docker-create.1.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ docker-create - Create a new container
1515
[**--cap-drop**[=*[]*]]
1616
[**--cgroup-parent**[=*CGROUP-PATH*]]
1717
[**--cidfile**[=*CIDFILE*]]
18+
[**--cpu-count**[=*0*]]
19+
[**--cpu-percent**[=*0*]]
1820
[**--cpu-period**[=*0*]]
1921
[**--cpu-quota**[=*0*]]
2022
[**--cpu-rt-period**[=*0*]]
@@ -124,6 +126,18 @@ The initial status of the container created with **docker create** is 'created'.
124126
**--cidfile**=""
125127
Write the container ID to the file
126128

129+
**--cpu-count**=*0*
130+
Limit the number of CPUs available for execution by the container.
131+
132+
On Windows Server containers, this is approximated as a percentage of total CPU usage.
133+
134+
On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
135+
136+
**--cpu-percent**=*0*
137+
Limit the percentage of CPU available for execution by a container running on a Windows daemon.
138+
139+
On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
140+
127141
**--cpu-period**=*0*
128142
Limit the CPU CFS (Completely Fair Scheduler) period
129143

0 commit comments

Comments
 (0)