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 */
23package org.graalvm.compiler.hotspot;
24
25import java.lang.ref.Reference;
26import java.lang.ref.WeakReference;
27import java.lang.reflect.Field;
28import java.util.ArrayList;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Objects;
32import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
33import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
34import jdk.vm.ci.hotspot.HotSpotResolvedJavaType;
35import jdk.vm.ci.hotspot.HotSpotResolvedObjectType;
36import jdk.vm.ci.meta.MetaUtil;
37import jdk.vm.ci.meta.ResolvedJavaMethod;
38import jdk.vm.ci.meta.ResolvedJavaType;
39import jdk.vm.ci.runtime.JVMCI;
40
41import org.graalvm.compiler.debug.DebugOptions;
42import org.graalvm.compiler.options.OptionDescriptor;
43import org.graalvm.compiler.options.OptionDescriptors;
44import org.graalvm.compiler.options.OptionKey;
45import org.graalvm.compiler.options.OptionValues;
46import org.graalvm.compiler.options.OptionsParser;
47import org.graalvm.util.EconomicMap;
48import org.graalvm.util.EconomicSet;
49import org.graalvm.util.Equivalence;
50import org.graalvm.util.UnmodifiableEconomicMap;
51
52public 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}
365