1 /* 2 * Copyright (c) 2013, 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 package org.graalvm.compiler.hotspot.test; 26 27 import static org.graalvm.compiler.hotspot.test.HotSpotGraalCompilerTest.assumeGraalIsNotJIT; 28 import static org.graalvm.compiler.hotspot.test.HotSpotGraalManagementTest.JunitShield.findAttributeInfo; 29 import static org.junit.Assert.assertEquals; 30 import static org.junit.Assert.assertNotNull; 31 import static org.junit.Assert.assertTrue; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.lang.management.ManagementFactory; 36 import java.nio.file.Files; 37 import java.nio.file.Path; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Comparator; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.stream.Collectors; 45 46 import javax.management.Attribute; 47 import javax.management.AttributeList; 48 import javax.management.AttributeNotFoundException; 49 import javax.management.InvalidAttributeValueException; 50 import javax.management.MBeanAttributeInfo; 51 import javax.management.MBeanInfo; 52 import javax.management.MBeanOperationInfo; 53 import javax.management.MBeanServer; 54 import javax.management.MBeanServerFactory; 55 import javax.management.ObjectInstance; 56 import javax.management.ObjectName; 57 58 import org.graalvm.compiler.api.test.Graal; 59 import org.graalvm.compiler.hotspot.HotSpotGraalManagementRegistration; 60 import org.graalvm.compiler.hotspot.HotSpotGraalRuntime; 61 import org.graalvm.compiler.options.EnumOptionKey; 62 import org.graalvm.compiler.options.NestedBooleanOptionKey; 63 import org.graalvm.compiler.options.OptionDescriptor; 64 import org.graalvm.compiler.options.OptionDescriptors; 65 import org.graalvm.compiler.options.OptionKey; 66 import org.graalvm.compiler.options.OptionsParser; 67 import org.junit.Assert; 68 import org.junit.AssumptionViolatedException; 69 import org.junit.Test; 70 71 public class HotSpotGraalManagementTest { 72 73 private static final boolean DEBUG = Boolean.getBoolean(HotSpotGraalManagementTest.class.getSimpleName() + ".debug"); 74 75 public HotSpotGraalManagementTest() { 76 assumeGraalIsNotJIT("random flipping of Graal options can cause havoc if Graal is being used as a JIT"); 77 try { 78 /* Trigger loading of the management library using the bootstrap class loader. */ 79 ManagementFactory.getThreadMXBean(); 80 MBeanServerFactory.findMBeanServer(null); 81 } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { 82 throw new AssumptionViolatedException("Management classes/module(s) not available: " + e); 83 } 84 } 85 86 @Test 87 public void registration() throws Exception { 88 HotSpotGraalRuntime runtime = (HotSpotGraalRuntime) Graal.getRuntime(); 89 HotSpotGraalManagementRegistration management = runtime.getManagement(); 90 if (management == null) { 91 return; 92 } 93 94 MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 95 96 ObjectName name; 97 assertNotNull("Now the bean thinks it is registered", name = (ObjectName) management.poll(true)); 98 99 assertNotNull("And the bean is found", server.getObjectInstance(name)); 100 } 101 102 @Test 103 public void readBeanInfo() throws Exception { 104 105 assertNotNull("Server is started", ManagementFactory.getPlatformMBeanServer()); 106 107 HotSpotGraalRuntime runtime = (HotSpotGraalRuntime) Graal.getRuntime(); 108 HotSpotGraalManagementRegistration management = runtime.getManagement(); 109 if (management == null) { 110 return; 111 } 112 113 ObjectName mbeanName; 114 assertNotNull("Bean is registered", mbeanName = (ObjectName) management.poll(true)); 115 final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 116 117 ObjectInstance bean = server.getObjectInstance(mbeanName); 118 assertNotNull("Bean is registered", bean); 119 MBeanInfo info = server.getMBeanInfo(mbeanName); 120 assertNotNull("Info is found", info); 121 122 AttributeList originalValues = new AttributeList(); 123 AttributeList newValues = new AttributeList(); 124 for (OptionDescriptors set : OptionsParser.getOptionsLoader()) { 125 for (OptionDescriptor option : set) { 126 JunitShield.testOption(info, mbeanName, server, runtime, option, newValues, originalValues); 127 } 128 } 129 130 String[] attributeNames = new String[originalValues.size()]; 131 for (int i = 0; i < attributeNames.length; i++) { 132 attributeNames[i] = ((Attribute) originalValues.get(i)).getName(); 133 } 134 AttributeList actualValues = server.getAttributes(mbeanName, attributeNames); 135 assertEquals(originalValues.size(), actualValues.size()); 136 for (int i = 0; i < attributeNames.length; i++) { 137 Object expect = String.valueOf(((Attribute) originalValues.get(i)).getValue()); 138 Object actual = String.valueOf(((Attribute) actualValues.get(i)).getValue()); 139 assertEquals(attributeNames[i], expect, actual); 140 } 141 142 try { 143 server.setAttributes(mbeanName, newValues); 144 } finally { 145 server.setAttributes(mbeanName, originalValues); 146 } 147 } 148 149 /** 150 * Junit scans all methods of a test class and tries to resolve all method parameter and return 151 * types. We hide such methods in an inner class to prevent errors such as: 152 * 153 * <pre> 154 * java.lang.NoClassDefFoundError: javax/management/MBeanInfo 155 * at java.base/java.lang.Class.getDeclaredMethods0(Native Method) 156 * at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3119) 157 * at java.base/java.lang.Class.getDeclaredMethods(Class.java:2268) 158 * at org.junit.internal.MethodSorter.getDeclaredMethods(MethodSorter.java:54) 159 * at org.junit.runners.model.TestClass.scanAnnotatedMembers(TestClass.java:65) 160 * at org.junit.runners.model.TestClass.<init>(TestClass.java:57) 161 * 162 * </pre> 163 */ 164 static class JunitShield { 165 166 /** 167 * Tests changing the value of {@code option} via the management interface to a) a new legal 168 * value and b) an illegal value. 169 */ 170 static void testOption(MBeanInfo mbeanInfo, 171 ObjectName mbeanName, 172 MBeanServer server, 173 HotSpotGraalRuntime runtime, 174 OptionDescriptor option, 175 AttributeList newValues, 176 AttributeList originalValues) throws Exception { 177 OptionKey<?> optionKey = option.getOptionKey(); 178 Object currentValue = optionKey.getValue(runtime.getOptions()); 179 Class<?> optionType = option.getOptionValueType(); 180 String name = option.getName(); 181 if (DEBUG) { 182 System.out.println("Testing option " + name); 183 } 184 MBeanAttributeInfo attrInfo = findAttributeInfo(name, mbeanInfo); 185 assertNotNull("Attribute not found for option " + name, attrInfo); 186 187 String expectAttributeValue = stringValue(currentValue, option.getOptionValueType() == String.class); 188 Object actualAttributeValue = server.getAttribute(mbeanName, name); 189 assertEquals(expectAttributeValue, actualAttributeValue); 190 191 Map<String, String> legalValues = new HashMap<>(); 192 List<String> illegalValues = new ArrayList<>(); 193 if (optionKey instanceof EnumOptionKey) { 194 EnumOptionKey<?> enumOptionKey = (EnumOptionKey<?>) optionKey; 195 for (Object obj : enumOptionKey.getAllValues()) { 196 if (obj != currentValue) { 197 legalValues.put(obj.toString(), obj.toString()); 198 } 199 } 200 illegalValues.add(String.valueOf(42)); 201 } else if (optionType == Boolean.class) { 202 Object defaultValue; 203 if (optionKey instanceof NestedBooleanOptionKey) { 204 NestedBooleanOptionKey nbok = (NestedBooleanOptionKey) optionKey; 205 defaultValue = nbok.getMasterOption().getValue(runtime.getOptions()); 206 } else { 207 defaultValue = optionKey.getDefaultValue(); 208 } 209 legalValues.put("", unquotedStringValue(defaultValue)); 210 illegalValues.add(String.valueOf(42)); 211 illegalValues.add("true"); 212 illegalValues.add("false"); 213 } else if (optionType == String.class) { 214 legalValues.put("", quotedStringValue(optionKey.getDefaultValue())); 215 legalValues.put("\"" + currentValue + "Prime\"", "\"" + currentValue + "Prime\""); 216 legalValues.put("\"quoted string\"", "\"quoted string\""); 217 illegalValues.add("\"unbalanced quotes"); 218 illegalValues.add("\""); 219 illegalValues.add("non quoted string"); 220 } else if (optionType == Float.class) { 221 legalValues.put("", unquotedStringValue(optionKey.getDefaultValue())); 222 String value = unquotedStringValue(currentValue == null ? 33F : ((float) currentValue) + 11F); 223 legalValues.put(value, value); 224 illegalValues.add("string"); 225 } else if (optionType == Double.class) { 226 legalValues.put("", unquotedStringValue(optionKey.getDefaultValue())); 227 String value = unquotedStringValue(currentValue == null ? 33D : ((double) currentValue) + 11D); 228 legalValues.put(value, value); 229 illegalValues.add("string"); 230 } else if (optionType == Integer.class) { 231 legalValues.put("", unquotedStringValue(optionKey.getDefaultValue())); 232 String value = unquotedStringValue(currentValue == null ? 33 : ((int) currentValue) + 11); 233 legalValues.put(value, value); 234 illegalValues.add("42.42"); 235 illegalValues.add("string"); 236 } else if (optionType == Long.class) { 237 legalValues.put("", unquotedStringValue(optionKey.getDefaultValue())); 238 String value = unquotedStringValue(currentValue == null ? 33L : ((long) currentValue) + 11L); 239 legalValues.put(value, value); 240 illegalValues.add("42.42"); 241 illegalValues.add("string"); 242 } 243 244 Attribute originalAttributeValue = new Attribute(name, expectAttributeValue); 245 try { 246 for (Map.Entry<String, String> e : legalValues.entrySet()) { 247 String legalValue = e.getKey(); 248 if (DEBUG) { 249 System.out.printf("Changing %s from %s to %s%n", name, currentValue, legalValue); 250 } 251 Attribute newAttributeValue = new Attribute(name, legalValue); 252 newValues.add(newAttributeValue); 253 server.setAttribute(mbeanName, newAttributeValue); 254 Object actual = optionKey.getValue(runtime.getOptions()); 255 actual = server.getAttribute(mbeanName, name); 256 String expectValue = e.getValue(); 257 if (option.getOptionValueType() == String.class && expectValue == null) { 258 expectValue = ""; 259 } else if (option.getOptionKey() instanceof NestedBooleanOptionKey && null == expectValue) { 260 NestedBooleanOptionKey nbok = (NestedBooleanOptionKey) option.getOptionKey(); 261 expectValue = String.valueOf(nbok.getValue(runtime.getOptions())); 262 actual = server.getAttribute(mbeanName, name); 263 } 264 assertEquals(expectValue, actual); 265 } 266 } finally { 267 if (DEBUG) { 268 System.out.printf("Resetting %s to %s%n", name, currentValue); 269 } 270 originalValues.add(originalAttributeValue); 271 server.setAttribute(mbeanName, originalAttributeValue); 272 } 273 274 try { 275 for (Object illegalValue : illegalValues) { 276 if (DEBUG) { 277 System.out.printf("Changing %s from %s to illegal value %s%n", name, currentValue, illegalValue); 278 } 279 server.setAttribute(mbeanName, new Attribute(name, illegalValue)); 280 Assert.fail("Expected setting " + name + " to " + illegalValue + " to fail"); 281 } 282 } catch (InvalidAttributeValueException e) { 283 // Expected 284 } finally { 285 if (DEBUG) { 286 System.out.printf("Resetting %s to %s%n", name, currentValue); 287 } 288 server.setAttribute(mbeanName, originalAttributeValue); 289 } 290 291 try { 292 293 String unknownOptionName = "definitely not an option name"; 294 server.setAttribute(mbeanName, new Attribute(unknownOptionName, "")); 295 Assert.fail("Expected setting option with name \"" + unknownOptionName + "\" to fail"); 296 } catch (AttributeNotFoundException e) { 297 // Expected 298 } 299 } 300 301 static MBeanAttributeInfo findAttributeInfo(String attrName, MBeanInfo info) { 302 for (MBeanAttributeInfo attr : info.getAttributes()) { 303 if (attr.getName().equals(attrName)) { 304 assertTrue("Readable", attr.isReadable()); 305 assertTrue("Writable", attr.isWritable()); 306 return attr; 307 } 308 } 309 return null; 310 } 311 } 312 313 private static String quotedStringValue(Object optionValue) { 314 return stringValue(optionValue, true); 315 } 316 317 private static String unquotedStringValue(Object optionValue) { 318 return stringValue(optionValue, false); 319 } 320 321 private static String stringValue(Object optionValue, boolean withQuoting) { 322 if (optionValue == null) { 323 return ""; 324 } 325 if (withQuoting) { 326 return "\"" + optionValue + "\""; 327 } 328 return String.valueOf(optionValue); 329 } 330 331 private static String quoted(Object s) { 332 return "\"" + s + "\""; 333 } 334 335 /** 336 * Tests publicaly visible names and identifiers used by tools developed and distributed on an 337 * independent schedule (like VisualVM). Consider keeping the test passing without any semantic 338 * modifications. The cost of changes is higher than you estimate. Include all available 339 * stakeholders as reviewers to give them a chance to stop you before causing too much damage. 340 */ 341 @Test 342 public void publicJmxApiOfGraalDumpOperation() throws Exception { 343 assertNotNull("Server is started", ManagementFactory.getPlatformMBeanServer()); 344 345 HotSpotGraalRuntime runtime = (HotSpotGraalRuntime) Graal.getRuntime(); 346 HotSpotGraalManagementRegistration management = runtime.getManagement(); 347 if (management == null) { 348 return; 349 } 350 351 ObjectName mbeanName; 352 assertNotNull("Bean is registered", mbeanName = (ObjectName) management.poll(true)); 353 final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 354 355 assertEquals("Domain name is used to lookup the beans by VisualVM", "org.graalvm.compiler.hotspot", mbeanName.getDomain()); 356 assertEquals("type can be used to identify the Graal bean", "HotSpotGraalRuntime_VM", mbeanName.getKeyProperty("type")); 357 358 ObjectInstance bean = server.getObjectInstance(mbeanName); 359 assertNotNull("Bean is registered", bean); 360 361 MBeanInfo info = server.getMBeanInfo(mbeanName); 362 assertNotNull("Info is found", info); 363 364 final MBeanOperationInfo[] arr = info.getOperations(); 365 MBeanOperationInfo dumpOp = null; 366 int dumpMethodCount = 0; 367 for (int i = 0; i < arr.length; i++) { 368 if ("dumpMethod".equals(arr[i].getName())) { 369 if (arr[i].getSignature().length == 3) { 370 dumpOp = arr[i]; 371 } 372 dumpMethodCount++; 373 } 374 } 375 assertEquals("Currently three overloads", 3, dumpMethodCount); 376 assertNotNull("three args variant (as used by VisualVM) found", dumpOp); 377 378 MBeanAttributeInfo dumpPath = findAttributeInfo("DumpPath", info); 379 MBeanAttributeInfo printGraphFile = findAttributeInfo("PrintGraphFile", info); 380 MBeanAttributeInfo showDumpFiles = findAttributeInfo("ShowDumpFiles", info); 381 MBeanAttributeInfo methodFilter = findAttributeInfo("MethodFilter", info); 382 Object originalDumpPath = server.getAttribute(mbeanName, dumpPath.getName()); 383 Object originalPrintGraphFile = server.getAttribute(mbeanName, printGraphFile.getName()); 384 Object originalShowDumpFiles = server.getAttribute(mbeanName, showDumpFiles.getName()); 385 Object originalMethodFilter = server.getAttribute(mbeanName, methodFilter.getName()); 386 final File tmpDir = new File(HotSpotGraalManagementTest.class.getSimpleName() + "_" + System.currentTimeMillis()).getAbsoluteFile(); 387 388 server.setAttribute(mbeanName, new Attribute(dumpPath.getName(), quoted(tmpDir))); 389 server.setAttribute(mbeanName, new Attribute(methodFilter.getName(), "")); 390 // Force output to a file even if there's a running IGV instance available. 391 server.setAttribute(mbeanName, new Attribute(printGraphFile.getName(), true)); 392 server.setAttribute(mbeanName, new Attribute(showDumpFiles.getName(), false)); 393 Object[] params = {"java.util.Arrays", "asList", ":3"}; 394 try { 395 server.invoke(mbeanName, "dumpMethod", params, null); 396 boolean found = false; 397 String expectedIgvDumpSuffix = "[Arrays.asList(Object[])List].bgv"; 398 Assert.assertTrue(tmpDir.toString() + " was not created or is not a directory", tmpDir.isDirectory()); 399 List<String> dumpPathEntries = Arrays.asList(tmpDir.list()); 400 for (String entry : dumpPathEntries) { 401 if (entry.endsWith(expectedIgvDumpSuffix)) { 402 found = true; 403 } 404 } 405 if (!found) { 406 Assert.fail(String.format("Expected file ending with \"%s\" in %s but only found:%n%s", expectedIgvDumpSuffix, tmpDir, 407 dumpPathEntries.stream().collect(Collectors.joining(System.lineSeparator())))); 408 } 409 } finally { 410 if (tmpDir.isDirectory()) { 411 deleteDirectory(tmpDir.toPath()); 412 } 413 server.setAttribute(mbeanName, new Attribute(dumpPath.getName(), originalDumpPath)); 414 server.setAttribute(mbeanName, new Attribute(methodFilter.getName(), originalMethodFilter)); 415 server.setAttribute(mbeanName, new Attribute(printGraphFile.getName(), originalPrintGraphFile)); 416 server.setAttribute(mbeanName, new Attribute(showDumpFiles.getName(), originalShowDumpFiles)); 417 } 418 } 419 420 static void deleteDirectory(Path toDelete) throws IOException { 421 Files.walk(toDelete).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); 422 } 423 }