mirror of
https://gitee.com/arthas/arthas.git
synced 2024-12-02 04:08:34 +08:00
add arthas-grpc-services, support ObjectService
This commit is contained in:
parent
8d9af06592
commit
e5f2050f33
145
arthas-grpc-services/pom.xml
Normal file
145
arthas-grpc-services/pom.xml
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>arthas-all</artifactId>
|
||||||
|
<groupId>com.taobao.arthas</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>arthas-grpc-services</artifactId>
|
||||||
|
<name>arthas-grpc-services</name>
|
||||||
|
<url>https://github.com/alibaba/arthas</url>
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<grpc.version>1.46.0</grpc.version>
|
||||||
|
</properties>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-bom</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.taobao.arthas</groupId>
|
||||||
|
<artifactId>arthas-common</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.taobao.arthas</groupId>
|
||||||
|
<artifactId>arthas-vmtool</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-codec-http</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.arthas</groupId>
|
||||||
|
<artifactId>arthas-repackage-logger</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-services</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.annotation</groupId>
|
||||||
|
<artifactId>javax.annotation-api</artifactId>
|
||||||
|
<version>1.3.2</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpmime</artifactId>
|
||||||
|
<version>4.5.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>mac</id>
|
||||||
|
<activation>
|
||||||
|
<os>
|
||||||
|
<family>mac</family>
|
||||||
|
</os>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<os.detected.classifier>osx-x86_64</os.detected.classifier>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>kr.motd.maven</groupId>
|
||||||
|
<artifactId>os-maven-plugin</artifactId>
|
||||||
|
<version>1.6.2</version>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.xolstice.maven.plugins</groupId>
|
||||||
|
<artifactId>protobuf-maven-plugin</artifactId>
|
||||||
|
<version>0.6.1</version>
|
||||||
|
<configuration>
|
||||||
|
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
|
||||||
|
<protocArtifact>com.google.protobuf:protoc:3.11.0:exe:${os.detected.classifier}</protocArtifact>
|
||||||
|
<pluginId>grpc-java</pluginId>
|
||||||
|
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.28.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
<goal>compile-custom</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,198 @@
|
|||||||
|
package io.arthas.services;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import io.arthas.api.ArthasServices.ArrayElement;
|
||||||
|
import io.arthas.api.ArthasServices.ArrayValue;
|
||||||
|
import io.arthas.api.ArthasServices.BasicValue;
|
||||||
|
import io.arthas.api.ArthasServices.CollectionValue;
|
||||||
|
import io.arthas.api.ArthasServices.CollectionValue.Builder;
|
||||||
|
import io.arthas.api.ArthasServices.JavaField;
|
||||||
|
import io.arthas.api.ArthasServices.JavaFields;
|
||||||
|
import io.arthas.api.ArthasServices.JavaObject;
|
||||||
|
import io.arthas.api.ArthasServices.MapEntry;
|
||||||
|
import io.arthas.api.ArthasServices.MapValue;
|
||||||
|
import io.arthas.api.ArthasServices.NullValue;
|
||||||
|
import io.arthas.api.ArthasServices.UnexpandedObject;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class JavaObjectConverter {
|
||||||
|
private static final int MAX_DEPTH = 5;
|
||||||
|
|
||||||
|
public static JavaObject toJavaObject(Object obj) {
|
||||||
|
return toJavaObject(obj, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JavaObject toJavaObject(Object obj, int depth) {
|
||||||
|
if (depth >= MAX_DEPTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null) {
|
||||||
|
return JavaObject.newBuilder().setNullValue(NullValue.getDefaultInstance()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaObject.Builder objectBuilder = JavaObject.newBuilder();
|
||||||
|
Class<? extends Object> objClazz = obj.getClass();
|
||||||
|
objectBuilder.setClassName(objClazz.getName());
|
||||||
|
|
||||||
|
// 基础类型
|
||||||
|
if (isBasicType(objClazz)) {
|
||||||
|
return objectBuilder.setBasicValue(createBasicValue(obj)).build();
|
||||||
|
} else if (obj instanceof Collection) { // 集合
|
||||||
|
return objectBuilder.setCollection(createCollectionValue((Collection<?>) obj, depth)).build();
|
||||||
|
} else if (obj instanceof Map) { // map
|
||||||
|
return objectBuilder.setMap(createMapValue((Map<?, ?>) obj, depth)).build();
|
||||||
|
} else if (objClazz.isArray()) {
|
||||||
|
return objectBuilder.setArrayValue(toArrayValue(obj, depth)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Field[] fields = objClazz.getDeclaredFields();
|
||||||
|
List<JavaField> javaFields = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Field field : fields) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
JavaField.Builder fieldBuilder = JavaField.newBuilder();
|
||||||
|
fieldBuilder.setName(field.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object fieldValue = field.get(obj);
|
||||||
|
Class<?> fieldType = field.getType();
|
||||||
|
|
||||||
|
if (fieldValue == null) {
|
||||||
|
fieldBuilder.setNullValue(NullValue.newBuilder().setClassName(fieldType.getName()).build());
|
||||||
|
} else if (fieldType.isArray()) {
|
||||||
|
ArrayValue arrayValue = toArrayValue(fieldValue, depth + 1);
|
||||||
|
if (arrayValue != null) {
|
||||||
|
fieldBuilder.setArrayValue(arrayValue);
|
||||||
|
} else {
|
||||||
|
fieldBuilder.setUnexpandedObject(
|
||||||
|
UnexpandedObject.newBuilder().setClassName(fieldType.getName()).build());
|
||||||
|
}
|
||||||
|
} else if (fieldType.isPrimitive() || isBasicType(fieldType)) {
|
||||||
|
BasicValue basicValue = createBasicValue(fieldValue);
|
||||||
|
fieldBuilder.setBasicValue(basicValue);
|
||||||
|
} else if (fieldValue instanceof Collection) { // 集合
|
||||||
|
fieldBuilder.setCollection(createCollectionValue((Collection<?>) fieldValue, depth));
|
||||||
|
} else if (fieldValue instanceof Map) { // map
|
||||||
|
fieldBuilder.setMap(createMapValue((Map<?, ?>) fieldValue, depth));
|
||||||
|
} else {
|
||||||
|
JavaObject nestedObject = toJavaObject(fieldValue, depth + 1);
|
||||||
|
if (nestedObject != null) {
|
||||||
|
fieldBuilder.setObjectValue(nestedObject);
|
||||||
|
} else {
|
||||||
|
fieldBuilder.setUnexpandedObject(
|
||||||
|
UnexpandedObject.newBuilder().setClassName(fieldType.getName()).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// TODO ignore ?
|
||||||
|
}
|
||||||
|
javaFields.add(fieldBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
objectBuilder.setFields(JavaFields.newBuilder().addAllFields(javaFields).build());
|
||||||
|
return objectBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArrayValue toArrayValue(Object array, int depth) {
|
||||||
|
if (array == null || depth >= MAX_DEPTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayValue.Builder arrayBuilder = ArrayValue.newBuilder();
|
||||||
|
Class<?> componentType = array.getClass().getComponentType();
|
||||||
|
|
||||||
|
arrayBuilder.setClassName(componentType.getName());
|
||||||
|
|
||||||
|
int length = Array.getLength(array);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Object element = Array.get(array, i);
|
||||||
|
|
||||||
|
if (element != null) {
|
||||||
|
if (componentType.isArray()) {
|
||||||
|
ArrayValue nestedArrayValue = toArrayValue(element, depth + 1);
|
||||||
|
if (nestedArrayValue == null) {
|
||||||
|
arrayBuilder.addElements(ArrayElement.newBuilder().setArrayValue(nestedArrayValue));
|
||||||
|
} else {
|
||||||
|
arrayBuilder.addElements(ArrayElement.newBuilder().setUnexpandedObject(
|
||||||
|
UnexpandedObject.newBuilder().setClassName(element.getClass().getName()).build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (componentType.isPrimitive() || isBasicType(componentType)) {
|
||||||
|
BasicValue basicValue = createBasicValue(element);
|
||||||
|
arrayBuilder.addElements(ArrayElement.newBuilder().setBasicValue(basicValue));
|
||||||
|
} else {
|
||||||
|
JavaObject nestedObject = toJavaObject(element, depth + 1);
|
||||||
|
if (nestedObject != null) {
|
||||||
|
arrayBuilder.addElements(ArrayElement.newBuilder().setObjectValue(nestedObject));
|
||||||
|
} else {
|
||||||
|
arrayBuilder.addElements(ArrayElement.newBuilder().setUnexpandedObject(
|
||||||
|
UnexpandedObject.newBuilder().setClassName(element.getClass().getName()).build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arrayBuilder.addElements(ArrayElement.newBuilder()
|
||||||
|
.setNullValue(NullValue.newBuilder().setClassName(componentType.getName()).build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrayBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MapValue createMapValue(Map<?, ?> map, int depth) {
|
||||||
|
MapValue.Builder builder = MapValue.newBuilder();
|
||||||
|
|
||||||
|
for (Entry<?, ?> entry : map.entrySet()) {
|
||||||
|
MapEntry mapEntry = MapEntry.newBuilder().setKey(toJavaObject(entry.getKey(), depth))
|
||||||
|
.setValue(toJavaObject(entry.getValue(), depth)).build();
|
||||||
|
builder.addEntries(mapEntry);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CollectionValue createCollectionValue(Collection<?> collection, int depth) {
|
||||||
|
Builder builder = CollectionValue.newBuilder();
|
||||||
|
for (Object o : collection) {
|
||||||
|
builder.addElements(toJavaObject(o, depth));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BasicValue createBasicValue(Object value) {
|
||||||
|
BasicValue.Builder builder = BasicValue.newBuilder();
|
||||||
|
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
builder.setInt((int) value);
|
||||||
|
} else if (value instanceof Long) {
|
||||||
|
builder.setLong((long) value);
|
||||||
|
} else if (value instanceof Float) {
|
||||||
|
builder.setFloat((float) value);
|
||||||
|
} else if (value instanceof Double) {
|
||||||
|
builder.setDouble((double) value);
|
||||||
|
} else if (value instanceof Boolean) {
|
||||||
|
builder.setBoolean((boolean) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
builder.setString((String) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBasicType(Class<?> clazz) {
|
||||||
|
if (String.class.equals(clazz) || Integer.class.equals(clazz) || Long.class.equals(clazz)
|
||||||
|
|| Float.class.equals(clazz) || Double.class.equals(clazz) || Boolean.class.equals(clazz)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package io.arthas.services;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.taobao.arthas.common.VmToolUtils;
|
||||||
|
|
||||||
|
import arthas.VmTool;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
import io.arthas.api.ObjectServiceGrpc.ObjectServiceImplBase;
|
||||||
|
import io.arthas.api.ArthasServices.JavaObject;
|
||||||
|
import io.arthas.api.ArthasServices.ObjectQuery;
|
||||||
|
import io.arthas.api.ArthasServices.ObjectQueryResult;
|
||||||
|
import io.arthas.api.ArthasServices.ObjectQueryResult.Builder;
|
||||||
|
|
||||||
|
public class ObjectServiceImpl extends ObjectServiceImplBase {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
|
||||||
|
|
||||||
|
private VmTool vmTool;
|
||||||
|
private Instrumentation inst;
|
||||||
|
|
||||||
|
public ObjectServiceImpl(Instrumentation inst, String libDir) {
|
||||||
|
this.inst = inst;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String detectLibName = VmToolUtils.detectLibName();
|
||||||
|
String vmToolLibPath = Paths.get(libDir, detectLibName).toString();
|
||||||
|
|
||||||
|
logger.info("vmtool lib path: {}", vmToolLibPath);
|
||||||
|
|
||||||
|
vmTool = VmTool.getInstance(vmToolLibPath);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("init vmtool error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void query(ObjectQuery query, StreamObserver<ObjectQueryResult> responseObserver) {
|
||||||
|
if (vmTool == null) {
|
||||||
|
throw Status.UNAVAILABLE.withDescription("vmtool can not work").asRuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
String className = query.getClassName();
|
||||||
|
String classLoaderHash = query.getClassLoaderHash();
|
||||||
|
String classLoaderClass = query.getClassLoaderClass();
|
||||||
|
int limit = query.getLimit();
|
||||||
|
int depth = query.getDepth();
|
||||||
|
|
||||||
|
// 如果只传递了 class name 参数,则jvm 里可能有多个同名的 class,需要全部查找
|
||||||
|
if (isEmpty(classLoaderHash) && isEmpty(classLoaderClass)) {
|
||||||
|
List<Class<?>> foundClassList = new ArrayList<>();
|
||||||
|
for (Class<?> clazz : inst.getAllLoadedClasses()) {
|
||||||
|
if (clazz.getName().equals(className)) {
|
||||||
|
foundClassList.add(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没找到
|
||||||
|
if (foundClassList.size() == 0) {
|
||||||
|
responseObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false)
|
||||||
|
.setMessage("can not find class: " + className).build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
return;
|
||||||
|
} else if (foundClassList.size() > 1) {
|
||||||
|
String message = "found more than one class: " + className;
|
||||||
|
responseObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false).setMessage(message).build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
return;
|
||||||
|
} else { // 找到了指定的 类
|
||||||
|
Object[] instances = vmTool.getInstances(foundClassList.get(0), limit);
|
||||||
|
Builder builder = ObjectQueryResult.newBuilder().setSuccess(true);
|
||||||
|
for (Object obj : instances) {
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(obj, depth);
|
||||||
|
builder.addObjects(javaObject);
|
||||||
|
}
|
||||||
|
responseObserver.onNext(builder.build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有指定 classloader hash 或者 classloader className
|
||||||
|
|
||||||
|
Class<?> foundClass = null;
|
||||||
|
|
||||||
|
for (Class<?> clazz : inst.getAllLoadedClasses()) {
|
||||||
|
if (!clazz.getName().equals(className)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader classLoader = clazz.getClassLoader();
|
||||||
|
|
||||||
|
if (classLoader == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmpty(classLoaderHash)) {
|
||||||
|
String hex = Integer.toHexString(classLoader.hashCode());
|
||||||
|
if (classLoaderHash.equals(hex)) {
|
||||||
|
foundClass = clazz;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmpty(classLoaderClass) && classLoaderClass.equals(classLoader.getClass().getName())) {
|
||||||
|
foundClass = clazz;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 没找到类
|
||||||
|
if (foundClass == null) {
|
||||||
|
responseObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false)
|
||||||
|
.setMessage("can not find class: " + className).build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] instances = vmTool.getInstances(foundClass, limit);
|
||||||
|
Builder builder = ObjectQueryResult.newBuilder().setSuccess(true);
|
||||||
|
for (Object obj : instances) {
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(obj, depth);
|
||||||
|
builder.addObjects(javaObject);
|
||||||
|
}
|
||||||
|
responseObserver.onNext(builder.build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEmpty(Object str) {
|
||||||
|
return str == null || "".equals(str);
|
||||||
|
}
|
||||||
|
}
|
106
arthas-grpc-services/src/main/proto/ArthasServices.proto
Normal file
106
arthas-grpc-services/src/main/proto/ArthasServices.proto
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package io.arthas.api;
|
||||||
|
|
||||||
|
message BasicValue {
|
||||||
|
oneof value {
|
||||||
|
int32 int = 1;
|
||||||
|
int64 long = 2;
|
||||||
|
float float = 3;
|
||||||
|
double double = 4;
|
||||||
|
bool boolean = 5;
|
||||||
|
string string = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ArrayElement {
|
||||||
|
oneof element {
|
||||||
|
BasicValue basicValue = 1;
|
||||||
|
JavaObject objectValue = 2;
|
||||||
|
ArrayValue arrayValue = 3;
|
||||||
|
NullValue nullValue = 4;
|
||||||
|
UnexpandedObject unexpandedObject = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ArrayValue {
|
||||||
|
string className = 1;
|
||||||
|
repeated ArrayElement elements = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NullValue {
|
||||||
|
string className = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UnexpandedObject {
|
||||||
|
string className = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CollectionValue {
|
||||||
|
string className = 1;
|
||||||
|
repeated JavaObject elements = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MapEntry {
|
||||||
|
JavaObject key = 1;
|
||||||
|
JavaObject value = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MapValue {
|
||||||
|
string className = 1;
|
||||||
|
repeated MapEntry entries = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message JavaField {
|
||||||
|
string name = 1;
|
||||||
|
|
||||||
|
oneof value {
|
||||||
|
JavaObject objectValue = 2;
|
||||||
|
BasicValue basicValue = 3;
|
||||||
|
ArrayValue arrayValue = 4;
|
||||||
|
NullValue nullValue = 5;
|
||||||
|
CollectionValue collection = 6;
|
||||||
|
MapValue map = 7;
|
||||||
|
UnexpandedObject unexpandedObject = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message JavaFields {
|
||||||
|
repeated JavaField fields = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message JavaObject {
|
||||||
|
string className = 1;
|
||||||
|
|
||||||
|
oneof value {
|
||||||
|
JavaObject objectValue = 2;
|
||||||
|
BasicValue basicValue = 3;
|
||||||
|
ArrayValue arrayValue = 4;
|
||||||
|
NullValue nullValue = 5;
|
||||||
|
CollectionValue collection = 6;
|
||||||
|
MapValue map = 7;
|
||||||
|
UnexpandedObject unexpandedObject = 8;
|
||||||
|
JavaFields fields = 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ObjectQuery {
|
||||||
|
string className = 1;
|
||||||
|
string express = 2;
|
||||||
|
string ClassLoaderHash = 3;
|
||||||
|
string classLoaderClass = 4;
|
||||||
|
int32 limit = 5;
|
||||||
|
int32 depth = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ObjectQueryResult {
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
repeated JavaObject objects = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
service ObjectService {
|
||||||
|
rpc query(ObjectQuery) returns (ObjectQueryResult);
|
||||||
|
}
|
@ -0,0 +1,301 @@
|
|||||||
|
package io.arthas.services;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import io.arthas.api.ArthasServices.ArrayElement;
|
||||||
|
import io.arthas.api.ArthasServices.ArrayValue;
|
||||||
|
import io.arthas.api.ArthasServices.BasicValue;
|
||||||
|
import io.arthas.api.ArthasServices.CollectionValue;
|
||||||
|
import io.arthas.api.ArthasServices.JavaField;
|
||||||
|
import io.arthas.api.ArthasServices.JavaFields;
|
||||||
|
import io.arthas.api.ArthasServices.JavaObject;
|
||||||
|
import io.arthas.api.ArthasServices.MapEntry;
|
||||||
|
import io.arthas.api.ArthasServices.MapValue;
|
||||||
|
import io.arthas.api.ArthasServices.NullValue;
|
||||||
|
|
||||||
|
public class JavaObjectConverterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testString() {
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject("sss");
|
||||||
|
System.err.println(javaObject);
|
||||||
|
assertNotNull(javaObject);
|
||||||
|
assertEquals("java.lang.String", javaObject.getClassName());
|
||||||
|
assertTrue(javaObject.hasBasicValue());
|
||||||
|
assertEquals("sss", javaObject.getBasicValue().getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithBasicType() {
|
||||||
|
int intValue = 123;
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(intValue);
|
||||||
|
assertNotNull(javaObject);
|
||||||
|
assertEquals("java.lang.Integer", javaObject.getClassName());
|
||||||
|
assertTrue(javaObject.hasBasicValue());
|
||||||
|
assertEquals(intValue, javaObject.getBasicValue().getInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithArray() {
|
||||||
|
int[] intArray = { 1, 2, 3 };
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(intArray);
|
||||||
|
assertNotNull(javaObject);
|
||||||
|
assertEquals("[I", javaObject.getClassName());
|
||||||
|
assertTrue(javaObject.hasArrayValue());
|
||||||
|
ArrayValue arrayValue = javaObject.getArrayValue();
|
||||||
|
assertNotNull(arrayValue);
|
||||||
|
assertEquals("int", arrayValue.getClassName());
|
||||||
|
assertEquals(3, arrayValue.getElementsCount());
|
||||||
|
|
||||||
|
ArrayElement element = arrayValue.getElements(1);
|
||||||
|
assertEquals(2, element.getBasicValue().getInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithCollection() {
|
||||||
|
List<String> stringList = new ArrayList<>();
|
||||||
|
stringList.add("foo");
|
||||||
|
stringList.add("bar");
|
||||||
|
stringList.add("baz");
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(stringList);
|
||||||
|
assertNotNull(javaObject);
|
||||||
|
assertEquals("java.util.ArrayList", javaObject.getClassName());
|
||||||
|
assertTrue(javaObject.hasCollection());
|
||||||
|
CollectionValue collectionValue = javaObject.getCollection();
|
||||||
|
assertNotNull(collectionValue);
|
||||||
|
assertEquals(3, collectionValue.getElementsCount());
|
||||||
|
|
||||||
|
JavaObject object3 = collectionValue.getElements(2);
|
||||||
|
assertEquals("baz", object3.getBasicValue().getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithMap() {
|
||||||
|
Map<String, Integer> stringIntegerMap = new HashMap<>();
|
||||||
|
stringIntegerMap.put("one", 1);
|
||||||
|
stringIntegerMap.put("two", 2);
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(stringIntegerMap);
|
||||||
|
assertNotNull(javaObject);
|
||||||
|
assertEquals("java.util.HashMap", javaObject.getClassName());
|
||||||
|
assertTrue(javaObject.hasMap());
|
||||||
|
MapValue mapValue = javaObject.getMap();
|
||||||
|
assertNotNull(mapValue);
|
||||||
|
assertEquals(2, mapValue.getEntriesCount());
|
||||||
|
|
||||||
|
MapEntry mapEntry = mapValue.getEntries(0);
|
||||||
|
|
||||||
|
JavaObject key = mapEntry.getKey();
|
||||||
|
assertEquals("one", key.getBasicValue().getString());
|
||||||
|
JavaObject value = mapEntry.getValue();
|
||||||
|
assertEquals(1, value.getBasicValue().getInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObject() {
|
||||||
|
// 创建一个复杂的 Object
|
||||||
|
ComplexObject complexObject = createComplexObject();
|
||||||
|
|
||||||
|
// 转换为 JavaObject
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(complexObject);
|
||||||
|
|
||||||
|
// 对转换后的 JavaObject 进行断言,验证各个 field 的值是否一致
|
||||||
|
Assert.assertNotNull(javaObject);
|
||||||
|
Assert.assertEquals(ComplexObject.class.getName(), javaObject.getClassName());
|
||||||
|
|
||||||
|
JavaFields fields = javaObject.getFields();
|
||||||
|
|
||||||
|
Map<String, JavaField> fieldMap = fields.getFieldsList().stream()
|
||||||
|
.collect(Collectors.toMap(JavaField::getName, field -> field));
|
||||||
|
|
||||||
|
// 验证基础类型字段
|
||||||
|
BasicValue basicValue = fieldMap.get("basicValue").getBasicValue();
|
||||||
|
Assert.assertEquals(5, basicValue.getInt());
|
||||||
|
|
||||||
|
// 验证集合字段
|
||||||
|
JavaField collection = fieldMap.get("collection");
|
||||||
|
CollectionValue collectionValue = collection.getCollection();
|
||||||
|
|
||||||
|
Assert.assertEquals(2, collectionValue.getElementsCount());
|
||||||
|
|
||||||
|
// 验证数组字段
|
||||||
|
JavaField array = fieldMap.get("arrayValue");
|
||||||
|
ArrayValue arrayValue = array.getArrayValue();
|
||||||
|
Assert.assertEquals(2, arrayValue.getElementsCount());
|
||||||
|
|
||||||
|
// 验证嵌套对象字段
|
||||||
|
JavaField nestedObject = fieldMap.get("nestedObject");
|
||||||
|
JavaObject nestedJavaObject = nestedObject.getObjectValue();
|
||||||
|
JavaFields nestedObjectFields = nestedJavaObject.getFields();
|
||||||
|
Assert.assertEquals(1, nestedObjectFields.getFieldsCount());
|
||||||
|
JavaField nestedObjectField = nestedObjectFields.getFields(0);
|
||||||
|
Assert.assertEquals("stringValue", nestedObjectField.getName());
|
||||||
|
Assert.assertEquals("nestedValue", nestedObjectField.getBasicValue().getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ComplexObject createComplexObject() {
|
||||||
|
ComplexObject complexObject = new ComplexObject();
|
||||||
|
complexObject.setBasicValue(5);
|
||||||
|
complexObject.setCollection(Arrays.asList("element1", "element2"));
|
||||||
|
complexObject.setArrayValue(new int[] { 1, 2 });
|
||||||
|
complexObject.setNestedObject(new NestedObject("nestedValue"));
|
||||||
|
return complexObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ComplexObject {
|
||||||
|
private int basicValue;
|
||||||
|
private Collection<String> collection;
|
||||||
|
private int[] arrayValue;
|
||||||
|
private NestedObject nestedObject;
|
||||||
|
|
||||||
|
public int getBasicValue() {
|
||||||
|
return basicValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBasicValue(int basicValue) {
|
||||||
|
this.basicValue = basicValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getCollection() {
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollection(Collection<String> collection) {
|
||||||
|
this.collection = collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getArrayValue() {
|
||||||
|
return arrayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArrayValue(int[] arrayValue) {
|
||||||
|
this.arrayValue = arrayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestedObject getNestedObject() {
|
||||||
|
return nestedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNestedObject(NestedObject nestedObject) {
|
||||||
|
this.nestedObject = nestedObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NestedObject {
|
||||||
|
private String stringValue;
|
||||||
|
|
||||||
|
public NestedObject(String stringValue) {
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringValue() {
|
||||||
|
return stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringValue(String stringValue) {
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestObject {
|
||||||
|
private Double[] doubleArray;
|
||||||
|
|
||||||
|
public Double[] getDoubleArray() {
|
||||||
|
return doubleArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDoubleArray(Double[] doubleArray) {
|
||||||
|
this.doubleArray = doubleArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObjectWithDubboArrayField() {
|
||||||
|
// 创建测试对象
|
||||||
|
TestObject testObject = new TestObject();
|
||||||
|
testObject.setDoubleArray(new Double[] { 1.0, 2.0, 3.0 });
|
||||||
|
|
||||||
|
// 转换为JavaObject
|
||||||
|
JavaObject javaObject = JavaObjectConverter.toJavaObject(testObject);
|
||||||
|
|
||||||
|
// 检查各个field的值是否一致
|
||||||
|
for (int i = 0; i < testObject.getDoubleArray().length; i++) {
|
||||||
|
Double expectedValue = testObject.getDoubleArray()[i];
|
||||||
|
ArrayValue arrayValue = javaObject.getFields().getFields(0).getArrayValue();
|
||||||
|
ArrayElement arrayElement = arrayValue.getElements(i);
|
||||||
|
Double actualValue = arrayElement.getBasicValue().getDouble();
|
||||||
|
Assert.assertEquals(expectedValue, actualValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithNullValue() {
|
||||||
|
JavaObject result = JavaObjectConverter.toJavaObject(null);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.hasNullValue());
|
||||||
|
assertEquals(NullValue.getDefaultInstance(), result.getNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithNullKeyInMap() {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put(null, "value");
|
||||||
|
|
||||||
|
JavaObject result = JavaObjectConverter.toJavaObject(map);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.hasMap());
|
||||||
|
MapValue mapValue = result.getMap();
|
||||||
|
assertEquals(1, mapValue.getEntriesCount());
|
||||||
|
|
||||||
|
MapEntry entry = mapValue.getEntries(0);
|
||||||
|
assertNotNull(entry.getKey());
|
||||||
|
assertTrue(entry.getKey().hasNullValue());
|
||||||
|
assertEquals(NullValue.getDefaultInstance(), entry.getKey().getNullValue());
|
||||||
|
|
||||||
|
assertNotNull(entry.getValue());
|
||||||
|
assertTrue(entry.getValue().hasBasicValue());
|
||||||
|
|
||||||
|
assertEquals("value", entry.getValue().getBasicValue().getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJavaObjectWithNullValueInArray() {
|
||||||
|
Object[] array = new Object[3];
|
||||||
|
array[0] = "value";
|
||||||
|
array[1] = null;
|
||||||
|
array[2] = 123;
|
||||||
|
|
||||||
|
JavaObject result = JavaObjectConverter.toJavaObject(array);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.hasArrayValue());
|
||||||
|
ArrayValue arrayValue = result.getArrayValue();
|
||||||
|
assertEquals(3, arrayValue.getElementsCount());
|
||||||
|
|
||||||
|
ArrayElement element1 = arrayValue.getElements(0);
|
||||||
|
assertTrue(element1.hasObjectValue());
|
||||||
|
JavaObject objectValue1 = element1.getObjectValue();
|
||||||
|
assertTrue(objectValue1.hasBasicValue());
|
||||||
|
assertEquals("value", objectValue1.getBasicValue().getString());
|
||||||
|
|
||||||
|
ArrayElement element2 = arrayValue.getElements(1);
|
||||||
|
assertNotNull(element2.getNullValue());
|
||||||
|
|
||||||
|
ArrayElement element3 = arrayValue.getElements(2);
|
||||||
|
assertTrue(element3.hasObjectValue());
|
||||||
|
JavaObject objectValue3 = element3.getObjectValue();
|
||||||
|
assertTrue(objectValue3.hasBasicValue());
|
||||||
|
assertEquals(123, objectValue3.getBasicValue().getInt());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user