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 }