1
2
3
4
5
6
7
8
9
10
11 package psiprobe.beans;
12
13 import com.maxmind.db.CHMCache;
14 import com.maxmind.geoip2.DatabaseReader;
15 import com.maxmind.geoip2.exception.AddressNotFoundException;
16 import com.maxmind.geoip2.model.CountryResponse;
17 import com.maxmind.geoip2.record.Country;
18
19 import jakarta.inject.Inject;
20
21 import java.net.InetAddress;
22 import java.nio.file.Path;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Set;
29
30 import javax.management.InstanceNotFoundException;
31 import javax.management.MBeanServer;
32 import javax.management.MBeanServerNotification;
33 import javax.management.MalformedObjectNameException;
34 import javax.management.Notification;
35 import javax.management.NotificationListener;
36 import javax.management.ObjectInstance;
37 import javax.management.ObjectName;
38 import javax.management.RuntimeOperationsException;
39
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import psiprobe.model.Connector;
44 import psiprobe.model.RequestProcessor;
45 import psiprobe.model.ThreadPool;
46 import psiprobe.model.jmx.ThreadPoolObjectName;
47 import psiprobe.tools.JmxTools;
48
49
50
51
52
53 public class ContainerListenerBean implements NotificationListener {
54
55
56 private static final Logger logger = LoggerFactory.getLogger(ContainerListenerBean.class);
57
58
59 private final Set<String> allowedOperation =
60 new HashSet<>(Arrays.asList("start", "stop", "pause", "resume"));
61
62
63 private List<ThreadPoolObjectName> poolNames;
64
65
66 private List<ObjectName> executorNames;
67
68
69 @Inject
70 private ContainerWrapperBean containerWrapper;
71
72
73
74
75
76
77 private boolean isInitialized() {
78 return poolNames != null && !poolNames.isEmpty();
79 }
80
81
82
83
84
85
86
87
88 private ThreadPoolObjectName findPool(String name) {
89 if (name != null && isInitialized()) {
90 for (ThreadPoolObjectName threadPoolObjectName : poolNames) {
91 if (name.equals(threadPoolObjectName.getThreadPoolName().getKeyProperty("name"))) {
92 return threadPoolObjectName;
93 }
94 }
95 }
96 return null;
97 }
98
99
100
101
102
103
104
105 @Override
106 public synchronized void handleNotification(Notification notification, Object object) {
107 if (!(notification instanceof MBeanServerNotification)) {
108 return;
109 }
110
111 if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(notification.getType())
112 || MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals(notification.getType())) {
113
114 ObjectName objectName = ((MBeanServerNotification) notification).getMBeanName();
115 if ("RequestProcessor".equals(objectName.getKeyProperty("type"))) {
116 ThreadPoolObjectName threadPoolObjectName = findPool(objectName.getKeyProperty("worker"));
117 if (threadPoolObjectName != null) {
118 if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(notification.getType())) {
119 threadPoolObjectName.getRequestProcessorNames().add(objectName);
120 } else {
121 threadPoolObjectName.getRequestProcessorNames().remove(objectName);
122 }
123 }
124 }
125 }
126 }
127
128
129
130
131
132
133
134
135 private synchronized void initialize()
136 throws MalformedObjectNameException, InstanceNotFoundException {
137
138 MBeanServer server = containerWrapper.getResourceResolver().getMBeanServer();
139 String serverName = containerWrapper.getTomcatContainer().getName();
140 Set<ObjectInstance> threadPools =
141 server.queryMBeans(new ObjectName(serverName + ":type=ThreadPool,name=\"*\""), null);
142 poolNames = new ArrayList<>(threadPools.size());
143 for (ObjectInstance threadPool : threadPools) {
144
145 ThreadPoolObjectName threadPoolObjectName = new ThreadPoolObjectName();
146 ObjectName threadPoolName = threadPool.getObjectName();
147
148 String name = threadPoolName.getKeyProperty("name");
149
150 threadPoolObjectName.setThreadPoolName(threadPoolName);
151 ObjectName grpName = server
152 .getObjectInstance(new ObjectName(
153 threadPoolName.getDomain() + ":type=GlobalRequestProcessor,name=" + name))
154 .getObjectName();
155 threadPoolObjectName.setGlobalRequestProcessorName(grpName);
156
157
158
159
160
161 Set<ObjectInstance> workers = server.queryMBeans(
162 new ObjectName(threadPoolName.getDomain() + ":type=RequestProcessor,*"), null);
163
164 for (ObjectInstance worker : workers) {
165 ObjectName wrkName = worker.getObjectName();
166 if (name.equals(wrkName.getKeyProperty("worker"))) {
167 threadPoolObjectName.getRequestProcessorNames().add(wrkName);
168 }
169 }
170
171 poolNames.add(threadPoolObjectName);
172 }
173
174 Set<ObjectInstance> executors =
175 server.queryMBeans(new ObjectName(serverName + ":type=Executor,*"), null);
176 executorNames = new ArrayList<>(executors.size());
177 for (ObjectInstance executor : executors) {
178 ObjectName executorName = executor.getObjectName();
179 executorNames.add(executorName);
180 }
181
182
183 server.addNotificationListener(new ObjectName("JMImplementation:type=MBeanServerDelegate"),
184 this, null, null);
185
186 }
187
188
189
190
191
192
193
194
195 public synchronized List<ThreadPool> getThreadPools() throws Exception {
196 if (!isInitialized()) {
197 initialize();
198 }
199
200 List<ThreadPool> threadPools = new ArrayList<>(poolNames.size());
201
202 MBeanServer server = containerWrapper.getResourceResolver().getMBeanServer();
203
204 for (ObjectName executorName : executorNames) {
205 ThreadPool threadPool = new ThreadPool();
206 threadPool.setName(executorName.getKeyProperty("name"));
207 threadPool.setMaxThreads(JmxTools.getIntAttr(server, executorName, "maxThreads"));
208 threadPool.setMaxSpareThreads(JmxTools.getIntAttr(server, executorName, "largestPoolSize"));
209 threadPool.setMinSpareThreads(JmxTools.getIntAttr(server, executorName, "minSpareThreads"));
210 threadPool.setCurrentThreadsBusy(JmxTools.getIntAttr(server, executorName, "activeCount"));
211 threadPool.setCurrentThreadCount(JmxTools.getIntAttr(server, executorName, "poolSize"));
212 threadPools.add(threadPool);
213 }
214
215 for (ThreadPoolObjectName threadPoolObjectName : poolNames) {
216 ObjectName poolName = threadPoolObjectName.getThreadPoolName();
217
218 ThreadPool threadPool = new ThreadPool();
219 threadPool.setName(poolName.getKeyProperty("name"));
220 threadPool.setMaxThreads(JmxTools.getIntAttr(server, poolName, "maxThreads"));
221
222 if (JmxTools.hasAttribute(server, poolName, "maxSpareThreads")) {
223 threadPool.setMaxSpareThreads(JmxTools.getIntAttr(server, poolName, "maxSpareThreads"));
224 threadPool.setMinSpareThreads(JmxTools.getIntAttr(server, poolName, "minSpareThreads"));
225 }
226
227 threadPool.setCurrentThreadsBusy(JmxTools.getIntAttr(server, poolName, "currentThreadsBusy"));
228 threadPool.setCurrentThreadCount(JmxTools.getIntAttr(server, poolName, "currentThreadCount"));
229
230
231
232
233
234 if (threadPool.getMaxThreads() > -1) {
235 threadPools.add(threadPool);
236 }
237 }
238 return threadPools;
239 }
240
241
242
243
244
245
246
247
248
249 public synchronized void toggleConnectorStatus(String operation, String port) throws Exception {
250
251 if (!allowedOperation.contains(operation)) {
252 logger.error("operation {} not supported", operation);
253 throw new IllegalArgumentException("Not support operation");
254 }
255
256 ObjectName objectName = new ObjectName("Catalina:type=Connector,port=" + port);
257
258 MBeanServer server = containerWrapper.getResourceResolver().getMBeanServer();
259
260 JmxTools.invoke(server, objectName, operation, null, null);
261
262 logger.info("operation {} on Connector {} invoked success", operation, objectName);
263 }
264
265
266
267
268
269
270
271
272
273
274 public synchronized List<Connector> getConnectors(boolean includeRequestProcessors)
275 throws Exception {
276
277 boolean workerThreadNameSupported = true;
278
279 if (!isInitialized()) {
280 initialize();
281 }
282
283 List<Connector> connectors = new ArrayList<>(poolNames.size());
284
285 MBeanServer server = containerWrapper.getResourceResolver().getMBeanServer();
286
287 for (ThreadPoolObjectName threadPoolObjectName : poolNames) {
288 ObjectName poolName = threadPoolObjectName.getThreadPoolName();
289
290 Connector connector = new Connector();
291
292 String name = poolName.getKeyProperty("name");
293
294 connector.setProtocolHandler(poolName.getKeyProperty("name"));
295
296 if (name.startsWith("\"") && name.endsWith("\"")) {
297 name = name.substring(1, name.length() - 1);
298 }
299
300 String[] arr = name.split("-", -1);
301 String port = "-1";
302 if (arr.length == 3) {
303 port = arr[2];
304 }
305
306 if (!"-1".equals(port)) {
307 String str = "Catalina:type=Connector,port=" + port;
308
309 ObjectName objectName = new ObjectName(str);
310
311
312 connector.setStatus(JmxTools.getStringAttr(server, objectName, "stateName"));
313 connector.setProtocol(JmxTools.getStringAttr(server, objectName, "protocol"));
314 connector
315 .setSecure(Boolean.parseBoolean(JmxTools.getStringAttr(server, objectName, "secure")));
316 connector.setPort(JmxTools.getIntAttr(server, objectName, "port"));
317 connector.setLocalPort(JmxTools.getIntAttr(server, objectName, "localPort"));
318 connector.setSchema(JmxTools.getStringAttr(server, objectName, "schema"));
319 }
320
321 ObjectName grpName = threadPoolObjectName.getGlobalRequestProcessorName();
322
323 connector.setMaxTime(JmxTools.getLongAttr(server, grpName, "maxTime"));
324 connector.setProcessingTime(JmxTools.getLongAttr(server, grpName, "processingTime"));
325 connector.setBytesReceived(JmxTools.getLongAttr(server, grpName, "bytesReceived"));
326 connector.setBytesSent(JmxTools.getLongAttr(server, grpName, "bytesSent"));
327 connector.setRequestCount(JmxTools.getIntAttr(server, grpName, "requestCount"));
328 connector.setErrorCount(JmxTools.getIntAttr(server, grpName, "errorCount"));
329
330 if (includeRequestProcessors) {
331 List<ObjectName> wrkNames = threadPoolObjectName.getRequestProcessorNames();
332 for (ObjectName wrkName : wrkNames) {
333 RequestProcessor rp = new RequestProcessor();
334 rp.setName(wrkName.getKeyProperty("name"));
335 rp.setStage(JmxTools.getIntAttr(server, wrkName, "stage"));
336 rp.setProcessingTime(JmxTools.getLongAttr(server, wrkName, "requestProcessingTime"));
337 rp.setBytesSent(JmxTools.getLongAttr(server, wrkName, "requestBytesSent"));
338 rp.setBytesReceived(JmxTools.getLongAttr(server, wrkName, "requestBytesReceived"));
339 try {
340 rp.setRemoteAddr(JmxTools.getStringAttr(server, wrkName, "remoteAddr"));
341 } catch (RuntimeOperationsException ex) {
342 logger.trace("", ex);
343 }
344
345 if (rp.getRemoteAddr() != null) {
346
347 if (InetAddress.getByName(rp.getRemoteAddr()).isLoopbackAddress()) {
348 rp.setRemoteAddrLocale(new Locale(System.getProperty("user.language"),
349 System.getProperty("user.country")));
350 } else {
351
352 try (DatabaseReader reader = new DatabaseReader.Builder(
353 Path.of(getClass().getClassLoader().getResource("GeoLite2-Country.mmdb").toURI())
354 .toFile())
355 .withCache(new CHMCache()).build()) {
356 CountryResponse response =
357 reader.country(InetAddress.getByName(rp.getRemoteAddr()));
358 Country country = response.country();
359 rp.setRemoteAddrLocale(new Locale("", country.isoCode()));
360 } catch (AddressNotFoundException e) {
361 logger.debug("Address Not Found: {}", e.getMessage());
362 logger.trace("", e);
363 }
364 }
365 }
366
367 rp.setVirtualHost(JmxTools.getStringAttr(server, wrkName, "virtualHost"));
368 rp.setMethod(JmxTools.getStringAttr(server, wrkName, "method"));
369 rp.setCurrentUri(JmxTools.getStringAttr(server, wrkName, "currentUri"));
370 rp.setCurrentQueryString(JmxTools.getStringAttr(server, wrkName, "currentQueryString"));
371 rp.setProtocol(JmxTools.getStringAttr(server, wrkName, "protocol"));
372
373
374 if (workerThreadNameSupported
375 && JmxTools.hasAttribute(server, wrkName, "workerThreadName")) {
376
377 rp.setWorkerThreadName(JmxTools.getStringAttr(server, wrkName, "workerThreadName"));
378 rp.setWorkerThreadNameSupported(true);
379 } else {
380
381
382
383
384
385 rp.setWorkerThreadNameSupported(false);
386 workerThreadNameSupported = false;
387 }
388 connector.addRequestProcessor(rp);
389 }
390 }
391
392 connectors.add(connector);
393 }
394 return connectors;
395 }
396
397 }