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