package org.graalvm.compiler.hotspot;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
import jdk.vm.ci.hotspot.HotSpotResolvedJavaType;
import jdk.vm.ci.hotspot.HotSpotResolvedObjectType;
import jdk.vm.ci.meta.MetaUtil;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.runtime.JVMCI;
import org.graalvm.compiler.debug.DebugOptions;
import org.graalvm.compiler.options.OptionDescriptor;
import org.graalvm.compiler.options.OptionDescriptors;
import org.graalvm.compiler.options.OptionKey;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.compiler.options.OptionsParser;
import org.graalvm.util.EconomicMap;
import org.graalvm.util.EconomicSet;
import org.graalvm.util.Equivalence;
import org.graalvm.util.UnmodifiableEconomicMap;
public final class HotSpotGraalMBean implements javax.management.DynamicMBean {
private static Object mBeanServerField;
private final HotSpotGraalCompiler compiler;
private final OptionValues options;
private final EconomicMap<OptionKey<?>, Object> changes;
private final EconomicSet<Dump> methodDumps;
private volatile EconomicSet<Reference<ClassLoader>> loaders;
private javax.management.ObjectName registered;
private OptionValues cachedOptions;
private HotSpotGraalMBean(HotSpotGraalCompiler compiler, OptionValues options) {
this.compiler = compiler;
this.options = options;
this.changes = EconomicMap.create();
this.methodDumps = EconomicSet.create();
EconomicSet<Reference<ClassLoader>> systemLoaderSet = EconomicSet.create(RefEquivalence.INSTANCE);
systemLoaderSet.add(new WeakReference<>(ClassLoader.getSystemClassLoader()));
this.loaders = systemLoaderSet;
}
private static boolean isMXServerOn() {
if (mBeanServerField == null) {
try {
final Field field = java.lang.management.ManagementFactory.class.getDeclaredField("platformMBeanServer");
field.setAccessible(true);
mBeanServerField = field;
} catch (Exception ex) {
mBeanServerField = java.lang.management.ManagementFactory.class;
}
}
if (mBeanServerField instanceof Field) {
try {
return ((Field) mBeanServerField).get(null) != null;
} catch (Exception ex) {
return true;
}
} else {
return false;
}
}
public static HotSpotGraalMBean create(HotSpotGraalCompiler compiler) {
OptionValues options = HotSpotGraalOptionValues.HOTSPOT_OPTIONS;
HotSpotGraalMBean mbean = new HotSpotGraalMBean(compiler, options);
return mbean;
}
public javax.management.ObjectName ensureRegistered(boolean check) {
for (int cnt = 0;; cnt++) {
if (registered != null) {
return registered;
}
if (check && !isMXServerOn()) {
return null;
}
try {
javax.management.MBeanServer mbs = java.lang.management.ManagementFactory.getPlatformMBeanServer();
javax.management.ObjectName name = new javax.management.ObjectName("org.graalvm.compiler.hotspot:type=Options" + (cnt == 0 ? "" : cnt));
mbs.registerMBean(this, name);
registered = name;
break;
} catch (javax.management.MalformedObjectNameException | javax.management.MBeanRegistrationException | javax.management.NotCompliantMBeanException ex) {
throw new IllegalStateException(ex);
} catch (javax.management.InstanceAlreadyExistsException ex) {
continue;
}
}
return registered;
}
public OptionValues optionsFor(OptionValues initialValues, ResolvedJavaMethod forMethod) {
ensureRegistered(true);
if (forMethod instanceof HotSpotResolvedJavaMethod) {
HotSpotResolvedObjectType type = ((HotSpotResolvedJavaMethod) forMethod).getDeclaringClass();
if (type instanceof HotSpotResolvedJavaType) {
Class<?> clazz = ((HotSpotResolvedJavaType) type).mirror();
Reference<ClassLoader> addNewRef = new WeakReference<>(clazz.getClassLoader());
if (!loaders.contains(addNewRef)) {
EconomicSet<Reference<ClassLoader>> newLoaders = EconomicSet.create(RefEquivalence.INSTANCE, loaders);
newLoaders.add(addNewRef);
this.loaders = newLoaders;
}
}
}
return currentMap(initialValues, forMethod);
}
private OptionValues currentMap(OptionValues initialValues, ResolvedJavaMethod method) {
if (changes.isEmpty() && methodDumps.isEmpty()) {
return initialValues;
}
OptionValues current = cachedOptions;
if (current == null) {
current = new OptionValues(initialValues, changes);
cachedOptions = current;
}
if (method != null) {
for (Dump request : methodDumps) {
final String clazzName = method.getDeclaringClass().getName();
if (method.getName().equals(request.method) && clazzName.equals(request.clazz)) {
current = new OptionValues(current, DebugOptions.Dump, request.filter,
DebugOptions.PrintGraphHost, request.host,
DebugOptions.PrintBinaryGraphPort, request.port);
break;
}
}
}
return current;
}
@Override
public Object getAttribute(String attribute) {
UnmodifiableEconomicMap<OptionKey<?>, Object> map = currentMap(options, null).getMap();
for (OptionKey<?> k : map.getKeys()) {
if (k.getName().equals(attribute)) {
return map.get(k);
}
}
return null;
}
@Override
public void setAttribute(javax.management.Attribute attribute) throws javax.management.AttributeNotFoundException {
javax.management.Attribute newAttr = setImpl(attribute);
if (newAttr == null) {
throw new javax.management.AttributeNotFoundException();
}
}
private javax.management.Attribute setImpl(javax.management.Attribute attribute) {
cachedOptions = null;
for (OptionDescriptor option : allOptionDescriptors()) {
if (option.getName().equals(attribute.getName())) {
changes.put(option.getOptionKey(), attribute.getValue());
return attribute;
}
}
return null;
}
@Override
public javax.management.AttributeList getAttributes(String[] names) {
javax.management.AttributeList list = new javax.management.AttributeList();
for (String name : names) {
Object value = getAttribute(name);
if (value != null) {
list.add(new javax.management.Attribute(name, value));
}
}
return list;
}
@Override
public javax.management.AttributeList setAttributes(javax.management.AttributeList attributes) {
javax.management.AttributeList setOk = new javax.management.AttributeList();
for (javax.management.Attribute attr : attributes.asList()) {
javax.management.Attribute newAttr = setImpl(attr);
if (newAttr != null) {
setOk.add(newAttr);
}
}
return setOk;
}
@Override
public Object invoke(String actionName, Object[] params, String[] signature) throws javax.management.MBeanException, javax.management.ReflectionException {
if ("dumpMethod".equals(actionName)) {
try {
String className = param(params, 0, "className", String.class, null);
String methodName = param(params, 1, "methodName", String.class, null);
String filter = param(params, 2, "filter", String.class, ":3");
String host = param(params, 3, "host", String.class, "localhost");
Number port = param(params, 4, "port", Number.class, 4445);
dumpMethod(className, methodName, filter, host, port.intValue());
} catch (Exception ex) {
throw new javax.management.ReflectionException(ex);
}
}
return null;
}
private static <T> T param(Object[] arr, int index, String name, Class<T> type, T defaultValue) {
Object value = arr.length > index ? arr[index] : null;
if (value == null || (value instanceof String && ((String) value).isEmpty())) {
if (defaultValue == null) {
throw new IllegalArgumentException(name + " must be specified");
}
value = defaultValue;
}
if (type.isInstance(value)) {
return type.cast(value);
}
throw new IllegalArgumentException("Expecting " + type.getName() + " for " + name + " but was " + value);
}
public void dumpMethod(String className, String methodName, String filter, String host, int port) throws javax.management.MBeanException {
String jvmName = MetaUtil.toInternalName(className);
methodDumps.add(new Dump(host, port, jvmName, methodName, filter));
ClassNotFoundException last = null;
EconomicSet<Class<?>> found = EconomicSet.create();
Iterator<Reference<ClassLoader>> it = loaders.iterator();
while (it.hasNext()) {
Reference<ClassLoader> ref = it.next();
ClassLoader loader = ref.get();
if (loader == null) {
it.remove();
continue;
}
try {
Class<?> clazz = Class.forName(className, false, loader);
if (found.add(clazz)) {
ResolvedJavaType type = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaType(clazz);
if (compiler != null) {
for (ResolvedJavaMethod method : type.getDeclaredMethods()) {
if (methodName.equals(method.getName()) && method instanceof HotSpotResolvedJavaMethod) {
HotSpotResolvedJavaMethod hotSpotMethod = (HotSpotResolvedJavaMethod) method;
compiler.compileMethod(new HotSpotCompilationRequest(hotSpotMethod, -1, 0L), false);
}
}
}
}
} catch (ClassNotFoundException ex) {
last = ex;
}
}
if (found.isEmpty()) {
throw new javax.management.MBeanException(last, "Cannot find class " + className + " to schedule recompilation");
}
}
@Override
public javax.management.MBeanInfo getMBeanInfo() {
List<javax.management.MBeanAttributeInfo> attrs = new ArrayList<>();
for (OptionDescriptor descr : allOptionDescriptors()) {
attrs.add(new javax.management.MBeanAttributeInfo(descr.getName(), descr.getType().getName(), descr.getHelp(), true, true, false));
}
javax.management.MBeanOperationInfo[] ops = {
new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
}, "void", javax.management.MBeanOperationInfo.ACTION),
new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
new javax.management.MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
}, "void", javax.management.MBeanOperationInfo.ACTION),
new javax.management.MBeanOperationInfo("dumpMethod", "Enable IGV dumps for provided method", new javax.management.MBeanParameterInfo[]{
new javax.management.MBeanParameterInfo("className", "java.lang.String", "Class to observe"),
new javax.management.MBeanParameterInfo("methodName", "java.lang.String", "Method to observe"),
new javax.management.MBeanParameterInfo("filter", "java.lang.String", "The parameter for Dump option"),
new javax.management.MBeanParameterInfo("host", "java.lang.String", "The host where the IGV tool is running at"),
new javax.management.MBeanParameterInfo("port", "int", "The port where the IGV tool is listening at"),
}, "void", javax.management.MBeanOperationInfo.ACTION)
};
return new javax.management.MBeanInfo(
HotSpotGraalMBean.class.getName(),
"Graal",
attrs.toArray(new javax.management.MBeanAttributeInfo[attrs.size()]),
null, ops, null);
}
private static Iterable<OptionDescriptor> allOptionDescriptors() {
List<OptionDescriptor> arr = new ArrayList<>();
for (OptionDescriptors set : OptionsParser.getOptionsLoader()) {
for (OptionDescriptor descr : set) {
arr.add(descr);
}
}
return arr;
}
private static final class Dump {
final String host;
final int port;
final String clazz;
final String method;
final String filter;
Dump(String host, int port, String clazz, String method, String filter) {
this.host = host;
this.port = port;
this.clazz = clazz;
this.method = method;
this.filter = filter;
}
}
private static final class RefEquivalence extends Equivalence {
static final Equivalence INSTANCE = new RefEquivalence();
private RefEquivalence() {
}
@Override
public boolean equals(Object a, Object b) {
Reference<?> refA = (Reference<?>) a;
Reference<?> refB = (Reference<?>) b;
return Objects.equals(refA.get(), refB.get());
}
@Override
public int hashCode(Object o) {
Reference<?> ref = (Reference<?>) o;
Object obj = ref.get();
return obj == null ? 0 : obj.hashCode();
}
}
}