添加线程池内运行堆栈查看. (#23)

pull/84/head
chen.ma 3 years ago
parent 997ca84836
commit e7b81cf0e8

@ -0,0 +1,23 @@
package cn.hippo4j.common.api;
import cn.hippo4j.common.model.ThreadDetailStateInfo;
import java.util.List;
/**
* Get thread status in thread pool.
*
* @author chen.ma
* @date 2022/1/9 12:47
*/
public interface ThreadDetailState {
/**
* Get thread status in thread pool.
*
* @param threadPoolId
* @return
*/
List<ThreadDetailStateInfo> getThreadDetailStateInfo(String threadPoolId);
}

@ -0,0 +1,20 @@
package cn.hippo4j.common.function;
/**
* Matcher.
*
* @author chen.ma
* @date 2022/1/9 13:29
*/
@FunctionalInterface
public interface Matcher<T> {
/**
* Match.
*
* @param t
* @return
*/
boolean match(T t);
}

@ -0,0 +1,38 @@
package cn.hippo4j.common.model;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* Thread detail state info.
*
* @author chen.ma
* @date 2022/1/9 12:36
*/
@Data
@Accessors(chain = true)
public class ThreadDetailStateInfo {
/**
* threadId
*/
private Long threadId;
/**
* threadName
*/
private String threadName;
/**
* threadStatus
*/
private String threadStatus;
/**
* threadStack
*/
private List<String> threadStack;
}

@ -1,5 +1,7 @@
package cn.hippo4j.common.toolkit;
import cn.hippo4j.common.function.Matcher;
/**
* Array util.
*
@ -19,4 +21,24 @@ public class ArrayUtil {
return array == null || array.length == 0;
}
/**
* First match.
*
* @param matcher
* @param array
* @param <T>
* @return
*/
public static <T> T firstMatch(Matcher<T> matcher, T... array) {
if (!isEmpty(array)) {
for (final T val : array) {
if (matcher.match(val)) {
return val;
}
}
}
return null;
}
}

@ -1,5 +1,6 @@
package cn.hippo4j.common.toolkit;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -69,4 +70,44 @@ public class CollectionUtil {
return !isEmpty(map);
}
/**
* Is empty.
*
* @param Iterator
* @return
*/
public static boolean isEmpty(Iterator<?> Iterator) {
return null == Iterator || false == Iterator.hasNext();
}
/**
* Is not empty.
*
* @param Iterator
* @return
*/
public static boolean isNotEmpty(Iterator<?> Iterator) {
return !isEmpty(Iterator);
}
/**
* Is empty.
*
* @param collection
* @return
*/
public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty();
}
/**
* Is not empty.
*
* @param collection
* @return
*/
public static boolean isNotEmpty(Collection<?> collection) {
return !isEmpty(collection);
}
}

@ -0,0 +1,104 @@
package cn.hippo4j.common.toolkit;
import com.sun.xml.internal.ws.util.UtilException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Reflect util.
*
* @author chen.ma
* @date 2022/1/9 13:16
*/
public class ReflectUtil {
private static final Map<Class<?>, Field[]> FIELDS_CACHE = new ConcurrentHashMap();
public static Object getFieldValue(Object obj, String fieldName) throws UtilException {
if (null == obj || StringUtil.isBlank(fieldName)) {
return null;
}
Field field = getField(obj instanceof Class ? (Class<?>) obj : obj.getClass(), fieldName);
return getFieldValue(obj, field);
}
public static Object getFieldValue(Object obj, Field field) throws UtilException {
if (null == field) {
return null;
}
if (obj instanceof Class) {
obj = null;
}
setAccessible(field);
Object result;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
String exceptionMsg = String.format("IllegalAccess for %s.%s", field.getDeclaringClass(), field.getName());
throw new RuntimeException(exceptionMsg, e);
}
return result;
}
public static <T extends AccessibleObject> T setAccessible(T accessibleObject) {
if (null != accessibleObject && false == accessibleObject.isAccessible()) {
accessibleObject.setAccessible(true);
}
return accessibleObject;
}
public static Field getField(Class<?> beanClass, String name) throws SecurityException {
final Field[] fields = getFields(beanClass);
return ArrayUtil.firstMatch((field) -> name.equals(getFieldName(field)), fields);
}
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
Field[] allFields = FIELDS_CACHE.get(beanClass);
if (null != allFields) {
return allFields;
}
allFields = getFieldsDirectly(beanClass, true);
FIELDS_CACHE.put(beanClass, allFields);
return allFields;
}
public static Field[] getFieldsDirectly(Class<?> beanClass, boolean withSuperClassFields) throws SecurityException {
Assert.notNull(beanClass);
Field[] allFields = null;
Class<?> searchType = beanClass;
Field[] declaredFields;
while (searchType != null) {
declaredFields = searchType.getDeclaredFields();
if (null == allFields) {
allFields = declaredFields;
} else {
int length = allFields.length;
allFields = Arrays.copyOf(allFields, length + declaredFields.length);
for (int i = 1; i < declaredFields.length; i++) {
allFields[length + i] = declaredFields[i - 1];
}
}
searchType = withSuperClassFields ? searchType.getSuperclass() : null;
}
return allFields;
}
public static String getFieldName(Field field) {
if (null == field) {
return null;
}
return field.getName();
}
}

