1 /* 2 * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 /* 26 * @test 27 * @summary Test JVM's CPU resource awareness when running inside docker container 28 * @requires docker.support 29 * @library /test/lib 30 * @modules java.base/jdk.internal.misc 31 * java.management 32 * jdk.jartool/sun.tools.jar 33 * @run driver TestCPUAwareness 34 */ 35 import java.util.List; 36 import jdk.test.lib.process.OutputAnalyzer; 37 import jdk.test.lib.containers.docker.Common; 38 import jdk.test.lib.containers.docker.DockerRunOptions; 39 import jdk.test.lib.containers.docker.DockerTestUtils; 40 import jdk.test.lib.containers.cgroup.CPUSetsReader; 41 42 public class TestCPUAwareness { 43 private static final String imageName = Common.imageName("cpu"); 44 private static final int availableCPUs = Runtime.getRuntime().availableProcessors(); 45 46 public static void main(String[] args) throws Exception { 47 if (!DockerTestUtils.canTestDocker()) { 48 return; 49 } 50 51 System.out.println("Test Environment: detected availableCPUs = " + availableCPUs); 52 DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker"); 53 54 try { 55 // cpuset, period, shares, expected Active Processor Count 56 testComboWithCpuSets(); 57 58 // cpu shares - it should be safe to use CPU shares exceeding available CPUs 59 testCpuShares(256, 1); 60 testCpuShares(2048, 2); 61 testCpuShares(4096, 4); 62 63 // leave one CPU for system and tools, otherwise this test may be unstable 64 int maxNrOfAvailableCpus = availableCPUs - 1; 65 for (int i=1; i < maxNrOfAvailableCpus; i = i * 2) { 66 testCpus(i, i); 67 } 68 69 // If ActiveProcessorCount is set, the VM should use it, regardless of other 70 // container settings, host settings or available CPUs on the host. 71 testActiveProcessorCount(1, 1); 72 testActiveProcessorCount(2, 2); 73 74 // cpu quota and period 75 testCpuQuotaAndPeriod(50*1000, 100*1000); 76 testCpuQuotaAndPeriod(100*1000, 100*1000); 77 testCpuQuotaAndPeriod(150*1000, 100*1000); 78 testCpuQuotaAndPeriod(400*1000, 100*1000); 79 80 } finally { 81 DockerTestUtils.removeDockerImage(imageName); 82 } 83 } 84 85 86 private static void testComboWithCpuSets() throws Exception { 87 String cpuSetStr = CPUSetsReader.readFromProcStatus("Cpus_allowed_list"); 88 System.out.println("cpuSetStr = " + cpuSetStr); 89 90 if (cpuSetStr == null) { 91 System.out.printf("The cpuset test cases are skipped"); 92 } else { 93 List<Integer> cpuSet = CPUSetsReader.parseCpuSet(cpuSetStr); 94 95 // Test subset of cpuset with one element 96 if (cpuSet.size() >= 1) { 97 String testCpuSet = CPUSetsReader.listToString(cpuSet, 1); 98 testAPCCombo(testCpuSet, 200*1000, 100*1000, 4*1024, true, 1); 99 } 100 101 // Test subset of cpuset with two elements 102 if (cpuSet.size() >= 2) { 103 String testCpuSet = CPUSetsReader.listToString(cpuSet, 2); 104 testAPCCombo(testCpuSet, 200*1000, 100*1000, 4*1024, true, 2); 105 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, true, 2); 106 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, false, 1); 107 } 108 109 // Test subset of cpuset with three elements 110 if (cpuSet.size() >= 3) { 111 String testCpuSet = CPUSetsReader.listToString(cpuSet, 3); 112 testAPCCombo(testCpuSet, 100*1000, 100*1000, 2*1024, true, 1); 113 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, true, 2); 114 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, false, 1); 115 } 116 } 117 } 118 119 120 private static void testActiveProcessorCount(int valueToSet, int expectedValue) throws Exception { 121 Common.logNewTestCase("Test ActiveProcessorCount: valueToSet = " + valueToSet); 122 123 DockerRunOptions opts = Common.newOpts(imageName) 124 .addJavaOpts("-XX:ActiveProcessorCount=" + valueToSet, "-Xlog:os=trace"); 125 Common.run(opts) 126 .shouldMatch("active processor count set by user.*" + expectedValue); 127 } 128 129 130 private static void testCpus(int valueToSet, int expectedTraceValue) throws Exception { 131 Common.logNewTestCase("test cpus: " + valueToSet); 132 DockerRunOptions opts = Common.newOpts(imageName) 133 .addDockerOpts("--cpu-period=" + 10000) 134 .addDockerOpts("--cpu-quota=" + valueToSet * 10000); 135 Common.run(opts) 136 .shouldMatch("active_processor_count.*" + expectedTraceValue); 137 } 138 139 140 // Expected active processor count can not exceed available CPU count 141 private static int adjustExpectedAPCForAvailableCPUs(int expectedAPC) { 142 if (expectedAPC > availableCPUs) { 143 expectedAPC = availableCPUs; 144 System.out.println("Adjusted expectedAPC = " + expectedAPC); 145 } 146 return expectedAPC; 147 } 148 149 150 private static void testCpuQuotaAndPeriod(int quota, int period) 151 throws Exception { 152 Common.logNewTestCase("test cpu quota and period: "); 153 System.out.println("quota = " + quota); 154 System.out.println("period = " + period); 155 156 int expectedAPC = (int) Math.ceil((float) quota / (float) period); 157 System.out.println("expectedAPC = " + expectedAPC); 158 expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); 159 160 DockerRunOptions opts = Common.newOpts(imageName) 161 .addDockerOpts("--cpu-period=" + period) 162 .addDockerOpts("--cpu-quota=" + quota); 163 164 Common.run(opts) 165 .shouldMatch("CPU Period is.*" + period) 166 .shouldMatch("CPU Quota is.*" + quota) 167 .shouldMatch("active_processor_count.*" + expectedAPC); 168 } 169 170 171 // Test correctess of automatically selected active processor cound 172 private static void testAPCCombo(String cpuset, int quota, int period, int shares, 173 boolean usePreferContainerQuotaForCPUCount, 174 int expectedAPC) throws Exception { 175 Common.logNewTestCase("test APC Combo"); 176 System.out.println("cpuset = " + cpuset); 177 System.out.println("quota = " + quota); 178 System.out.println("period = " + period); 179 System.out.println("shares = " + shares); 180 System.out.println("usePreferContainerQuotaForCPUCount = " + usePreferContainerQuotaForCPUCount); 181 System.out.println("expectedAPC = " + expectedAPC); 182 183 expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); 184 185 DockerRunOptions opts = Common.newOpts(imageName) 186 .addDockerOpts("--cpuset-cpus", "" + cpuset) 187 .addDockerOpts("--cpu-period=" + period) 188 .addDockerOpts("--cpu-quota=" + quota) 189 .addDockerOpts("--cpu-shares=" + shares); 190 191 if (!usePreferContainerQuotaForCPUCount) opts.addJavaOpts("-XX:-PreferContainerQuotaForCPUCount"); 192 193 Common.run(opts) 194 .shouldMatch("active_processor_count.*" + expectedAPC); 195 } 196 197 198 private static void testCpuShares(int shares, int expectedAPC) throws Exception { 199 Common.logNewTestCase("test cpu shares, shares = " + shares); 200 System.out.println("expectedAPC = " + expectedAPC); 201 202 expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); 203 204 DockerRunOptions opts = Common.newOpts(imageName) 205 .addDockerOpts("--cpu-shares=" + shares); 206 OutputAnalyzer out = Common.run(opts); 207 // Cgroups v2 needs to do some scaling of raw shares values. Hence, 208 // 256 CPU shares come back as 264. Raw value written to cpu.weight 209 // is 10. The reason this works for >= 1024 shares value is because 210 // post-scaling the closest multiple of 1024 is found and returned. 211 // 212 // For values < 1024, this doesn't happen so loosen the match to a 213 // 3-digit number and ensure the active_processor_count is as 214 // expected. 215 if (shares < 1024) { 216 out.shouldMatch("CPU Shares is.*\\d{3}"); 217 } else { 218 out.shouldMatch("CPU Shares is.*" + shares); 219 } 220 out.shouldMatch("active_processor_count.*" + expectedAPC); 221 } 222 }