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         if (registered != null) {
 287             for (OptionDescriptor descr : allOptionDescriptors()) {
 288                 attrs.add(new javax.management.MBeanAttributeInfo(descr.getName(), descr.getType().getName(), descr.getHelp(), true, true, false));
 289             }
 290         }
 291         javax.management.MBeanOperationInfo[] ops = {
 292                         new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
 293                                         new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 294                                         new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 295                         }, "void", javax.management.MBeanOperationInfo.ACTION),
 296                         new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
 297                                         new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 298                                         new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 299                                         new javax.management.MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
 300                         }, "void", javax.management.MBeanOperationInfo.ACTION),
 301                         new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
 302                                         new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
 303                                         new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
 304                                         new javax.management.MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
 305                                         new javax.management.MBeanParameterInfo("host", "java.lang.String", "The host where the IGV tool is running at"),
 306                                         new javax.management.MBeanParameterInfo("port", "int", "The port where the IGV tool is listening at"),
 307                         }, "void", javax.management.MBeanOperationInfo.ACTION)
 308         };
 309 
 310         return new javax.management.MBeanInfo(
 311                         HotSpotGraalMBean.class.getName(),
 312                         "Graal",
 313                         attrs.toArray(new javax.management.MBeanAttributeInfo[attrs.size()]),
 314                         null, ops, null);
 315     }
 316 
 317     private static Iterable<OptionDescriptor> allOptionDescriptors() {
 318         List<OptionDescriptor> arr = new ArrayList<>();
 319         for (OptionDescriptors set : OptionsParser.getOptionsLoader()) {
 320             for (OptionDescriptor descr : set) {
 321                 arr.add(descr);
 322             }
 323         }
 324         return arr;
 325     }
 326 
 327     private static final class Dump {
 328         final String host;
 329         final int port;
 330         final String clazz;
 331         final String method;
 332         final String filter;
 333 
 334         Dump(String host, int port, String clazz, String method, String filter) {
 335             this.host = host;
 336             this.port = port;
 337             this.clazz = clazz;
 338             this.method = method;
 339             this.filter = filter;
 340         }
 341     }
 342 
 343     private static final class RefEquivalence extends Equivalence {
 344         static final Equivalence INSTANCE = new RefEquivalence();
 345 
 346         private RefEquivalence() {
 347         }
 348 
 349         @Override
 350         public boolean equals(Object a, Object b) {
 351             Reference<?> refA = (Reference<?>) a;
 352             Reference<?> refB = (Reference<?>) b;
 353             return Objects.equals(refA.get(), refB.get());
 354         }
 355 
 356         @Override
 357         public int hashCode(Object o) {
 358             Reference<?> ref = (Reference<?>) o;
 359             Object obj = ref.get();
 360             return obj == null ? 0 : obj.hashCode();
 361         }
 362 
 363     }
 364 }