1 /*
   2  * Copyright (c) 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 package org.graalvm.compiler.hotspot;
  24 
  25 import java.lang.ref.Reference;
  26 import java.lang.ref.WeakReference;
  27 import java.lang.reflect.Field;
  28 import java.util.ArrayList;
  29 import java.util.Iterator;
  30 import java.util.List;
  31 import java.util.Objects;
  32 import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
  33 import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
  34 import jdk.vm.ci.hotspot.HotSpotResolvedJavaType;
  35 import jdk.vm.ci.hotspot.HotSpotResolvedObjectType;
  36 import jdk.vm.ci.meta.MetaUtil;
  37 import jdk.vm.ci.meta.ResolvedJavaMethod;
  38 import jdk.vm.ci.meta.ResolvedJavaType;
  39 import jdk.vm.ci.runtime.JVMCI;
  40 
  41 import org.graalvm.compiler.debug.DebugOptions;
  42 import org.graalvm.compiler.options.OptionDescriptor;
  43 import org.graalvm.compiler.options.OptionDescriptors;
  44 import org.graalvm.compiler.options.OptionKey;
  45 import org.graalvm.compiler.options.OptionValues;
  46 import org.graalvm.compiler.options.OptionsParser;
  47 import org.graalvm.util.EconomicMap;
  48 import org.graalvm.util.EconomicSet;
  49 import org.graalvm.util.Equivalence;
  50 import org.graalvm.util.UnmodifiableEconomicMap;
  51 
  52 public final class HotSpotGraalMBean implements javax.management.DynamicMBean {
  53     private static Object mBeanServerField;
  54     private final HotSpotGraalCompiler compiler;
  55     private final OptionValues options;
  56     private final EconomicMap<OptionKey<?>, Object> changes;
  57     private final EconomicSet<Dump> methodDumps;
  58     private volatile EconomicSet<Reference<ClassLoader>> loaders;
  59     private javax.management.ObjectName registered;
  60     private OptionValues cachedOptions;
  61 
  62     private HotSpotGraalMBean(HotSpotGraalCompiler compiler, OptionValues options) {
  63         this.compiler = compiler;
  64         this.options = options;
  65         this.changes = EconomicMap.create();
  66         this.methodDumps = EconomicSet.create();
  67         EconomicSet<Reference<ClassLoader>> systemLoaderSet = EconomicSet.create(RefEquivalence.INSTANCE);
  68         systemLoaderSet.add(new WeakReference<>(ClassLoader.getSystemClassLoader()));
  69         this.loaders = systemLoaderSet;
  70     }
  71 
  72     private static boolean isMXServerOn() {
  73         if (mBeanServerField == null) {
  74             try {
  75                 final Field field = java.lang.management.ManagementFactory.class.getDeclaredField("platformMBeanServer");
  76                 field.setAccessible(true);
  77                 mBeanServerField = field;
  78             } catch (Exception ex) {
  79                 mBeanServerField = java.lang.management.ManagementFactory.class;
  80             }
  81         }
  82         if (mBeanServerField instanceof Field) {
  83             try {
  84                 return ((Field) mBeanServerField).get(null) != null;
  85             } catch (Exception ex) {
  86                 return true;
  87             }
  88         } else {
  89             return false;
  90         }
  91     }
  92 
  93     public static HotSpotGraalMBean create(HotSpotGraalCompiler compiler) {
  94         OptionValues options = HotSpotGraalOptionValues.HOTSPOT_OPTIONS;
  95         HotSpotGraalMBean mbean = new HotSpotGraalMBean(compiler, options);
  96         return mbean;
  97     }
  98 
  99     public javax.management.ObjectName ensureRegistered(boolean check) {
 100         for (int cnt = 0;; cnt++) {
 101             if (registered != null) {
 102                 return registered;
 103             }
 104             if (check && !isMXServerOn()) {
 105                 return null;
 106             }
 107             try {
 108                 javax.management.MBeanServer mbs = java.lang.management.ManagementFactory.getPlatformMBeanServer();
 109                 javax.management.ObjectName name = new javax.management.ObjectName("org.graalvm.compiler.hotspot:type=Options" + (cnt == 0 ? "" : cnt));
 110                 mbs.registerMBean(this, name);
 111                 registered = name;
 112                 break;
 113             } catch (javax.management.MalformedObjectNameException | javax.management.MBeanRegistrationException | javax.management.NotCompliantMBeanException ex) {
 114                 throw new IllegalStateException(ex);
 115             } catch (javax.management.InstanceAlreadyExistsException ex) {
 116                 continue;
 117             }
 118         }
 119         return registered;
 120     }
 121 
 122     public OptionValues optionsFor(OptionValues initialValues, ResolvedJavaMethod forMethod) {
 123         ensureRegistered(true);
 124         if (forMethod instanceof HotSpotResolvedJavaMethod) {
 125             HotSpotResolvedObjectType type = ((HotSpotResolvedJavaMethod) forMethod).getDeclaringClass();
 126             if (type instanceof HotSpotResolvedJavaType) {
 127                 Class<?> clazz = ((HotSpotResolvedJavaType) type).mirror();
 128                 Reference<ClassLoader> addNewRef = new WeakReference<>(clazz.getClassLoader());
 129                 if (!loaders.contains(addNewRef)) {
 130                     EconomicSet<Reference<ClassLoader>> newLoaders = EconomicSet.create(RefEquivalence.INSTANCE, loaders);
 131                     newLoaders.add(addNewRef);
 132                     this.loaders = newLoaders;
 133                 }
 134             }
 135         }
 136         return currentMap(initialValues, forMethod);
 137     }
 138 
 139     private OptionValues currentMap(OptionValues initialValues, ResolvedJavaMethod method) {
 140         if (changes.isEmpty() && methodDumps.isEmpty()) {
 141             return initialValues;
 142         }
 143         OptionValues current = cachedOptions;
 144         if (current == null) {
 145             current = new OptionValues(initialValues, changes);
 146             cachedOptions = current;
 147         }
 148         if (method != null) {
 149             for (Dump request : methodDumps) {
 150                 final String clazzName = method.getDeclaringClass().getName();
 151                 if (method.getName().equals(request.method) && clazzName.equals(request.clazz)) {
 152                     current = new OptionValues(current, DebugOptions.Dump, request.filter,
 153                                     DebugOptions.PrintGraphHost, request.host,
 154                                     DebugOptions.PrintBinaryGraphPort, request.port);
 155                     break;
 156                 }
 157             }
 158         }
 159         return current;
 160     }
 161 
 162     @Override
 163     public Object getAttribute(String attribute) {
 164         UnmodifiableEconomicMap<OptionKey<?>, Object> map = currentMap(options, null).getMap();
 165         for (OptionKey<?> k : map.getKeys()) {
 166             if (k.getName().equals(attribute)) {
 167                 return map.get(k);
 168             }
 169         }
 170         return null;
 171     }
 172 
 173     @Override
 174     public void setAttribute(javax.management.Attribute attribute) throws javax.management.AttributeNotFoundException {
 175         javax.management.Attribute newAttr = setImpl(attribute);
 176         if (newAttr == null) {
 177             throw new javax.management.AttributeNotFoundException();
 178         }
 179     }
 180 
 181     private javax.management.Attribute setImpl(javax.management.Attribute attribute) {
 182         cachedOptions = null;
 183         for (OptionDescriptor option : allOptionDescriptors()) {
 184             if (option.getName().equals(attribute.getName())) {
 185                 changes.put(option.getOptionKey(), attribute.getValue());
 186                 return attribute;
 187             }
 188         }
 189         return null;
 190     }
 191 
 192     @Override
 193     public javax.management.AttributeList getAttributes(String[] names) {
 194         javax.management.AttributeList list = new javax.management.AttributeList();
 195         for (String name : names) {
 196             Object value = getAttribute(name);
 197             if (value != null) {
 198                 list.add(new javax.management.Attribute(name, value));
 199             }
 200         }
 201         return list;
 202     }
 203 
 204     @Override
 205     public javax.management.AttributeList setAttributes(javax.management.AttributeList attributes) {
 206         javax.management.AttributeList setOk = new javax.management.AttributeList();
 207         for (javax.management.Attribute attr : attributes.asList()) {
 208             javax.management.Attribute newAttr = setImpl(attr);
 209             if (newAttr != null) {
 210                 setOk.add(newAttr);
 211             }
 212         }
 213         return setOk;
 214     }
 215 
 216     @Override
 217     public Object invoke(String actionName, Object[] params, String[] signature) throws javax.management.MBeanException, javax.management.ReflectionException {
 218         if ("dumpMethod".equals(actionName)) {
 219             try {
 220                 String className = param(params, 0, "className", String.class, null);
 221                 String methodName = param(params, 1, "methodName", String.class, null);
 222                 String filter = param(params, 2, "filter", String.class, ":3");
 223                 String host = param(params, 3, "host", String.class, "localhost");
 224                 Number port = param(params, 4, "port", Number.class, 4445);
 225                 dumpMethod(className, methodName, filter, host, port.intValue());
 226             } catch (Exception ex) {
 227                 throw new javax.management.ReflectionException(ex);
 228             }
 229         }
 230         return null;
 231     }
 232 
 233     private static <T> T param(Object[] arr, int index, String name, Class<T> type, T defaultValue) {
 234         Object value = arr.length > index ? arr[index] : null;
 235         if (value == null || (value instanceof String && ((String) value).isEmpty())) {
 236             if (defaultValue == null) {
 237                 throw new IllegalArgumentException(name + " must be specified");
 238             }
 239             value = defaultValue;
 240         }
 241         if (type.isInstance(value)) {
 242             return type.cast(value);
 243         }
 244         throw new IllegalArgumentException("Expecting " + type.getName() + " for " + name + " but was " + value);
 245     }
 246 
 247     public void dumpMethod(String className, String methodName, String filter, String host, int port) throws javax.management.MBeanException {
 248         String jvmName = MetaUtil.toInternalName(className);
 249         methodDumps.add(new Dump(host, port, jvmName, methodName, filter));
 250 
 251         ClassNotFoundException last = null;
 252         EconomicSet<Class<?>> found = EconomicSet.create();
 253         Iterator<Reference<ClassLoader>> it = loaders.iterator();
 254         while (it.hasNext()) {
 255             Reference<ClassLoader> ref = it.next();
 256             ClassLoader loader = ref.get();
 257             if (loader == null) {
 258                 it.remove();
 259                 continue;
 260             }
 261             try {
 262                 Class<?> clazz = Class.forName(className, false, loader);
 263                 if (found.add(clazz)) {
 264                     ResolvedJavaType type = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaType(clazz);
 265                     if (compiler != null) {
 266                         for (ResolvedJavaMethod method : type.getDeclaredMethods()) {
 267                             if (methodName.equals(method.getName()) && method instanceof HotSpotResolvedJavaMethod) {
 268                                 HotSpotResolvedJavaMethod hotSpotMethod = (HotSpotResolvedJavaMethod) method;
 269                                 compiler.compileMethod(new HotSpotCompilationRequest(hotSpotMethod, -1, 0L), false);
 270                             }
 271                         }
 272                     }
 273                 }
 274             } catch (ClassNotFoundException ex) {
 275                 last = ex;
 276             }
 277         }
 278         if (found.isEmpty()) {
 279             throw new javax.management.MBeanException(last, "Cannot find class " + className + " to schedule recompilation");
 280         }
 281     }
 282 
 283     @Override
 284     public javax.management.MBeanInfo getMBeanInfo() {
 285         List<javax.management.MBeanAttributeInfo> attrs = new ArrayList<>();
 286         for (OptionDescriptor descr : allOptionDescriptors()) {
 287             attrs.add(new javax.management.MBeanAttributeInfo(descr.getName(), descr.getType().getName(), descr.getHelp(), true, true, false));
 288         }
 289         javax.management.MBeanOperationInfo[] ops = {
 290                         new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
 291                                         new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 292                                         new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 293                         }, "void", javax.management.MBeanOperationInfo.ACTION),
 294                         new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
 295                                         new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 296                                         new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 297                                         new javax.management.MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
 298                         }, "void", javax.management.MBeanOperationInfo.ACTION),
 299                         new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
 300                                         new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 301                                         new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 302                                         new javax.management.MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
 303                                         new javax.management.MBeanParameterInfo("host", "java.lang.String", "The host where the IGV tool is running at"),
 304                                         new javax.management.MBeanParameterInfo("port", "int", "The port where the IGV tool is listening at"),
 305                         }, "void", javax.management.MBeanOperationInfo.ACTION)
 306         };
 307 
 308         return new javax.management.MBeanInfo(
 309                         HotSpotGraalMBean.class.getName(),
 310                         "Graal",
 311                         attrs.toArray(new javax.management.MBeanAttributeInfo[attrs.size()]),
 312                         null, ops, null);
 313     }
 314 
 315     private static Iterable<OptionDescriptor> allOptionDescriptors() {
 316         List<OptionDescriptor> arr = new ArrayList<>();
 317         for (OptionDescriptors set : OptionsParser.getOptionsLoader()) {
 318             for (OptionDescriptor descr : set) {
 319                 arr.add(descr);
 320             }
 321         }
 322         return arr;
 323     }
 324 
 325     private static final class Dump {
 326         final String host;
 327         final int port;
 328         final String clazz;
 329         final String method;
 330         final String filter;
 331 
 332         Dump(String host, int port, String clazz, String method, String filter) {
 333             this.host = host;
 334             this.port = port;
 335             this.clazz = clazz;
 336             this.method = method;
 337             this.filter = filter;
 338         }
 339     }
 340 
 341     private static final class RefEquivalence extends Equivalence {
 342         static final Equivalence INSTANCE = new RefEquivalence();
 343 
 344         private RefEquivalence() {
 345         }
 346 
 347         @Override
 348         public boolean equals(Object a, Object b) {
 349             Reference<?> refA = (Reference<?>) a;
 350             Reference<?> refB = (Reference<?>) b;
 351             return Objects.equals(refA.get(), refB.get());
 352         }
 353 
 354         @Override
 355         public int hashCode(Object o) {
 356             Reference<?> ref = (Reference<?>) o;
 357             Object obj = ref.get();
 358             return obj == null ? 0 : obj.hashCode();
 359         }
 360 
 361     }
 362 }