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 java.util.Optional; 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 import jdk.test.lib.process.OutputAnalyzer; 42 43 public class TestCPUAwareness { 44 private static final String imageName = Common.imageName("cpu"); 45 private static final int availableCPUs = Runtime.getRuntime().availableProcessors(); 46 47 public static void main(String[] args) throws Exception { 48 if (!DockerTestUtils.canTestDocker()) { 49 return; 50 } 51 52 System.out.println("Test Environment: detected availableCPUs = " + availableCPUs); 53 DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker"); 54 55 try { 56 // cpuset, period, shares, expected Active Processor Count 57 testComboWithCpuSets(); 58 59 // cpu shares - it should be safe to use CPU shares exceeding available CPUs 60 testCpuShares(256, 1); 61 testCpuShares(2048, 2); 62 testCpuShares(4096, 4); 63 64 // leave one CPU for system and tools, otherwise this test may be unstable 65 int maxNrOfAvailableCpus = availableCPUs - 1; 66 for (int i=1; i < maxNrOfAvailableCpus; i = i * 2) { 67 testCpus(i, i); 68 } 69 70 // If ActiveProcessorCount is set, the VM should use it, regardless of other 71 // container settings, host settings or available CPUs on the host. 72 testActiveProcessorCount(1, 1); 73 testActiveProcessorCount(2, 2); 74 75 // cpu quota and period 76 testCpuQuotaAndPeriod(50*1000, 100*1000); 77 testCpuQuotaAndPeriod(100*1000, 100*1000); 78 testCpuQuotaAndPeriod(150*1000, 100*1000); 79 testCpuQuotaAndPeriod(400*1000, 100*1000); 80 81 } finally { 82 DockerTestUtils.removeDockerImage(imageName); 83 } 84 } 85 86 87 private static void testComboWithCpuSets() throws Exception { 88 String cpuSetStr = CPUSetsReader.readFromProcStatus("Cpus_allowed_list"); 89 System.out.println("cpuSetStr = " + cpuSetStr); 90 91 if (cpuSetStr == null) { 92 System.out.printf("The cpuset test cases are skipped"); 93 } else { 94 List<Integer> cpuSet = CPUSetsReader.parseCpuSet(cpuSetStr); 95 96 // Test subset of cpuset with one element 97 if (cpuSet.size() >= 1) { 98 String testCpuSet = CPUSetsReader.listToString(cpuSet, 1); 99 testAPCCombo(testCpuSet, 200*1000, 100*1000, 4*1024, true, 1); 100 } 101 102 // Test subset of cpuset with two elements 103 if (cpuSet.size() >= 2) { 104 String testCpuSet = CPUSetsReader.listToString(cpuSet, 2); 105 testAPCCombo(testCpuSet, 200*1000, 100*1000, 4*1024, true, 2); 106 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, true, 2); 107 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, false, 1); 108 } 109 110 // Test subset of cpuset with three elements 111 if (cpuSet.size() >= 3) { 112 String testCpuSet = CPUSetsReader.listToString(cpuSet, 3); 113 testAPCCombo(testCpuSet, 100*1000, 100*1000, 2*1024, true, 1); 114 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, true, 2); 115 testAPCCombo(testCpuSet, 200*1000, 100*1000, 1023, false, 1); 116 } 117 } 118 } 119 120 121 private static void testActiveProcessorCount(int valueToSet, int expectedValue) throws Exception { 122 Common.logNewTestCase("Test ActiveProcessorCount: valueToSet = " + valueToSet); 123 124 DockerRunOptions opts = Common.newOpts(imageName) 125 .addJavaOpts("-XX:ActiveProcessorCount=" + valueToSet, "-Xlog:os=trace"); 126 Common.run(opts) 127 .shouldMatch("active processor count set by user.*" + expectedValue); 128 } 129 130 131 private static void testCpus(int valueToSet, int expectedTraceValue) throws Exception { 132 Common.logNewTestCase("test cpus: " + valueToSet); 133 DockerRunOptions opts = Common.newOpts(imageName) 134 .addDockerOpts("--cpu-period=" + 10000) 135 .addDockerOpts("--cpu-quota=" + valueToSet * 10000); 136 Common.run(opts) 137 .shouldMatch("active_processor_count.*" + expectedTraceValue); 138 } 139 140 141 // Expected active processor count can not exceed available CPU count 142 private static int adjustExpectedAPCForAvailableCPUs(int expectedAPC) { 143 if (expectedAPC > availableCPUs) { 144 expectedAPC = availableCPUs; 145 System.out.println("Adjusted expectedAPC = " + expectedAPC); 146 } 147 return expectedAPC; 148 } 149 150 151 private static void testCpuQuotaAndPeriod(int quota, int period) 152 throws Exception { 153 Common.logNewTestCase("test cpu quota and period: "); 154 System.out.println("quota = " + quota); 155 System.out.println("period = " + period); 156 157 int expectedAPC = (int) Math.ceil((float) quota / (float) period); 158 System.out.println("expectedAPC = " + expectedAPC); 159 expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); 160 161 DockerRunOptions opts = Common.newOpts(imageName) 162 .addDockerOpts("--cpu-period=" + period) 163 .addDockerOpts("--cpu-quota=" + quota); 164 165 Common.run(opts) 166 .shouldMatch("CPU Period is.*" + period) 167 .shouldMatch("CPU Quota is.*" + quota) 168 .shouldMatch("active_processor_count.*" + expectedAPC); 169 } 170 171 172 // Test correctess of automatically selected active processor cound 173 private static void testAPCCombo(String cpuset, int quota, int period, int shares, 174 boolean usePreferContainerQuotaForCPUCount, 175 int expectedAPC) throws Exception { 176 Common.logNewTestCase("test APC Combo"); 177 System.out.println("cpuset = " + cpuset); 178 System.out.println("quota = " + quota); 179 System.out.println("period = " + period); 180 System.out.println("shares = " + shares); 181 System.out.println("usePreferContainerQuotaForCPUCount = " + usePreferContainerQuotaForCPUCount); 182 System.out.println("expectedAPC = " + expectedAPC); 183 184 expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); 185 186 DockerRunOptions opts = Common.newOpts(imageName) 187 .addDockerOpts("--cpuset-cpus", "" + cpuset) 188 .addDockerOpts("--cpu-period=" + period) 189 .addDockerOpts("--cpu-quota=" + quota) 190 .addDockerOpts("--cpu-shares=" + shares); 191 192 if (!usePreferContainerQuotaForCPUCount) opts.addJavaOpts("-XX:-PreferContainerQuotaForCPUCount"); 193 194 Common.run(opts) 195 .shouldMatch("active_processor_count.*" + expectedAPC); 196 } 197 198 199 private static void testCpuShares(int shares, int expectedAPC) throws Exception { 200 Common.logNewTestCase("test cpu shares, shares = " + shares); 201 System.out.println("expectedAPC = " + expectedAPC); 202 203 expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); 204 205 DockerRunOptions opts = Common.newOpts(imageName) 206 .addDockerOpts("--cpu-shares=" + shares); 207 OutputAnalyzer out = Common.run(opts); 208 String cgroupVer = getDetectedCgroupVersion(out); 209 if (cgroupVer != null ) { 210 if ("cgroupv1".equals(cgroupVer)) { 211 out.shouldMatch("CPU Shares is.*" + shares); 212 } else if ("cgroupsv2".equals(cgroupVer)) { 213 out.shouldMatch("Scaled CPU Shares value is:.*"); 214 } 215 } 216 out.shouldMatch("active_processor_count.*" + expectedAPC); 217 } 218 219 private static String getDetectedCgroupVersion(OutputAnalyzer out) throws Exception { 220 Optional<String> cgroupVersString = out.asLines() 221 .stream() 222 .filter( l -> l.startsWith("Detected CGroups version is:") ) 223 .findFirst(); 224 if (cgroupVersString.isPresent()) { // only non-product builds have this 225 return cgroupVersString.get().split(": ")[1].trim(); 226 } else { 227 return null; 228 } 229 } 230 231 }