mirror of
https://gitee.com/arthas/arthas.git
synced 2024-12-01 19:58:30 +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