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 }