1 /* 2 * Copyright (c) 2019, Red Hat Inc. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.test.lib.containers.cgroup; 27 28 import java.io.IOException; 29 import java.nio.file.Files; 30 import java.nio.file.Path; 31 import java.nio.file.Paths; 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.concurrent.TimeUnit; 35 import java.util.stream.Collectors; 36 37 import jdk.internal.platform.Metrics; 38 import jdk.test.lib.Asserts; 39 40 public class MetricsTesterCgroupV2 implements CgroupMetricsTester { 41 42 private static final long UNLIMITED = -1; 43 private static final long NOT_SUPPORTED = -2; 44 private static final UnifiedController UNIFIED = new UnifiedController(); 45 private static final String MAX = "max"; 46 private static final int PER_CPU_SHARES = 1024; 47 48 private final long startSysVal; 49 private final long startUserVal; 50 private final long startUsage; 51 52 static class UnifiedController { 53 54 private static final String NAME = "unified"; 55 private final String path; 56 57 UnifiedController() { 58 path = constructPath(); 59 } 60 61 String getPath() { 62 return path; 63 } 64 65 private static String constructPath() { 66 String mountPath; 67 String cgroupPath; 68 try { 69 List<String> fifthTokens = Files.lines(Paths.get("/proc/self/mountinfo")) 70 .filter( l -> l.contains("- cgroup2")) 71 .map(UnifiedController::splitAndMountPath) 72 .collect(Collectors.toList()); 73 if (fifthTokens.size() != 1) { 74 throw new AssertionError("Expected only one cgroup2 line"); 75 } 76 mountPath = fifthTokens.get(0); 77 78 List<String> cgroupPaths = Files.lines(Paths.get("/proc/self/cgroup")) 79 .filter( l -> l.startsWith("0:")) 80 .map(UnifiedController::splitAndCgroupPath) 81 .collect(Collectors.toList()); 82 if (cgroupPaths.size() != 1) { 83 throw new AssertionError("Expected only one unified controller line"); 84 } 85 cgroupPath = cgroupPaths.get(0); 86 return Paths.get(mountPath, cgroupPath).toString(); 87 } catch (IOException e) { 88 return null; 89 } 90 } 91 92 public static String splitAndMountPath(String input) { 93 String[] tokens = input.split("\\s+"); 94 return tokens[4]; // fifth entry is the mount path 95 } 96 97 public static String splitAndCgroupPath(String input) { 98 String[] tokens = input.split(":"); 99 return tokens[2]; 100 } 101 } 102 103 private long getLongLimitValueFromFile(String file) { 104 String strVal = getStringVal(file); 105 if (MAX.equals(strVal)) { 106 return UNLIMITED; 107 } 108 return convertStringToLong(strVal); 109 } 110 111 public MetricsTesterCgroupV2() { 112 Metrics metrics = Metrics.systemMetrics(); 113 // Initialize CPU usage metrics before we do any testing. 114 startSysVal = metrics.getCpuSystemUsage(); 115 startUserVal = metrics.getCpuUserUsage(); 116 startUsage = metrics.getCpuUsage(); 117 } 118 119 private long getLongValueFromFile(String file) { 120 return convertStringToLong(getStringVal(file)); 121 } 122 123 private long getLongValueEntryFromFile(String file, String metric) { 124 Path filePath = Paths.get(UNIFIED.getPath(), file); 125 try { 126 String strVal = Files.lines(filePath).filter(l -> l.startsWith(metric)).collect(Collectors.joining()); 127 String[] keyValues = strVal.split("\\s+"); 128 String value = keyValues[1]; 129 return convertStringToLong(value); 130 } catch (IOException e) { 131 return 0; 132 } 133 } 134 135 private String getStringVal(String file) { 136 Path filePath = Paths.get(UNIFIED.getPath(), file); 137 try { 138 return Files.lines(filePath).collect(Collectors.joining()); 139 } catch (IOException e) { 140 return null; 141 } 142 } 143 144 private void fail(String metric, long oldVal, long newVal) { 145 CgroupMetricsTester.fail(UnifiedController.NAME, metric, oldVal, newVal); 146 } 147 148 private void fail(String metric, String oldVal, String newVal) { 149 CgroupMetricsTester.fail(UnifiedController.NAME, metric, oldVal, newVal); 150 } 151 152 private void fail(String metric, String message) { 153 throw new RuntimeException(metric + ": " + message); 154 } 155 156 private void warn(String metric, long oldVal, long newVal) { 157 CgroupMetricsTester.warn(UnifiedController.NAME, metric, oldVal, newVal); 158 } 159 160 private void verifyNotSupported(long metricVal) { 161 Asserts.assertEquals(metricVal, NOT_SUPPORTED, "Expected metric to be not supported!"); 162 } 163 164 private void verifyNotSupported(double metricVal) { 165 if (!CgroupMetricsTester.compareWithErrorMargin(NOT_SUPPORTED, metricVal)) { 166 throw new RuntimeException("Metric not supported, got: " + metricVal + 167 " expected: " + NOT_SUPPORTED); 168 } 169 } 170 171 private void verifyPerCpuNotSupported(long[] perCpuUsage) { 172 if (!Arrays.equals(perCpuUsage, new long[0])) { 173 throw new RuntimeException("perCpuUsage expected to be not supported"); 174 }; 175 } 176 177 private long getCpuShares(String file) { 178 long rawVal = getLongValueFromFile(file); 179 if (rawVal == 0 || rawVal == 100) { 180 return UNLIMITED; 181 } 182 int shares = (int)rawVal; 183 // CPU shares (OCI) value needs to get translated into 184 // a proper Cgroups v2 value. See: 185 // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller 186 // 187 // Use the inverse of (x == OCI value, y == cgroupsv2 value): 188 // ((262142 * y - 1)/9999) + 2 = x 189 // 190 int x = 262142 * shares - 1; 191 double frac = x/9999.0; 192 x = ((int)frac) + 2; 193 if ( x <= PER_CPU_SHARES ) { 194 return PER_CPU_SHARES; // mimic cgroups v1 195 } 196 int f = x/PER_CPU_SHARES; 197 int lower_multiple = f * PER_CPU_SHARES; 198 int upper_multiple = (f + 1) * PER_CPU_SHARES; 199 int distance_lower = Math.max(lower_multiple, x) - Math.min(lower_multiple, x); 200 int distance_upper = Math.max(upper_multiple, x) - Math.min(upper_multiple, x); 201 x = distance_lower <= distance_upper ? lower_multiple : upper_multiple; 202 return x; 203 } 204 205 private long getCpuMaxValueFromFile(String file) { 206 return getCpuValueFromFile(file, 0 /* $MAX index */); 207 } 208 209 private long getCpuPeriodValueFromFile(String file) { 210 return getCpuValueFromFile(file, 1 /* $PERIOD index */); 211 } 212 213 private long getCpuValueFromFile(String file, int index) { 214 String maxPeriod = getStringVal(file); 215 String[] tokens = maxPeriod.split("\\s+"); 216 String val = tokens[index]; 217 if (MAX.equals(val)) { 218 return UNLIMITED; 219 } 220 return convertStringToLong(val); 221 } 222 223 private long convertStringToLong(String val) { 224 return CgroupMetricsTester.convertStringToLong(val, UNLIMITED); 225 } 226 227 @Override 228 public void testMemorySubsystem() { 229 Metrics metrics = Metrics.systemMetrics(); 230 231 long memoryMaxUsageMin; 232 // User Memory 233 long oldVal = metrics.getMemoryFailCount(); 234 long newVal = getLongValueEntryFromFile("memory.events", "max"); 235 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 236 fail("memory.events[max]", oldVal, newVal); 237 } 238 239 oldVal = metrics.getMemoryLimit(); 240 newVal = getLongLimitValueFromFile("memory.max"); 241 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 242 fail("memory.max", oldVal, newVal); 243 } 244 245 memoryMaxUsageMin = oldVal = metrics.getMemoryUsage(); 246 newVal = getLongValueFromFile("memory.current"); 247 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 248 fail("memory.current", oldVal, newVal); 249 } 250 251 // Memory max has no knowledge of out of band read to "memory.current" 252 // above. Fix that by calling it again. 253 metrics.getMemoryUsage(); 254 long memoryMax = metrics.getMemoryMaxUsage(); 255 if (memoryMax < memoryMaxUsageMin || memoryMax < newVal) { 256 fail("memoryMaxUsage", "old usages [ " + memoryMaxUsageMin + ", " + 257 newVal + " ] >= " + memoryMax); 258 } 259 260 // Kernel memory 261 verifyNotSupported(metrics.getKernelMemoryFailCount()); 262 verifyNotSupported(metrics.getKernelMemoryLimit()); 263 verifyNotSupported(metrics.getKernelMemoryMaxUsage()); 264 verifyNotSupported(metrics.getKernelMemoryUsage()); 265 266 //TCP Memory 267 verifyNotSupported(metrics.getTcpMemoryFailCount()); 268 verifyNotSupported(metrics.getTcpMemoryLimit()); 269 verifyNotSupported(metrics.getTcpMemoryMaxUsage()); 270 verifyNotSupported(metrics.getTcpMemoryUsage()); 271 272 // Memory and Swap 273 verifyNotSupported(metrics.getMemoryAndSwapFailCount()); 274 verifyNotSupported(metrics.getMemoryAndSwapMaxUsage()); 275 276 oldVal = metrics.getMemoryAndSwapLimit(); 277 newVal = getLongLimitValueFromFile("memory.swap.max"); 278 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 279 fail("memory.swap.max", oldVal, newVal); 280 } 281 282 oldVal = metrics.getMemoryAndSwapUsage(); 283 newVal = getLongValueFromFile("memory.swap.current"); 284 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 285 fail("memory.swap.current", oldVal, newVal); 286 } 287 288 oldVal = metrics.getMemorySoftLimit(); 289 newVal = getLongLimitValueFromFile("memory.high"); 290 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 291 fail("memory.high", oldVal, newVal); 292 } 293 294 Asserts.assertFalse(metrics.isMemoryOOMKillEnabled(), "Not supported"); 295 } 296 297 @Override 298 public void testCpuAccounting() { 299 Metrics metrics = Metrics.systemMetrics(); 300 long oldVal = metrics.getCpuUsage(); 301 long newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "usage_usec")); 302 303 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 304 warn("cpu.stat[usage_usec]", oldVal, newVal); 305 } 306 307 verifyPerCpuNotSupported(metrics.getPerCpuUsage()); 308 309 oldVal = metrics.getCpuUserUsage(); 310 newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "user_usec")); 311 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 312 warn("cpu.stat[user_usec]", oldVal, newVal); 313 } 314 315 oldVal = metrics.getCpuSystemUsage(); 316 newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "system_usec")); 317 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 318 warn("cpu.stat[system_usec]", oldVal, newVal); 319 } 320 } 321 322 @Override 323 public void testCpuSchedulingMetrics() { 324 Metrics metrics = Metrics.systemMetrics(); 325 long oldVal = metrics.getCpuPeriod(); 326 long newVal = getCpuPeriodValueFromFile("cpu.max"); 327 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 328 fail("cpu.max[$PERIOD]", oldVal, newVal); 329 } 330 331 oldVal = metrics.getCpuQuota(); 332 newVal = getCpuMaxValueFromFile("cpu.max"); 333 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 334 fail("cpu.max[$MAX]", oldVal, newVal); 335 } 336 337 oldVal = metrics.getCpuShares(); 338 newVal = getCpuShares("cpu.weight"); 339 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 340 fail("cpu.weight", oldVal, newVal); 341 } 342 343 oldVal = metrics.getCpuNumPeriods(); 344 newVal = getLongValueEntryFromFile("cpu.stat", "nr_periods"); 345 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 346 fail("cpu.stat[nr_periods]", oldVal, newVal); 347 } 348 349 oldVal = metrics.getCpuNumThrottled(); 350 newVal = getLongValueEntryFromFile("cpu.stat", "nr_throttled"); 351 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 352 fail("cpu.stat[nr_throttled]", oldVal, newVal); 353 } 354 355 oldVal = metrics.getCpuThrottledTime(); 356 newVal = TimeUnit.MICROSECONDS.toNanos(getLongValueEntryFromFile("cpu.stat", "throttled_usec")); 357 if (!CgroupMetricsTester.compareWithErrorMargin(oldVal, newVal)) { 358 fail("cpu.stat[throttled_usec]", oldVal, newVal); 359 } 360 } 361 362 @Override 363 public void testCpuSets() { 364 Metrics metrics = Metrics.systemMetrics(); 365 Integer[] oldVal = Arrays.stream(metrics.getCpuSetCpus()).boxed().toArray(Integer[]::new); 366 Arrays.sort(oldVal); 367 368 String cpusstr = getStringVal("cpuset.cpus"); 369 // Parse range string in the format 1,2-6,7 370 Integer[] newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); 371 Arrays.sort(newVal); 372 if (Arrays.compare(oldVal, newVal) != 0) { 373 fail("cpuset.cpus", Arrays.toString(oldVal), 374 Arrays.toString(newVal)); 375 } 376 377 oldVal = Arrays.stream(metrics.getEffectiveCpuSetCpus()).boxed().toArray(Integer[]::new); 378 Arrays.sort(oldVal); 379 cpusstr = getStringVal("cpuset.cpus.effective"); 380 newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); 381 Arrays.sort(newVal); 382 if (Arrays.compare(oldVal, newVal) != 0) { 383 fail("cpuset.cpus.effective", Arrays.toString(oldVal), 384 Arrays.toString(newVal)); 385 } 386 387 oldVal = Arrays.stream(metrics.getCpuSetMems()).boxed().toArray(Integer[]::new); 388 Arrays.sort(oldVal); 389 cpusstr = getStringVal("cpuset.mems"); 390 newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); 391 Arrays.sort(newVal); 392 if (Arrays.compare(oldVal, newVal) != 0) { 393 fail("cpuset.mems", Arrays.toString(oldVal), 394 Arrays.toString(newVal)); 395 } 396 397 oldVal = Arrays.stream(metrics.getEffectiveCpuSetMems()).boxed().toArray(Integer[]::new); 398 Arrays.sort(oldVal); 399 cpusstr = getStringVal("cpuset.mems.effective"); 400 newVal = CgroupMetricsTester.convertCpuSetsToArray(cpusstr); 401 Arrays.sort(newVal); 402 if (Arrays.compare(oldVal, newVal) != 0) { 403 fail("cpuset.mems.effective", Arrays.toString(oldVal), 404 Arrays.toString(newVal)); 405 } 406 407 verifyNotSupported(metrics.getCpuSetMemoryPressure()); 408 Asserts.assertFalse(metrics.isCpuSetMemoryPressureEnabled(), "Should be not supported"); 409 } 410 411 @Override 412 public void testCpuConsumption() { 413 Metrics metrics = Metrics.systemMetrics(); 414 // make system call 415 long newSysVal = metrics.getCpuSystemUsage(); 416 long newUserVal = metrics.getCpuUserUsage(); 417 long newUsage = metrics.getCpuUsage(); 418 419 verifyPerCpuNotSupported(metrics.getPerCpuUsage()); 420 421 // system/user CPU usage counters may be slowly increasing. 422 // allow for equal values for a pass 423 if (newSysVal < startSysVal) { 424 fail("getCpuSystemUsage", newSysVal, startSysVal); 425 } 426 427 // system/user CPU usage counters may be slowly increasing. 428 // allow for equal values for a pass 429 if (newUserVal < startUserVal) { 430 fail("getCpuUserUsage", newUserVal, startUserVal); 431 } 432 433 if (newUsage <= startUsage) { 434 fail("getCpuUsage", newUsage, startUsage); 435 } 436 } 437 438 @Override 439 public void testMemoryUsage() { 440 Metrics metrics = Metrics.systemMetrics(); 441 long memoryMaxUsage = metrics.getMemoryMaxUsage(); 442 long memoryUsage = metrics.getMemoryUsage(); 443 long newMemoryMaxUsage = 0, newMemoryUsage = 0; 444 445 // allocate memory in a loop and check more than once for new values 446 // otherwise we might occasionally see the effect of decreasing new memory 447 // values. For example because the system could free up memory 448 byte[][] bytes = new byte[32][]; 449 for (int i = 0; i < 32; i++) { 450 bytes[i] = new byte[8*1024*1024]; 451 newMemoryUsage = metrics.getMemoryUsage(); 452 if (newMemoryUsage > memoryUsage) { 453 break; 454 } 455 } 456 newMemoryMaxUsage = metrics.getMemoryMaxUsage(); 457 458 if (newMemoryMaxUsage < memoryMaxUsage) { 459 fail("getMemoryMaxUsage", memoryMaxUsage, 460 newMemoryMaxUsage); 461 } 462 463 if (newMemoryUsage < memoryUsage) { 464 fail("getMemoryUsage", memoryUsage, newMemoryUsage); 465 } 466 } 467 468 @Override 469 public void testMisc() { 470 // nothing for cgroups v2 471 } 472 473 }