1 /* 2 * Copyright (c) 2016, 2021, 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 package requires; 25 26 import java.io.BufferedInputStream; 27 import java.io.FileInputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.nio.file.StandardOpenOption; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Properties; 40 import java.util.Set; 41 import java.util.concurrent.Callable; 42 import java.util.concurrent.TimeUnit; 43 import java.util.function.Supplier; 44 import java.util.regex.Matcher; 45 import java.util.regex.Pattern; 46 47 import jdk.test.whitebox.code.Compiler; 48 import jdk.test.whitebox.cpuinfo.CPUInfo; 49 import jdk.test.whitebox.gc.GC; 50 import jdk.test.whitebox.WhiteBox; 51 import jdk.test.lib.Platform; 52 import jdk.test.lib.Container; 53 54 /** 55 * The Class to be invoked by jtreg prior Test Suite execution to 56 * collect information about VM. 57 * Do not use any APIs that may not be available in all target VMs. 58 * Properties set by this Class will be available in the @requires expressions. 59 */ 60 public class VMProps implements Callable<Map<String, String>> { 61 // value known to jtreg as an indicator of error state 62 private static final String ERROR_STATE = "__ERROR__"; 63 64 private static final WhiteBox WB = WhiteBox.getWhiteBox(); 65 66 private static class SafeMap { 67 private final Map<String, String> map = new HashMap<>(); 68 69 public void put(String key, Supplier<String> s) { 70 String value; 71 try { 72 value = s.get(); 73 } catch (Throwable t) { 74 System.err.println("failed to get value for " + key); 75 t.printStackTrace(System.err); 76 value = ERROR_STATE + t; 77 } 78 map.put(key, value); 79 } 80 } 81 82 /** 83 * Collects information about VM properties. 84 * This method will be invoked by jtreg. 85 * 86 * @return Map of property-value pairs. 87 */ 88 @Override 89 public Map<String, String> call() { 90 SafeMap map = new SafeMap(); 91 map.put("vm.flavor", this::vmFlavor); 92 map.put("vm.compMode", this::vmCompMode); 93 map.put("vm.bits", this::vmBits); 94 map.put("vm.flightRecorder", this::vmFlightRecorder); 95 map.put("vm.simpleArch", this::vmArch); 96 map.put("vm.debug", this::vmDebug); 97 map.put("vm.jvmci", this::vmJvmci); 98 map.put("vm.emulatedClient", this::vmEmulatedClient); 99 // vm.hasSA is "true" if the VM contains the serviceability agent 100 // and jhsdb. 101 map.put("vm.hasSA", this::vmHasSA); 102 // vm.hasJFR is "true" if JFR is included in the build of the VM and 103 // so tests can be executed. 104 map.put("vm.hasJFR", this::vmHasJFR); 105 map.put("vm.hasDTrace", this::vmHasDTrace); 106 map.put("vm.jvmti", this::vmHasJVMTI); 107 map.put("vm.cpu.features", this::cpuFeatures); 108 map.put("vm.pageSize", this::vmPageSize); 109 map.put("vm.rtm.cpu", this::vmRTMCPU); 110 map.put("vm.rtm.compiler", this::vmRTMCompiler); 111 // vm.cds is true if the VM is compiled with cds support. 112 map.put("vm.cds", this::vmCDS); 113 map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders); 114 map.put("vm.cds.archived.java.heap", this::vmCDSForArchivedJavaHeap); 115 // vm.graal.enabled is true if Graal is used as JIT 116 map.put("vm.graal.enabled", this::isGraalEnabled); 117 map.put("vm.compiler1.enabled", this::isCompiler1Enabled); 118 map.put("vm.compiler2.enabled", this::isCompiler2Enabled); 119 map.put("docker.support", this::dockerSupport); 120 map.put("vm.musl", this::isMusl); 121 map.put("release.implementor", this::implementor); 122 map.put("jdk.containerized", this::jdkContainerized); 123 map.put("vm.flagless", this::isFlagless); 124 vmGC(map); // vm.gc.X = true/false 125 vmOptFinalFlags(map); 126 127 dump(map.map); 128 return map.map; 129 } 130 131 /** 132 * Print a stack trace before returning error state; 133 * Used by the various helper functions which parse information from 134 * VM properties in the case where they don't find an expected property 135 * or a property doesn't conform to an expected format. 136 * 137 * @return {@link #ERROR_STATE} 138 */ 139 private String errorWithMessage(String message) { 140 new Exception(message).printStackTrace(); 141 return ERROR_STATE + message; 142 } 143 144 /** 145 * @return vm.simpleArch value of "os.simpleArch" property of tested JDK. 146 */ 147 protected String vmArch() { 148 String arch = System.getProperty("os.arch"); 149 if (arch.equals("x86_64") || arch.equals("amd64")) { 150 return "x64"; 151 } else if (arch.contains("86")) { 152 return "x86"; 153 } else { 154 return arch; 155 } 156 } 157 158 /** 159 * @return VM type value extracted from the "java.vm.name" property. 160 */ 161 protected String vmFlavor() { 162 // E.g. "Java HotSpot(TM) 64-Bit Server VM" 163 String vmName = System.getProperty("java.vm.name"); 164 if (vmName == null) { 165 return errorWithMessage("Can't get 'java.vm.name' property"); 166 } 167 168 Pattern startP = Pattern.compile(".* (\\S+) VM"); 169 Matcher m = startP.matcher(vmName); 170 if (m.matches()) { 171 return m.group(1).toLowerCase(); 172 } 173 return errorWithMessage("Can't get VM flavor from 'java.vm.name'"); 174 } 175 176 /** 177 * @return VM compilation mode extracted from the "java.vm.info" property. 178 */ 179 protected String vmCompMode() { 180 // E.g. "mixed mode" 181 String vmInfo = System.getProperty("java.vm.info"); 182 if (vmInfo == null) { 183 return errorWithMessage("Can't get 'java.vm.info' property"); 184 } 185 vmInfo = vmInfo.toLowerCase(); 186 if (vmInfo.contains("mixed mode")) { 187 return "Xmixed"; 188 } else if (vmInfo.contains("compiled mode")) { 189 return "Xcomp"; 190 } else if (vmInfo.contains("interpreted mode")) { 191 return "Xint"; 192 } else { 193 return errorWithMessage("Can't get compilation mode from 'java.vm.info'"); 194 } 195 } 196 197 /** 198 * @return VM bitness, the value of the "sun.arch.data.model" property. 199 */ 200 protected String vmBits() { 201 String dataModel = System.getProperty("sun.arch.data.model"); 202 if (dataModel != null) { 203 return dataModel; 204 } else { 205 return errorWithMessage("Can't get 'sun.arch.data.model' property"); 206 } 207 } 208 209 /** 210 * @return "true" if Flight Recorder is enabled, "false" if is disabled. 211 */ 212 protected String vmFlightRecorder() { 213 Boolean isFlightRecorder = WB.getBooleanVMFlag("FlightRecorder"); 214 String startFROptions = WB.getStringVMFlag("StartFlightRecording"); 215 if (isFlightRecorder != null && isFlightRecorder) { 216 return "true"; 217 } 218 if (startFROptions != null && !startFROptions.isEmpty()) { 219 return "true"; 220 } 221 return "false"; 222 } 223 224 /** 225 * @return debug level value extracted from the "jdk.debug" property. 226 */ 227 protected String vmDebug() { 228 String debug = System.getProperty("jdk.debug"); 229 if (debug != null) { 230 return "" + debug.contains("debug"); 231 } else { 232 return errorWithMessage("Can't get 'jdk.debug' property"); 233 } 234 } 235 236 /** 237 * @return true if VM supports JVMCI and false otherwise 238 */ 239 protected String vmJvmci() { 240 // builds with jvmci have this flag 241 if (WB.getBooleanVMFlag("EnableJVMCI") == null) { 242 return "false"; 243 } 244 245 // Not all GCs have full JVMCI support 246 if (!WB.isJVMCISupportedByGC()) { 247 return "false"; 248 } 249 250 // Interpreted mode cannot enable JVMCI 251 if (vmCompMode().equals("Xint")) { 252 return "false"; 253 } 254 255 return "true"; 256 } 257 258 /** 259 * @return true if VM runs in emulated-client mode and false otherwise. 260 */ 261 protected String vmEmulatedClient() { 262 String vmInfo = System.getProperty("java.vm.info"); 263 if (vmInfo == null) { 264 return errorWithMessage("Can't get 'java.vm.info' property"); 265 } 266 return "" + vmInfo.contains(" emulated-client"); 267 } 268 269 /** 270 * @return supported CPU features 271 */ 272 protected String cpuFeatures() { 273 return CPUInfo.getFeatures().toString(); 274 } 275 276 /** 277 * For all existing GC sets vm.gc.X property. 278 * Example vm.gc.G1=true means: 279 * VM supports G1 280 * User either set G1 explicitely (-XX:+UseG1GC) or did not set any GC 281 * G1 can be selected, i.e. it doesn't conflict with other VM flags 282 * 283 * @param map - property-value pairs 284 */ 285 protected void vmGC(SafeMap map) { 286 var isJVMCIEnabled = Compiler.isJVMCIEnabled(); 287 for (GC gc: GC.values()) { 288 map.put("vm.gc." + gc.name(), 289 () -> "" + (gc.isSupported() 290 && (!isJVMCIEnabled || gc.isSupportedByJVMCICompiler()) 291 && (gc.isSelected() || GC.isSelectedErgonomically()))); 292 } 293 } 294 295 /** 296 * Selected final flag. 297 * 298 * @param map - property-value pairs 299 * @param flagName - flag name 300 */ 301 private void vmOptFinalFlag(SafeMap map, String flagName) { 302 map.put("vm.opt.final." + flagName, 303 () -> String.valueOf(WB.getBooleanVMFlag(flagName))); 304 } 305 306 /** 307 * Selected sets of final flags. 308 * 309 * @param map - property-value pairs 310 */ 311 protected void vmOptFinalFlags(SafeMap map) { 312 vmOptFinalFlag(map, "ClassUnloading"); 313 vmOptFinalFlag(map, "ClassUnloadingWithConcurrentMark"); 314 vmOptFinalFlag(map, "UseCompressedOops"); 315 vmOptFinalFlag(map, "UseVectorizedMismatchIntrinsic"); 316 vmOptFinalFlag(map, "EnableJVMCI"); 317 vmOptFinalFlag(map, "EliminateAllocations"); 318 vmOptFinalFlag(map, "UseVtableBasedCHA"); 319 } 320 321 /** 322 * @return "true" if VM has a serviceability agent. 323 */ 324 protected String vmHasSA() { 325 return "" + Platform.hasSA(); 326 } 327 328 /** 329 * @return "true" if the VM is compiled with Java Flight Recorder (JFR) 330 * support. 331 */ 332 protected String vmHasJFR() { 333 return "" + WB.isJFRIncluded(); 334 } 335 336 /** 337 * @return "true" if the VM is compiled with JVMTI 338 */ 339 protected String vmHasJVMTI() { 340 return "" + WB.isJVMTIIncluded(); 341 } 342 343 /** 344 * @return "true" if the VM is compiled with DTrace 345 */ 346 protected String vmHasDTrace() { 347 return "" + WB.isDTraceIncluded(); 348 } 349 350 /** 351 * @return true if compiler in use supports RTM and false otherwise. 352 */ 353 protected String vmRTMCompiler() { 354 boolean isRTMCompiler = false; 355 356 if (Compiler.isC2Enabled() && 357 (Platform.isX86() || Platform.isX64() || Platform.isPPC())) { 358 isRTMCompiler = true; 359 } 360 return "" + isRTMCompiler; 361 } 362 363 /** 364 * @return true if VM runs RTM supported CPU and false otherwise. 365 */ 366 protected String vmRTMCPU() { 367 return "" + CPUInfo.hasFeature("rtm"); 368 } 369 370 /** 371 * Check for CDS support. 372 * 373 * @return true if CDS is supported by the VM to be tested. 374 */ 375 protected String vmCDS() { 376 return "" + WB.isCDSIncluded(); 377 } 378 379 /** 380 * Check for CDS support for custom loaders. 381 * 382 * @return true if CDS provides support for customer loader in the VM to be tested. 383 */ 384 protected String vmCDSForCustomLoaders() { 385 return "" + ("true".equals(vmCDS()) && Platform.areCustomLoadersSupportedForCDS()); 386 } 387 388 /** 389 * Check for CDS support for archived Java heap regions. 390 * 391 * @return true if CDS provides support for archive Java heap regions in the VM to be tested. 392 */ 393 protected String vmCDSForArchivedJavaHeap() { 394 return "" + ("true".equals(vmCDS()) && WB.isJavaHeapArchiveSupported()); 395 } 396 397 /** 398 * @return System page size in bytes. 399 */ 400 protected String vmPageSize() { 401 return "" + WB.getVMPageSize(); 402 } 403 404 /** 405 * Check if Graal is used as JIT compiler. 406 * 407 * @return true if Graal is used as JIT compiler. 408 */ 409 protected String isGraalEnabled() { 410 return "" + Compiler.isGraalEnabled(); 411 } 412 413 /** 414 * Check if Compiler1 is present. 415 * 416 * @return true if Compiler1 is used as JIT compiler, either alone or as part of the tiered system. 417 */ 418 protected String isCompiler1Enabled() { 419 return "" + Compiler.isC1Enabled(); 420 } 421 422 /** 423 * Check if Compiler2 is present. 424 * 425 * @return true if Compiler2 is used as JIT compiler, either alone or as part of the tiered system. 426 */ 427 protected String isCompiler2Enabled() { 428 return "" + Compiler.isC2Enabled(); 429 } 430 431 /** 432 * A simple check for docker support 433 * 434 * @return true if docker is supported in a given environment 435 */ 436 protected String dockerSupport() { 437 boolean isSupported = false; 438 if (Platform.isLinux()) { 439 // currently docker testing is only supported for Linux, 440 // on certain platforms 441 442 String arch = System.getProperty("os.arch"); 443 444 if (Platform.isX64()) { 445 isSupported = true; 446 } else if (Platform.isAArch64()) { 447 isSupported = true; 448 } else if (Platform.isS390x()) { 449 isSupported = true; 450 } else if (arch.equals("ppc64le")) { 451 isSupported = true; 452 } 453 } 454 455 if (isSupported) { 456 try { 457 isSupported = checkDockerSupport(); 458 } catch (Exception e) { 459 isSupported = false; 460 } 461 } 462 463 return "" + isSupported; 464 } 465 466 private boolean checkDockerSupport() throws IOException, InterruptedException { 467 ProcessBuilder pb = new ProcessBuilder(Container.ENGINE_COMMAND, "ps"); 468 Process p = pb.start(); 469 p.waitFor(10, TimeUnit.SECONDS); 470 471 return (p.exitValue() == 0); 472 } 473 474 /** 475 * Checks musl libc. 476 * 477 * @return true if musl libc is used. 478 */ 479 protected String isMusl() { 480 return Boolean.toString(WB.getLibcName().contains("musl")); 481 } 482 483 private String implementor() { 484 try (InputStream in = new BufferedInputStream(new FileInputStream( 485 System.getProperty("java.home") + "/release"))) { 486 Properties properties = new Properties(); 487 properties.load(in); 488 String implementorProperty = properties.getProperty("IMPLEMENTOR"); 489 if (implementorProperty != null) { 490 return implementorProperty.replace("\"", ""); 491 } 492 return errorWithMessage("Can't get 'IMPLEMENTOR' property from 'release' file"); 493 } catch (IOException e) { 494 e.printStackTrace(); 495 return errorWithMessage("Failed to read 'release' file " + e); 496 } 497 } 498 499 private String jdkContainerized() { 500 String isEnabled = System.getenv("TEST_JDK_CONTAINERIZED"); 501 return "" + "true".equalsIgnoreCase(isEnabled); 502 } 503 504 /** 505 * Checks if we are in <i>almost</i> out-of-box configuration, i.e. the flags 506 * which JVM is started with don't affect its behavior "significantly". 507 * {@code TEST_VM_FLAGLESS} enviroment variable can be used to force this 508 * method to return true and allow any flags. 509 * 510 * @return true if there are no JVM flags 511 */ 512 private String isFlagless() { 513 boolean result = true; 514 if (System.getenv("TEST_VM_FLAGLESS") != null) { 515 return "" + result; 516 } 517 518 List<String> allFlags = new ArrayList<String>(); 519 Collections.addAll(allFlags, System.getProperty("test.vm.opts", "").trim().split("\\s+")); 520 Collections.addAll(allFlags, System.getProperty("test.java.opts", "").trim().split("\\s+")); 521 522 // check -XX flags 523 var ignoredXXFlags = Set.of( 524 // added by run-test framework 525 "MaxRAMPercentage", 526 // added by test environment 527 "CreateCoredumpOnCrash" 528 ); 529 result &= allFlags.stream() 530 .filter(s -> s.startsWith("-XX:")) 531 // map to names: 532 // remove -XX: 533 .map(s -> s.substring(4)) 534 // remove +/- from bool flags 535 .map(s -> s.charAt(0) == '+' || s.charAt(0) == '-' ? s.substring(1) : s) 536 // remove =.* from others 537 .map(s -> s.contains("=") ? s.substring(0, s.indexOf('=')) : s) 538 // skip known-to-be-there flags 539 .filter(s -> !ignoredXXFlags.contains(s)) 540 .findAny() 541 .isEmpty(); 542 543 // check -X flags 544 var ignoredXFlags = Set.of( 545 // default, yet still seen to be explicitly set 546 "mixed" 547 ); 548 result &= allFlags.stream() 549 .filter(s -> s.startsWith("-X") && !s.startsWith("-XX:")) 550 // map to names: 551 // remove -X 552 .map(s -> s.substring(2)) 553 // remove :.* from flags with values 554 .map(s -> s.contains(":") ? s.substring(0, s.indexOf(':')) : s) 555 // skip known-to-be-there flags 556 .filter(s -> !ignoredXFlags.contains(s)) 557 .findAny() 558 .isEmpty(); 559 560 return "" + result; 561 } 562 563 /** 564 * Dumps the map to the file if the file name is given as the property. 565 * This functionality could be helpful to know context in the real 566 * execution. 567 * 568 * @param map 569 */ 570 protected static void dump(Map<String, String> map) { 571 String dumpFileName = System.getProperty("vmprops.dump"); 572 if (dumpFileName == null) { 573 return; 574 } 575 List<String> lines = new ArrayList<>(); 576 map.forEach((k, v) -> lines.add(k + ":" + v)); 577 try { 578 Files.write(Paths.get(dumpFileName), lines, 579 StandardOpenOption.APPEND, StandardOpenOption.CREATE); 580 } catch (IOException e) { 581 throw new RuntimeException("Failed to dump properties into '" 582 + dumpFileName + "'", e); 583 } 584 } 585 586 /** 587 * This method is for the testing purpose only. 588 * 589 * @param args 590 */ 591 public static void main(String args[]) { 592 Map<String, String> map = new VMProps().call(); 593 map.forEach((k, v) -> System.out.println(k + ": '" + v + "'")); 594 } 595 }