1 /*
   2  * Copyright (c) 2018, 2019, 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.management;
  26 
  27 import org.graalvm.compiler.phases.common.jmx.HotSpotMBeanOperationProvider;
  28 import java.util.ArrayList;
  29 import java.util.Arrays;
  30 import java.util.Comparator;
  31 import java.util.List;
  32 
  33 import javax.management.Attribute;
  34 import javax.management.AttributeList;
  35 import javax.management.AttributeNotFoundException;
  36 import javax.management.DynamicMBean;
  37 import javax.management.InvalidAttributeValueException;
  38 import javax.management.MBeanAttributeInfo;
  39 import javax.management.MBeanException;
  40 import javax.management.MBeanInfo;
  41 import javax.management.MBeanOperationInfo;
  42 import javax.management.MBeanParameterInfo;
  43 import javax.management.ObjectName;
  44 import javax.management.ReflectionException;
  45 
  46 import jdk.internal.vm.compiler.collections.EconomicMap;
  47 import org.graalvm.compiler.core.common.SuppressFBWarnings;
  48 import org.graalvm.compiler.debug.TTY;
  49 import org.graalvm.compiler.hotspot.HotSpotGraalRuntime;
  50 import org.graalvm.compiler.options.OptionDescriptor;
  51 import org.graalvm.compiler.options.OptionDescriptors;
  52 import org.graalvm.compiler.options.OptionsParser;
  53 import org.graalvm.compiler.serviceprovider.GraalServices;
  54 
  55 import jdk.vm.ci.services.Services;
  56 
  57 /**
  58  * MBean used to access properties and operations of a {@link HotSpotGraalRuntime} instance.
  59  */
  60 final class HotSpotGraalRuntimeMBean implements DynamicMBean {
  61 
  62     /**
  63      * The runtime instance to which this bean provides a management connection.
  64      */
  65     private final HotSpotGraalRuntime runtime;
  66 
  67     /**
  68      * The object name under which the bean is registered.
  69      */
  70     private final ObjectName objectName;
  71 
  72     HotSpotGraalRuntimeMBean(ObjectName objectName, HotSpotGraalRuntime runtime) {
  73         this.objectName = objectName;
  74         this.runtime = runtime;
  75     }
  76 
  77     ObjectName getObjectName() {
  78         return objectName;
  79     }
  80 
  81     HotSpotGraalRuntime getRuntime() {
  82         return runtime;
  83     }
  84 
  85     private static final boolean DEBUG = initDebug();
  86 
  87     private static boolean initDebug() {
  88         try {
  89             return Boolean.parseBoolean(Services.getSavedProperties().get(HotSpotGraalRuntimeMBean.class.getSimpleName() + ".debug"));
  90         } catch (SecurityException e) {
  91             // Swallow the exception
  92             return false;
  93         }
  94     }
  95 
  96     @Override
  97     public Object getAttribute(String name) throws AttributeNotFoundException {
  98         String[] result = runtime.getOptionValues(name);
  99         String value = result[0];
 100         if (value == null) {
 101             throw new AttributeNotFoundException(name);
 102         }
 103         if (DEBUG) {
 104             System.out.printf("getAttribute: %s = %s (type: %s)%n", name, value, value == null ? "null" : value.getClass().getName());
 105         }
 106         return result[0];
 107     }
 108 
 109     @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "reference equality on the receiver is what we want")
 110     @Override
 111     public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException {
 112         String name = attribute.getName();
 113         Object value = attribute.getValue();
 114         String svalue = String.valueOf(value);
 115         if (DEBUG) {
 116             System.out.printf("setAttribute: %s = %s (type: %s)%n", name, svalue, value == null ? "null" : value.getClass().getName());
 117         }
 118         String[] result = runtime.setOptionValues(new String[]{name}, new String[]{svalue});
 119         if (result[0] != name) {
 120             if (result[0] == null) {
 121                 throw new AttributeNotFoundException(name);
 122             }
 123             throw new InvalidAttributeValueException(result[0]);
 124         }
 125     }
 126 
 127     @Override
 128     public AttributeList getAttributes(String[] names) {
 129         String[] values = runtime.getOptionValues(names);
 130         AttributeList list = new AttributeList();
 131         for (int i = 0; i < names.length; i++) {
 132             String value = values[i];
 133             String name = names[i];
 134             if (value == null) {
 135                 TTY.printf("No such option named %s%n", name);
 136             } else {
 137                 if (DEBUG) {
 138                     System.out.printf("getAttributes: %s = %s (type: %s)%n", name, value, value == null ? "null" : value.getClass().getName());
 139                 }
 140                 list.add(new Attribute(name, value));
 141             }
 142         }
 143         return list;
 144     }
 145 
 146     @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "reference equality on the receiver is what we want")
 147     @Override
 148     public AttributeList setAttributes(AttributeList attributes) {
 149         String[] names = new String[attributes.size()];
 150         String[] values = new String[attributes.size()];
 151 
 152         int i = 0;
 153         for (Attribute attr : attributes.asList()) {
 154             String name = attr.getName();
 155             names[i] = name;
 156             Object value = attr.getValue();
 157             String svalue = String.valueOf(value);
 158             values[i] = svalue;
 159             if (DEBUG) {
 160                 System.out.printf("setAttributes: %s = %s (type: %s)%n", name, svalue, value == null ? "null" : value.getClass().getName());
 161             }
 162             i++;
 163         }
 164         String[] result = runtime.setOptionValues(names, values);
 165         AttributeList setOk = new AttributeList();
 166         i = 0;
 167         for (Attribute attr : attributes.asList()) {
 168             if (names[i] == result[i]) {
 169                 setOk.add(attr);
 170             } else if (result[i] == null) {
 171                 TTY.printf("Error setting %s to %s: unknown option%n", attr.getName(), attr.getValue());
 172             } else {
 173                 TTY.printf("Error setting %s to %s: %s%n", attr.getName(), attr.getValue(), result[i]);
 174             }
 175             i++;
 176         }
 177         return setOk;
 178     }
 179 
 180     @Override
 181     public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
 182         try {
 183             if (DEBUG) {
 184                 System.out.printf("invoke: %s%s%n", actionName, Arrays.asList(params));
 185             }
 186             Object retvalue = null;
 187             if ("dumpMethod".equals(actionName)) {
 188                 retvalue = runtime.invokeManagementAction(actionName, params);
 189             } else {
 190                 boolean found = false;
 191                 for (HotSpotMBeanOperationProvider p : GraalServices.load(HotSpotMBeanOperationProvider.class)) {
 192                     List<MBeanOperationInfo> info = new ArrayList<>();
 193                     p.registerOperations(MBeanOperationInfo.class, info);
 194                     for (MBeanOperationInfo op : info) {
 195                         if (actionName.equals(op.getName())) {
 196                             retvalue = p.invoke(actionName, params, signature);
 197                             found = true;
 198                             break;
 199                         }
 200                     }
 201                 }
 202                 if (!found) {
 203                     throw new MBeanException(new IllegalStateException("Cannot find operation " + actionName));
 204                 }
 205             }
 206             if (DEBUG) {
 207                 System.out.printf("invoke: %s%s = %s%n", actionName, Arrays.asList(params), retvalue);
 208             }
 209             return retvalue;
 210         } catch (MBeanException ex) {
 211             throw ex;
 212         } catch (Exception ex) {
 213             throw new ReflectionException(ex);
 214         }
 215     }
 216 
 217     @Override
 218     public MBeanInfo getMBeanInfo() {
 219         List<MBeanAttributeInfo> attrs = new ArrayList<>();
 220         for (OptionDescriptor option : getOptionDescriptors().getValues()) {
 221             Class<?> optionValueType = option.getOptionValueType();
 222             if (Enum.class.isAssignableFrom(optionValueType)) {
 223                 // Enum values are passed through
 224                 // the management interface as Strings.
 225                 optionValueType = String.class;
 226             }
 227             attrs.add(new MBeanAttributeInfo(option.getName(), optionValueType.getName(), option.getHelp(), true, true, false));
 228         }
 229         attrs.sort(new Comparator<MBeanAttributeInfo>() {
 230             @Override
 231             public int compare(MBeanAttributeInfo o1, MBeanAttributeInfo o2) {
 232                 return o1.getName().compareTo(o2.getName());
 233             }
 234         });
 235         List<MBeanOperationInfo> opts = new ArrayList<>();
 236         opts.add(new MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new MBeanParameterInfo[]{
 237                         new MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 238                         new MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 239         }, "void", MBeanOperationInfo.ACTION));
 240         opts.add(new MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new MBeanParameterInfo[]{
 241                         new MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 242                         new MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 243                         new MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
 244         }, "void", MBeanOperationInfo.ACTION));
 245         opts.add(new MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new MBeanParameterInfo[]{
 246                         new MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 247                         new MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 248                         new MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
 249                         new MBeanParameterInfo("host", "java.lang.String", "The host where the IGV tool is running at"),
 250                         new MBeanParameterInfo("port", "int", "The port where the IGV tool is listening at"),
 251         }, "void", MBeanOperationInfo.ACTION));
 252 
 253         for (HotSpotMBeanOperationProvider p : GraalServices.load(HotSpotMBeanOperationProvider.class)) {
 254             p.registerOperations(MBeanOperationInfo.class, opts);
 255         }
 256 
 257         return new MBeanInfo(
 258                         HotSpotGraalRuntimeMBean.class.getName(),
 259                         "Graal",
 260                         attrs.toArray(new MBeanAttributeInfo[attrs.size()]),
 261                         null,
 262                         opts.toArray(new MBeanOperationInfo[opts.size()]),
 263                         null);
 264     }
 265 
 266     private static EconomicMap<String, OptionDescriptor> getOptionDescriptors() {
 267         EconomicMap<String, OptionDescriptor> result = EconomicMap.create();
 268         for (OptionDescriptors set : OptionsParser.getOptionsLoader()) {
 269             for (OptionDescriptor option : set) {
 270                 result.put(option.getName(), option);
 271             }
 272         }
 273         return result;
 274     }
 275 }