1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.model;
19
20 import java.util.Collections;
21 import java.util.HashSet;
22 import java.util.Set;
23
24 import javax.management.JMException;
25 import javax.management.MBeanAttributeInfo;
26 import javax.management.MBeanInfo;
27 import javax.management.MBeanServer;
28 import javax.management.MalformedObjectNameException;
29 import javax.management.ObjectName;
30
31 /**
32  * Accesseurs pour des MBeans particuliers.
33  * @author Emeric Vernat
34  */

35 final class MBeansAccessor {
36     private static final MBeanServer MBEAN_SERVER = MBeans.getPlatformMBeanServer();
37     private static final Set<ObjectName> NIO_BUFFER_POOLS = getNioBufferPools();
38     private static final ObjectName OPERATING_SYSTEM = createObjectName(
39             "java.lang:type=OperatingSystem");
40     private static final Set<String> OPERATING_SYSTEM_ATTRIBUTES = getAttributesNames(
41             OPERATING_SYSTEM);
42     private static final ObjectName THREADING = createObjectName("java.lang:type=Threading");
43     private static final boolean MBEAN_ALLOCATED_BYTES_ENABLED = isMbeanAllocatedBytesEnabled();
44     private static final String[] THREAD_ALLOCATED_BYTES_SIGNATURE = { long.class.getName(), };
45
46     private MBeansAccessor() {
47         super();
48     }
49
50     static Set<ObjectName> getTomcatThreadPools() {
51         final Set<ObjectName> result = new HashSet<>(
52                 MBEAN_SERVER.queryNames(createObjectName("*:type=ThreadPool,*"), null));
53         // #843 Tomcat info is not available anymore
54         result.removeAll(MBEAN_SERVER.queryNames(
55                 createObjectName("*:type=ThreadPool,*,subType=SocketProperties"), null));
56         return result;
57     }
58
59     static Set<ObjectName> getTomcatGlobalRequestProcessors() {
60         return MBEAN_SERVER.queryNames(createObjectName("*:type=GlobalRequestProcessor,*"), null);
61     }
62
63     private static Set<ObjectName> getNioBufferPools() {
64         return MBEAN_SERVER.queryNames(createObjectName("java.nio:type=BufferPool,*"), null);
65     }
66
67     static long getUsedBufferMemory() {
68         if (NIO_BUFFER_POOLS.isEmpty()) {
69             return -1;
70         }
71         long result = 0;
72         try {
73             for (final ObjectName objectName : NIO_BUFFER_POOLS) {
74                 // adds direct and mapped buffers
75                 result += (Long) getAttribute(objectName, "MemoryUsed");
76             }
77         } catch (final JMException e) {
78             throw new IllegalStateException(e);
79         }
80         return result;
81     }
82
83     // depuis jdk 9 et le module jdk.management/com.sun.management.internal,
84     // on ne peut plus appeler par réflexion des getters d'OperatingSystemMXBean "Sun" :
85     // https://docs.oracle.com/javase/9/docs/api/com/sun/management/OperatingSystemMXBean.html
86     // (ManagementFactory.getOperatingSystemMXBean() instanceof com.sun.management.internal.OperatingSystemImpl en jdk 9)
87     // car java.lang.reflect.InaccessibleObjectException:
88     // Unable to make public long com.sun.management.internal.OperatingSystemImpl...... accessible:
89     //    module jdk.management does not "opens com.sun.management.internal" to unnamed module ....
90     // sauf si par chance, --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED en ligne de commande
91     // donc on appelle les attributs des MBeans équivalents qui sont visibles dans tous les cas
92     @SuppressWarnings("unchecked")
93     static <T> T getAttribute(ObjectName name, String attribute) throws JMException {
94         return (T) MBEAN_SERVER.getAttribute(name, attribute);
95     }
96
97     static long getLongFromOperatingSystem(String attribute) {
98         if (!OPERATING_SYSTEM_ATTRIBUTES.contains(attribute)) {
99             // si on demande un attribut qui n'existe pas dans cette JVM ou dans cet OS, alors -1
100             return -1L;
101         }
102         try {
103             return getAttribute(OPERATING_SYSTEM, attribute);
104         } catch (final JMException e) {
105             throw new IllegalStateException(e);
106         }
107     }
108
109     static double getDoubleFromOperatingSystem(String attribute) {
110         if (!OPERATING_SYSTEM_ATTRIBUTES.contains(attribute)) {
111             // si on demande un attribut qui n'existe pas dans cette JVM ou dans cet OS, alors -1
112             return -1D;
113         }
114         try {
115             return getAttribute(OPERATING_SYSTEM, attribute);
116         } catch (final JMException e) {
117             throw new IllegalStateException(e);
118         }
119     }
120
121     static long getThreadAllocatedBytes(long threadId) {
122         if (!MBEAN_ALLOCATED_BYTES_ENABLED) {
123             return -1L;
124         }
125         try {
126             return (Long) MBEAN_SERVER.invoke(THREADING, "getThreadAllocatedBytes",
127                     new Object[] { threadId }, THREAD_ALLOCATED_BYTES_SIGNATURE);
128         } catch (final JMException e) {
129             throw new IllegalStateException(e);
130         }
131     }
132
133     static Object invoke(ObjectName name, String operationName, Object[] params, Class<?>[] classes)
134             throws JMException {
135         assert name != null;
136         assert operationName != null;
137         assert params != null;
138         assert classes != null;
139         final String[] signature = new String[classes.length];
140         for (int i = 0; i < signature.length; i++) {
141             signature[i] = classes[i].getName();
142         }
143         return MBEAN_SERVER.invoke(name, operationName, params, signature);
144     }
145
146     private static ObjectName createObjectName(String name) {
147         try {
148             return new ObjectName(name);
149         } catch (final MalformedObjectNameException e) {
150             // ne peut pas arriver ici vu les valeurs utilisées pour name
151             throw new IllegalStateException(e);
152         }
153     }
154
155     private static Set<String> getAttributesNames(ObjectName name) {
156         try {
157             final Set<String> result = new HashSet<>();
158             final MBeanInfo mBeanInfo = MBEAN_SERVER.getMBeanInfo(name);
159             final MBeanAttributeInfo[] attributes = mBeanInfo.getAttributes();
160             for (final MBeanAttributeInfo attribute : attributes) {
161                 if (attribute.isReadable()) {
162                     result.add(attribute.getName());
163                 }
164             }
165             return result;
166         } catch (final JMException e) {
167             return Collections.emptySet();
168         }
169     }
170
171     private static boolean isMbeanAllocatedBytesEnabled() {
172         if (getAttributesNames(THREADING).contains("ThreadAllocatedMemoryEnabled")) {
173             try {
174                 final boolean supported = getAttribute(THREADING, "ThreadAllocatedMemorySupported");
175                 if (supported) {
176                     return getAttribute(THREADING, "ThreadAllocatedMemoryEnabled");
177                 }
178             } catch (final JMException ex) {
179                 throw new IllegalStateException(ex);
180             }
181         }
182         return false;
183     }
184 }
185