001/* 002 * Copyright 2021 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.plugins.admin.jvmstatus; 017 018import java.lang.management.ManagementFactory; 019import java.lang.management.ThreadMXBean; 020import java.time.ZonedDateTime; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028import java.util.stream.Collectors; 029 030import org.apache.avalon.framework.component.Component; 031 032import org.ametys.core.ui.Callable; 033import org.ametys.core.util.DateUtils; 034 035/** 036 * Component used by the admin thread tool 037 */ 038public class ThreadInfo implements Component 039{ 040 /** Avalon role */ 041 public static final String ROLE = ThreadInfo.class.getName(); 042 043 private Pattern _groupPattern = Pattern.compile("^(.*)-\\d+$"); 044 045 /** 046 * Create a threaddump 047 * @return The new threaddump 048 */ 049 @Callable(rights = "REPOSITORY_Rights_Access", context = "/admin") 050 public Map<String, Object> getThreadDump() 051 { 052 StringBuffer threadDump = new StringBuffer (System.lineSeparator ()); 053 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean (); 054 for (java.lang.management.ThreadInfo threadInfo : threadMXBean.dumpAllThreads (true, true)) 055 { 056 threadDump.append (threadInfo.toString ()); 057 } 058 return Map.of( 059 "date", DateUtils.zonedDateTimeToString(ZonedDateTime.now()), 060 "data", threadDump.toString().replaceAll("\r", "") 061 ); 062 } 063 064 /** 065 * Dump all JVM threads. 066 * @return a JSON-oriented thread dump 067 */ 068 @Callable(rights = "REPOSITORY_Rights_Access", context = "/admin") 069 public List<Map<String, Object>> dumpAllThreads() 070 { 071 List<Map<String, Object>> result = new ArrayList<>(); 072 073 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 074 java.lang.management.ThreadInfo[] threads = threadMXBean.dumpAllThreads(true, true); 075 076 for (java.lang.management.ThreadInfo thread : threads) 077 { 078 Map<String, Object> info = new HashMap<>(); 079 080 String name = thread.getThreadName(); 081 String group = "-"; 082 Matcher matcher = _groupPattern.matcher(name); 083 if (matcher.matches()) 084 { 085 group = matcher.group(1); 086 } 087 088 info.put("name", name); 089 info.put("group", group); 090 info.put("state", thread.getThreadState()); 091 info.put("cpu", threadMXBean.getThreadCpuTime(thread.getThreadId())); 092 info.put("blocked-time", thread.getBlockedTime()); 093 info.put("waited-time", thread.getWaitedTime()); 094 info.put("lock-owner", thread.getLockOwnerName()); 095 096 String trace = Arrays.stream(thread.getStackTrace()) 097 .map(el -> _printStackTraceElement(el)) 098 .collect(Collectors.joining("\n")); 099 info.put("stack-trace", trace); 100 info.put("stack-ametys", Arrays.stream(thread.getStackTrace()).anyMatch(el -> el.getClassName().contains("org.ametys"))); 101 info.put("stack-size", thread.getStackTrace().length); 102 103 result.add(info); 104 } 105 106 return result; 107 } 108 109 private String _printStackTraceElement(StackTraceElement el) 110 { 111 return el.getClassName() + "." + el.getMethodName() + "(" + (el.isNativeMethod() ? "Native Method)" : (el.getFileName() != null && el.getLineNumber() >= 0 ? el.getFileName() + ":" + el.getLineNumber() + ")" : (el.getFileName() != null ? "" + el.getFileName() + ")" : "Unknown Source)"))); 112 } 113}