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 }