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.io.Serializable;
21 import java.lang.management.ManagementFactory;
22 import java.lang.management.ThreadMXBean;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.Collections;
26 import java.util.List;
27
28 /**
29  * Informations sur un thread java, sans code html de présentation.
30  * L'état d'une instance est initialisé à son instanciation et non mutable;
31  * il est donc de fait thread-safe.
32  * Cet état est celui d'un thread java à un instant t.
33  * Les instances sont sérialisables pour pouvoir être transmises au serveur de collecte.
34  * @author Emeric Vernat
35  */

36 public class ThreadInformations implements Serializable {
37     private static final long serialVersionUID = 3604281253550723654L;
38     @SuppressWarnings("all")
39     private static final ThreadMXBean THREAD_BEAN = ManagementFactory.getThreadMXBean();
40     private static final boolean CPU_TIME_ENABLED = THREAD_BEAN.isThreadCpuTimeSupported()
41             && THREAD_BEAN.isThreadCpuTimeEnabled();
42     private static final Method THREAD_ALLOCATED_BYTES_METHOD = getThreadAllocatedBytesMethod();
43     private final String name;
44     private final long id;
45     private final int priority;
46     private final boolean daemon;
47     private final Thread.State state;
48     private final long cpuTimeMillis;
49     private final long userTimeMillis;
50     private final boolean deadlocked;
51     private final String globalThreadId;
52     @SuppressWarnings("all")
53     private final List<StackTraceElement> stackTrace;
54
55     @SuppressWarnings("all")
56     public ThreadInformations(Thread thread, List<StackTraceElement> stackTrace, long cpuTimeMillis,
57             long userTimeMillis, boolean deadlocked, String hostAddress) {
58         super();
59         assert thread != null;
60         assert stackTrace == null || stackTrace instanceof Serializable;
61
62         this.name = thread.getName();
63         this.id = thread.getId();
64         this.priority = thread.getPriority();
65         this.daemon = thread.isDaemon();
66         this.state = thread.getState();
67         this.stackTrace = stackTrace;
68         this.cpuTimeMillis = cpuTimeMillis;
69         this.userTimeMillis = userTimeMillis;
70         this.deadlocked = deadlocked;
71         this.globalThreadId = buildGlobalThreadId(thread, hostAddress);
72     }
73
74     public static long getCurrentThreadCpuTime() {
75         return getThreadCpuTime(Thread.currentThread().getId());
76     }
77
78     static long getThreadCpuTime(long threadId) {
79         if (CPU_TIME_ENABLED) {
80             // le coût de cette méthode se mesure à environ 0,6 microseconde
81             return THREAD_BEAN.getThreadCpuTime(threadId);
82         }
83         return 0;
84     }
85
86     public static long getCurrentThreadAllocatedBytes() {
87         return getThreadAllocatedBytes(Thread.currentThread().getId());
88     }
89
90     static long getThreadAllocatedBytes(long threadId) {
91         // quand disponible (pas en jdk 9+), l'appel par réflexion est de l'ordre de 0,10 microseconde
92         // au lieu de 0,45 microseconde pour l'appel par MBeans
93         if (THREAD_ALLOCATED_BYTES_METHOD != null) {
94             try {
95                 return (Long) THREAD_ALLOCATED_BYTES_METHOD.invoke(THREAD_BEAN, threadId);
96             } catch (final IllegalAccessException | InvocationTargetException e) {
97                 throw new IllegalArgumentException(e);
98             }
99         }
100         return MBeansAccessor.getThreadAllocatedBytes(threadId);
101     }
102
103     private static Method getThreadAllocatedBytesMethod() {
104         // en général, THREAD_BEAN instanceof com.sun.management.ThreadMXBean, sauf sur JVM tierces
105         try {
106             final Class<? extends ThreadMXBean> clazz = THREAD_BEAN.getClass();
107             final Method method = clazz.getMethod("getThreadAllocatedBytes"long.class);
108             if (method != null) {
109                 method.setAccessible(true);
110                 // on teste pour vérifier que la fonction est supportée et activée
111                 final Long bytes = (Long) method.invoke(THREAD_BEAN,
112                         Thread.currentThread().getId());
113                 if (bytes.longValue() != -1) {
114                     return method;
115                 }
116             }
117             return null;
118         } catch (final Exception e) {
119             // catch Exception pour java 9 car java.lang.reflect.InaccessibleObjectException:
120             // Unable to make public long com.sun.management.internal.HotSpotThreadImpl.getThreadAllocatedBytes(long) accessible:
121             // module jdk.management does not "exports com.sun.management.internal" to unnamed module
122             return null;
123         }
124     }
125
126     public String getName() {
127         return name;
128     }
129
130     public long getId() {
131         return id;
132     }
133
134     public int getPriority() {
135         return priority;
136     }
137
138     public boolean isDaemon() {
139         return daemon;
140     }
141
142     public Thread.State getState() {
143         return state;
144     }
145
146     public List<StackTraceElement> getStackTrace() {
147         if (stackTrace != null) {
148             return Collections.unmodifiableList(stackTrace);
149         }
150         return stackTrace;
151     }
152
153     public String getExecutedMethod() {
154         final List<StackTraceElement> trace = stackTrace;
155         if (trace != null && !trace.isEmpty()) {
156             return trace.get(0).toString();
157         }
158         return "";
159     }
160
161     public long getCpuTimeMillis() {
162         return cpuTimeMillis;
163     }
164
165     public long getUserTimeMillis() {
166         return userTimeMillis;
167     }
168
169     public boolean isDeadlocked() {
170         return deadlocked;
171     }
172
173     public String getGlobalThreadId() {
174         return globalThreadId;
175     }
176
177     private static String buildGlobalThreadId(Thread thread, String hostAddress) {
178         return PID.getPID() + '_' + hostAddress + '_' + thread.getId();
179     }
180
181     /** {@inheritDoc} */
182     @Override
183     public String toString() {
184         return getClass().getSimpleName() + "[id=" + getId() + ", name=" + getName() + ", daemon="
185                 + isDaemon() + ", priority=" + getPriority() + ", deadlocked=" + isDeadlocked()
186                 + ", state=" + getState() + ']';
187     }
188 }
189