@ -1,5 +1,6 @@
package cn.hippo4j.starter.config;
import cn.hippo4j.common.api.ThreadDetailState;
import cn.hippo4j.common.config.ApplicationContextHolder;
import cn.hippo4j.starter.controller.PoolRunStateController;
import cn.hippo4j.starter.core.ConfigService;
@ -8,6 +9,7 @@ import cn.hippo4j.starter.core.ThreadPoolConfigService;
import cn.hippo4j.starter.core.ThreadPoolOperation;
import cn.hippo4j.starter.enable.MarkerConfiguration;
import cn.hippo4j.starter.event.ApplicationContentPostProcessor;
import cn.hippo4j.starter.handler.BaseThreadDetailStateHandler;
import cn.hippo4j.starter.handler.DynamicThreadPoolBannerHandler;
import cn.hippo4j.starter.handler.ThreadPoolRunStateHandler;
import cn.hippo4j.starter.monitor.ReportingEventExecutor;
@ -23,6 +25,7 @@ import cn.hutool.core.util.IdUtil;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@ -88,8 +91,15 @@ public class DynamicThreadPoolAutoConfiguration {
}
@Bean
public PoolRunStateController poolRunStateController(ThreadPoolRunStateHandler threadPoolRunStateHandler) {
return new PoolRunStateController(threadPoolRunStateHandler);
@ConditionalOnMissingBean(value = ThreadDetailState.class)
public ThreadDetailState baseThreadDetailStateHandler() {
return new BaseThreadDetailStateHandler();
}
@Bean
public PoolRunStateController poolRunStateController(ThreadPoolRunStateHandler threadPoolRunStateHandler,
ThreadDetailState threadDetailState) {
return new PoolRunStateController(threadPoolRunStateHandler, threadDetailState);
}
@Bean

@ -1,6 +1,8 @@
package cn.hippo4j.starter.controller;
import cn.hippo4j.common.api.ThreadDetailState;
import cn.hippo4j.common.model.PoolRunStateInfo;
import cn.hippo4j.common.model.ThreadDetailStateInfo;
import cn.hippo4j.common.web.base.Result;
import cn.hippo4j.common.web.base.Results;
import cn.hippo4j.starter.handler.ThreadPoolRunStateHandler;
@ -10,6 +12,8 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Pool run state controller.
*
@ -23,10 +27,18 @@ public class PoolRunStateController {
private final ThreadPoolRunStateHandler threadPoolRunStateHandler;
@GetMapping("/run/state/{tpId}")
public Result<PoolRunStateInfo> getPoolRunState(@PathVariable("tpId") String tpId) {
PoolRunStateInfo poolRunState = threadPoolRunStateHandler.getPoolRunState(tpId);
private final ThreadDetailState threadDetailState;
@GetMapping("/run/state/{threadPoolId}")
public Result<PoolRunStateInfo> getPoolRunState(@PathVariable("threadPoolId") String threadPoolId) {
PoolRunStateInfo poolRunState = threadPoolRunStateHandler.getPoolRunState(threadPoolId);
return Results.success(poolRunState);
}
@GetMapping("/run/thread/state/{threadPoolId}")
public Result<List<ThreadDetailStateInfo>> getThreadStateDetail(@PathVariable("threadPoolId") String threadPoolId) {
List<ThreadDetailStateInfo> detailStateInfo = threadDetailState.getThreadDetailStateInfo(threadPoolId);
return Results.success(detailStateInfo);
}
}

@ -0,0 +1,78 @@
package cn.hippo4j.starter.handler;
import cn.hippo4j.common.api.ThreadDetailState;
import cn.hippo4j.common.model.ThreadDetailStateInfo;
import cn.hippo4j.common.toolkit.CollectionUtil;
import cn.hippo4j.common.toolkit.ReflectUtil;
import cn.hippo4j.starter.core.GlobalThreadPoolManage;
import cn.hippo4j.starter.wrapper.DynamicThreadPoolWrapper;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Base thread detail state handler.
*
* <p>The Java 8 implementation is temporarily provided,
* {@link ThreadDetailState} interface can be customized.
*
* @author chen.ma
* @date 2022/1/9 13:01
*/
@Slf4j
public class BaseThreadDetailStateHandler implements ThreadDetailState {
private final String WORKERS = "workers";
private final String THREAD = "thread";
@Override
public List<ThreadDetailStateInfo> getThreadDetailStateInfo(String threadPoolId) {
DynamicThreadPoolWrapper poolWrapper = GlobalThreadPoolManage.getExecutorService(threadPoolId);
ThreadPoolExecutor executor = poolWrapper.getExecutor();
List<ThreadDetailStateInfo> resultThreadState = new ArrayList();
try {
// TODO: Should the object be copied deeply to avoid the destruction of the worker
HashSet<Object> workers = (HashSet<Object>) ReflectUtil.getFieldValue(executor, WORKERS);
if (CollectionUtil.isEmpty(workers)) {
return resultThreadState;
}
for (Object worker : workers) {
Thread thread;
try {
thread = (Thread) ReflectUtil.getFieldValue(worker, THREAD);
if (thread == null) {
continue;
}
} catch (Exception ex) {
continue;
}
long threadId = thread.getId();
String threadName = thread.getName();
String threadStatus = thread.getState().name();
StackTraceElement[] stackTrace = thread.getStackTrace();
List<String> stacks = new ArrayList(stackTrace.length);
for (int i = 0; i < stackTrace.length; i++) {
stacks.add(stackTrace[i].toString());
}
ThreadDetailStateInfo threadState = new ThreadDetailStateInfo();
threadState.setThreadId(threadId)
.setThreadName(threadName)
.setThreadStatus(threadStatus)
.setThreadStack(stacks);
resultThreadState.add(threadState);
}
} catch (Exception ex) {
log.error("Failed to get thread status.", ex);
}
return resultThreadState;
}
}
Loading…
Cancel
Save