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