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