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