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 }