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.LockInfo; 019import java.lang.management.ManagementFactory; 020import java.lang.management.MonitorInfo; 021import java.lang.management.ThreadMXBean; 022import java.time.ZonedDateTime; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030import java.util.stream.Collectors; 031 032import org.apache.avalon.framework.component.Component; 033 034import org.ametys.core.ui.Callable; 035import org.ametys.core.util.DateUtils; 036 037/** 038 * Component used by the admin thread tool 039 */ 040public class ThreadInfo implements Component 041{ 042 /** Avalon role */ 043 public static final String ROLE = ThreadInfo.class.getName(); 044 045 private Pattern _groupPattern = Pattern.compile("^(.*)-\\d+$"); 046 047 /** 048 * Create a threaddump 049 * @return The new threaddump 050 */ 051 @Callable(rights = "REPOSITORY_Rights_Access", context = "/admin") 052 public Map<String, Object> getThreadDump() 053 { 054 StringBuffer threadDump = new StringBuffer (System.lineSeparator ()); 055 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean (); 056 for (java.lang.management.ThreadInfo threadInfo : threadMXBean.dumpAllThreads (true, true)) 057 { 058 threadDump.append (_threadInfoToString(threadInfo)); 059 } 060 return Map.of( 061 "date", DateUtils.zonedDateTimeToString(ZonedDateTime.now()), 062 "data", threadDump.toString().replaceAll("\r", "") 063 ); 064 } 065 066 /** 067 * Dump all JVM threads. 068 * @return a JSON-oriented thread dump 069 */ 070 @Callable(rights = "REPOSITORY_Rights_Access", context = "/admin") 071 public List<Map<String, Object>> dumpAllThreads() 072 { 073 List<Map<String, Object>> result = new ArrayList<>(); 074 075 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 076 java.lang.management.ThreadInfo[] threads = threadMXBean.dumpAllThreads(true, true); 077 078 for (java.lang.management.ThreadInfo thread : threads) 079 { 080 Map<String, Object> info = new HashMap<>(); 081 082 String name = thread.getThreadName(); 083 String group = "-"; 084 Matcher matcher = _groupPattern.matcher(name); 085 if (matcher.matches()) 086 { 087 group = matcher.group(1); 088 } 089 090 info.put("name", name); 091 info.put("group", group); 092 info.put("state", thread.getThreadState()); 093 info.put("cpu", threadMXBean.getThreadCpuTime(thread.getThreadId())); 094 info.put("blocked-time", thread.getBlockedTime()); 095 info.put("waited-time", thread.getWaitedTime()); 096 info.put("lock-owner", thread.getLockOwnerName()); 097 098 String trace = Arrays.stream(thread.getStackTrace()) 099 .map(el -> _printStackTraceElement(el)) 100 .collect(Collectors.joining("\n")); 101 info.put("stack-trace", trace); 102 info.put("stack-ametys", Arrays.stream(thread.getStackTrace()).anyMatch(el -> el.getClassName().contains("org.ametys"))); 103 info.put("stack-size", thread.getStackTrace().length); 104 105 result.add(info); 106 } 107 108 return result; 109 } 110 111 private String _printStackTraceElement(StackTraceElement el) 112 { 113 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)"))); 114 } 115 116 // Duplicated from java.lang.management.ThreadInfo#toString removing the MAX_FRAMES limit 117 private String _threadInfoToString(java.lang.management.ThreadInfo threadInfo) 118 { 119 StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" + (threadInfo.isDaemon() ? " daemon" : "") + " prio=" + threadInfo.getPriority() + " Id=" + threadInfo.getThreadId() + " " + threadInfo.getThreadState()); 120 if (threadInfo.getLockName() != null) 121 { 122 sb.append(" on " + threadInfo.getLockName()); 123 } 124 if (threadInfo.getLockOwnerName() != null) 125 { 126 sb.append(" owned by \"" + threadInfo.getLockOwnerName() + "\" Id=" + threadInfo.getLockOwnerId()); 127 } 128 if (threadInfo.isSuspended()) 129 { 130 sb.append(" (suspended)"); 131 } 132 if (threadInfo.isInNative()) 133 { 134 sb.append(" (in native)"); 135 } 136 sb.append('\n'); 137 138 StackTraceElement[] stackTrace = threadInfo.getStackTrace(); 139 140 int i = 0; 141 for (; i < stackTrace.length; i++) 142 { 143 StackTraceElement ste = stackTrace[i]; 144 sb.append("\tat " + ste.toString()); 145 sb.append('\n'); 146 if (i == 0 && threadInfo.getLockInfo() != null) 147 { 148 Thread.State ts = threadInfo.getThreadState(); 149 switch (ts) 150 { 151 case BLOCKED: 152 sb.append("\t- blocked on " + threadInfo.getLockInfo()); 153 sb.append('\n'); 154 break; 155 case WAITING: 156 sb.append("\t- waiting on " + threadInfo.getLockInfo()); 157 sb.append('\n'); 158 break; 159 case TIMED_WAITING: 160 sb.append("\t- waiting on " + threadInfo.getLockInfo()); 161 sb.append('\n'); 162 break; 163 default: 164 } 165 } 166 167 for (MonitorInfo mi : threadInfo.getLockedMonitors()) 168 { 169 if (mi.getLockedStackDepth() == i) 170 { 171 sb.append("\t- locked " + mi); 172 sb.append('\n'); 173 } 174 } 175 } 176 177 LockInfo[] locks = threadInfo.getLockedSynchronizers(); 178 if (locks.length > 0) 179 { 180 sb.append("\n\tNumber of locked synchronizers = " + locks.length); 181 sb.append('\n'); 182 for (LockInfo li : locks) 183 { 184 sb.append("\t- " + li); 185 sb.append('\n'); 186 } 187 } 188 sb.append('\n'); 189 return sb.toString(); 190 } 191}