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