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 }