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