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