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