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 }