mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
arthas grpc service prototype (#2694)
This commit is contained in:
@@ -1,4 +1,36 @@
|
||||
## netty grpc web proxy
|
||||
# Arthas-grpc
|
||||
项目启动流程:
|
||||
|
||||
## 1. grpc-web代理服务配置
|
||||
1. 前端grpc-web请求ip和port配置: [配置文件](./ui/src/main.js)
|
||||
```js
|
||||
app.use(ViewUIPlus)
|
||||
.use(router)
|
||||
.provide("apiHost","http://localhost:8567")
|
||||
.mount('#app')
|
||||
```
|
||||
2. 后端端口配置: [配置文件](./src/main/java/com/taobao/arthas/grpcweb/grpc/DemoBootstrap.java), 修改``GRPC_WEB_PROXY_PORT``变量,即可配置grpc-web代理服务端口。<br><br>
|
||||
若需要配置grpc服务端口和http页面服务端口, 分别修改`GRPC_PORT`和`HTTP_PORT`即可<br><br>
|
||||
*注意, 前后端grpc-web代理服务端口需一致(默认使用端口号: 8567)
|
||||
## 2. 项目编译
|
||||
|
||||
```shell
|
||||
mvn compile
|
||||
```
|
||||
|
||||
## 3. 项目运行
|
||||
|
||||
启动 [com.taobao.arthas.grpcweb.grpc.DemoBootstrap](./src/main/java/com/taobao/arthas/grpcweb/grpc/DemoBootstrap.java)
|
||||
|
||||
## 4. 页面访问
|
||||
启动后,命令行终端会打印出访问地址
|
||||
```text
|
||||
Open your web browser and navigate to http://127.0.0.1:{http_port}/index.html
|
||||
```
|
||||
|
||||
# netty grpc web proxy
|
||||
|
||||
本项目中使用到的grpc-web代理服务
|
||||
|
||||
from: https://github.com/grpc/grpc-web/tree/1.4.2/src/connector
|
||||
|
||||
@@ -23,13 +55,13 @@ from: https://github.com/grpc/grpc-web/tree/1.4.2/src/connector
|
||||
|
||||
可以用其它的 grpc web proxy来抓包辅助验证。
|
||||
|
||||
### 用 envoy
|
||||
## 用 envoy
|
||||
|
||||
下载envoy 后,可以用本项目里的`envoy.yaml`
|
||||
|
||||
* `envoy --config-path ./envoy.yaml`
|
||||
|
||||
### 使用 grpcwebproxy
|
||||
## 使用 grpcwebproxy
|
||||
|
||||
* https://github.com/improbable-eng/grpc-web/blob/master/go/grpcwebproxy/README.md
|
||||
|
||||
|
||||
@@ -12,10 +12,18 @@
|
||||
<artifactId>arthas-grpc-web-proxy</artifactId>
|
||||
<name>arthas-grpc-web-proxy</name>
|
||||
<url>https://github.com/alibaba/arthas</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<grpc.version>1.46.0</grpc.version>
|
||||
<yarn.registry.url>https://registry.npmmirror.com/</yarn.registry.url>
|
||||
<yarn.download.url>http://npmmirror.com</yarn.download.url>
|
||||
<node.download.url>https://npmmirror.com/mirrors/node/</node.download.url>
|
||||
<node.version>v16.16.0</node.version>
|
||||
<yarn.version>v1.22.19</yarn.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -41,12 +49,14 @@
|
||||
<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>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy-agent</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
@@ -55,10 +65,19 @@
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>javax.activation-api</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zeroturnaround</groupId>
|
||||
<artifactId>zt-zip</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
@@ -89,11 +108,25 @@
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-common</artifactId>
|
||||
<artifactId>arthas-vmtool</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>math-game</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-spy</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -111,6 +144,7 @@
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<extensions>
|
||||
@@ -134,12 +168,98 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
<goal>test-compile-custom</goal>
|
||||
<goal>compile</goal>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<!-- Use the latest released version:
|
||||
https://repo1.maven.org/maven2/com/github/eirslett/frontend-maven-plugin/ -->
|
||||
<version>1.12.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- optional: you don't really need execution ids, but it looks nice in your build log. -->
|
||||
<id>install node and yarn</id>
|
||||
<goals>
|
||||
<goal>install-node-and-yarn</goal>
|
||||
</goals>
|
||||
<!-- optional: default phase is "generate-resources" -->
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>set registry</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<arguments>config set registry ${yarn.registry.url}</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>yarn install</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- optional: The default argument is actually
|
||||
"install", so unless you need to run some other yarn command,
|
||||
you can remove this whole <configuration> section.
|
||||
-->
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>run build</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run build</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<nodeVersion>${node.version}</nodeVersion>
|
||||
<yarnVersion>${yarn.version}</yarnVersion>
|
||||
|
||||
<!-- optional: where to download node from. Defaults to https://nodejs.org/dist/ -->
|
||||
<nodeDownloadRoot>${node.download.url}</nodeDownloadRoot>
|
||||
<!-- optional: where to download yarn from. Defaults to https://github.com/yarnpkg/yarn/releases/download/ -->
|
||||
<!-- <yarnDownloadRoot>${yarn.registry.url}</yarnDownloadRoot>-->
|
||||
<workingDirectory>ui</workingDirectory>
|
||||
|
||||
<installDirectory>target</installDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy dist</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>ui/dist</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<outputDirectory>${project.build.directory}/static</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
package com.taobao.arthas.grpcweb.grpc;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.SocketUtils;
|
||||
import com.taobao.arthas.core.advisor.TransformerManager;
|
||||
import com.taobao.arthas.grpcweb.grpc.objectUtils.ComplexObject;
|
||||
import com.taobao.arthas.grpcweb.grpc.server.GrpcServer;
|
||||
import com.taobao.arthas.grpcweb.grpc.server.httpServer.NettyHttpServer;
|
||||
import com.taobao.arthas.grpcweb.proxy.server.GrpcWebProxyServer;
|
||||
import demo.MathGame;
|
||||
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
|
||||
public class DemoBootstrap {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
|
||||
|
||||
private int GRPC_WEB_PROXY_PORT = 8567;
|
||||
|
||||
private int GRPC_PORT = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private int HTTP_PORT = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private Instrumentation instrumentation;
|
||||
|
||||
private TransformerManager transformerManager;
|
||||
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
|
||||
private static DemoBootstrap demoBootstrap;
|
||||
|
||||
|
||||
private DemoBootstrap() throws InterruptedException, IOException {
|
||||
ComplexObject ccc = createComplexObject();
|
||||
|
||||
// 0. 启动mathDemo
|
||||
Thread mathDemo = new Thread(() ->{
|
||||
MathGame game = new MathGame();
|
||||
while (true) {
|
||||
try {
|
||||
game.run();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
mathDemo.start();
|
||||
|
||||
// 1. 初始化相关参数,获取自身Inst
|
||||
instrumentation = ByteBuddyAgent.install();
|
||||
appendSpyJar(instrumentation);
|
||||
this.transformerManager = new TransformerManager(instrumentation);
|
||||
executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
final Thread t = new Thread(r, "grpc-service-execute");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
//2. 启动grpc、grpcweb proxy、 http服务器
|
||||
Thread allServerStartThread = new Thread("grpc-server-start"){
|
||||
@Override
|
||||
public void run(){
|
||||
try {
|
||||
serverStart();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
allServerStartThread.start();
|
||||
}
|
||||
|
||||
public void serverStart() throws IOException, InterruptedException {
|
||||
|
||||
// 0. 创建一个对象
|
||||
ComplexObject complexObject = createComplexObject();
|
||||
// 1. 启动grpc服务
|
||||
Thread grpcStartThread = new Thread(() -> {
|
||||
GrpcServer grpcServer = new GrpcServer(GRPC_PORT, instrumentation, transformerManager);
|
||||
grpcServer.start();
|
||||
try {
|
||||
System.in.read();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
grpcStartThread.start();
|
||||
|
||||
// 2. 启动grpc-web-proxy服务
|
||||
//this.GRPC_WEB_PROXY_PORT = SocketUtils.findAvailableTcpPort();
|
||||
Thread grpcWebProxyStartThread = new Thread(() -> {
|
||||
GrpcWebProxyServer grpcWebProxyServer = new GrpcWebProxyServer(GRPC_WEB_PROXY_PORT,GRPC_PORT);
|
||||
grpcWebProxyServer.start();
|
||||
});
|
||||
grpcWebProxyStartThread.start();
|
||||
|
||||
// 3. 启动http服务
|
||||
String currentDir = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()).getParentFile().getPath();
|
||||
String STATIC_LOCATION = Paths.get(currentDir, "static").toString();
|
||||
NettyHttpServer nettyHttpServer = new NettyHttpServer(HTTP_PORT,STATIC_LOCATION);
|
||||
logger.info("start grpc server on port: {}, grpc web proxy server on port: {}, " +
|
||||
"http server server on port: {}", GRPC_PORT,GRPC_WEB_PROXY_PORT,HTTP_PORT);
|
||||
System.out.println("Open your web browser and navigate to " + "http" + "://127.0.0.1:" + HTTP_PORT + '/' + "index.html");
|
||||
nettyHttpServer.start();
|
||||
}
|
||||
|
||||
public synchronized static DemoBootstrap getInstance() throws Throwable {
|
||||
if (demoBootstrap == null) {
|
||||
demoBootstrap = new DemoBootstrap();
|
||||
}
|
||||
return demoBootstrap;
|
||||
}
|
||||
|
||||
public static DemoBootstrap getRunningInstance() {
|
||||
if (demoBootstrap == null) {
|
||||
throw new IllegalStateException("AllServerStart must be initialized before!");
|
||||
}
|
||||
return demoBootstrap;
|
||||
}
|
||||
|
||||
public void execute(Runnable command) {
|
||||
executorService.execute(command);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void appendSpyJar(Instrumentation instrumentation) throws IOException {
|
||||
// find spy target/classes directory
|
||||
String file = DemoBootstrap.class.getProtectionDomain().getCodeSource().getLocation().getFile();
|
||||
|
||||
File spyClassDir = new File(file, "../../../spy/target/classes").getAbsoluteFile();
|
||||
|
||||
File destJarFile = new File(file, "../../../spy/target/test-spy.jar").getAbsoluteFile();
|
||||
|
||||
ZipUtil.pack(spyClassDir, destJarFile);
|
||||
|
||||
instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(destJarFile));
|
||||
|
||||
}
|
||||
|
||||
public static ComplexObject createComplexObject() {
|
||||
// 创建一个 ComplexObject 对象
|
||||
ComplexObject complexObject = new ComplexObject();
|
||||
|
||||
// 设置基本类型的值
|
||||
complexObject.setId(1);
|
||||
complexObject.setName("Complex Object");
|
||||
complexObject.setValue(3.14);
|
||||
|
||||
// 设置基本类型的数组
|
||||
int[] numbers = { 1, 2, 3, 4, 5 };
|
||||
complexObject.setNumbers(numbers);
|
||||
|
||||
Long[] longNumbers = {10086l,10087l,10088l,10089l,10090l,10091l};
|
||||
complexObject.setLongNumbers(longNumbers);
|
||||
|
||||
// 创建并设置嵌套对象
|
||||
ComplexObject.NestedObject nestedObject = new ComplexObject.NestedObject();
|
||||
nestedObject.setNestedId(10);
|
||||
nestedObject.setNestedName("Nested Object");
|
||||
nestedObject.setFlag(true);
|
||||
complexObject.setNestedObject(nestedObject);
|
||||
|
||||
|
||||
List<String> stringList = new ArrayList<>();
|
||||
stringList.add("foo");
|
||||
stringList.add("bar");
|
||||
stringList.add("baz");
|
||||
complexObject.setStringList(stringList);
|
||||
|
||||
Map<String, Integer> stringIntegerMap = new HashMap<>();
|
||||
stringIntegerMap.put("one", 1);
|
||||
stringIntegerMap.put("two", 2);
|
||||
complexObject.setStringIntegerMap(stringIntegerMap);
|
||||
|
||||
complexObject.setDoubleArray(new Double[] { 1.0, 2.0, 3.0 });
|
||||
|
||||
complexObject.setComplexArray(null);
|
||||
|
||||
complexObject.setCollection(Arrays.asList("element1", "element2"));
|
||||
|
||||
|
||||
// 创建并设置复杂对象数组
|
||||
ComplexObject[] complexArray = new ComplexObject[2];
|
||||
|
||||
ComplexObject complexObject1 = new ComplexObject();
|
||||
complexObject1.setId(2);
|
||||
complexObject1.setName("Complex Object 1");
|
||||
complexObject1.setValue(2.71);
|
||||
|
||||
ComplexObject complexObject2 = new ComplexObject();
|
||||
complexObject2.setId(3);
|
||||
complexObject2.setName("Complex Object 2");
|
||||
complexObject2.setValue(1.618);
|
||||
|
||||
complexArray[0] = complexObject1;
|
||||
complexArray[1] = complexObject2;
|
||||
|
||||
complexObject.setComplexArray(complexArray);
|
||||
|
||||
// 创建并设置多维数组
|
||||
int[][] multiDimensionalArray = { { 1, 2, 3 }, { 4, 5, 6 } };
|
||||
complexObject.setMultiDimensionalArray(multiDimensionalArray);
|
||||
|
||||
// 设置数组中的基本元素数组
|
||||
String[] stringArray = { "Hello", "World" };
|
||||
complexObject.setStringArray(stringArray);
|
||||
|
||||
// 输出 ComplexObject 对象的信息
|
||||
System.out.println(complexObject);
|
||||
|
||||
return complexObject;
|
||||
}
|
||||
|
||||
public Instrumentation getInstrumentation() {
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
public TransformerManager getTransformerManager() {
|
||||
return transformerManager;
|
||||
}
|
||||
|
||||
public ScheduledExecutorService getScheduledExecutorService() {
|
||||
return this.executorService;
|
||||
}
|
||||
public static void main(String[] args) throws Throwable {
|
||||
DemoBootstrap.getInstance();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.distribution;
|
||||
|
||||
import com.taobao.arthas.core.command.model.ResultModel;
|
||||
import com.taobao.arthas.core.distribution.ResultDistributor;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.view.GrpcResultView;
|
||||
import com.taobao.arthas.grpcweb.grpc.view.GrpcResultViewResolver;
|
||||
|
||||
|
||||
public class GrpcResultDistributorImpl implements ResultDistributor {
|
||||
|
||||
private final ArthasStreamObserver arthasStreamObserver;
|
||||
|
||||
private final GrpcResultViewResolver grpcResultViewResolver;
|
||||
|
||||
public GrpcResultDistributorImpl(ArthasStreamObserver arthasStreamObserver, GrpcResultViewResolver resultViewResolver) {
|
||||
this.arthasStreamObserver = arthasStreamObserver;
|
||||
this.grpcResultViewResolver = resultViewResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendResult(ResultModel model) {
|
||||
GrpcResultView resultView = grpcResultViewResolver.getResultView(model);
|
||||
if (resultView != null) {
|
||||
resultView.draw(arthasStreamObserver, model);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.model;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.advisor.InvokeTraceable;
|
||||
import com.taobao.arthas.core.command.model.EnhancerModel;
|
||||
import com.taobao.arthas.core.command.monitor200.AbstractTraceAdviceListener;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.view.Ansi;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.service.advisor.Enhancer;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class EnhancerRequestModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EnhancerRequestModel.class);
|
||||
protected static final List<String> EMPTY = Collections.emptyList();
|
||||
public static final String[] EXPRESS_EXAMPLES = { "params", "returnObj", "throwExp", "target", "clazz", "method",
|
||||
"{params,returnObj}", "params[0]" };
|
||||
protected String excludeClassPattern;
|
||||
|
||||
protected Matcher classNameMatcher;
|
||||
protected Matcher classNameExcludeMatcher;
|
||||
protected Matcher methodNameMatcher;
|
||||
|
||||
protected long jobId;
|
||||
protected long listenerId;
|
||||
|
||||
protected boolean verbose;
|
||||
|
||||
protected int maxNumOfMatchedClass;
|
||||
|
||||
/**
|
||||
* 类名匹配
|
||||
*
|
||||
* @return 获取类名匹配
|
||||
*/
|
||||
protected abstract Matcher getClassNameMatcher();
|
||||
|
||||
/**
|
||||
* 排除类名匹配
|
||||
*/
|
||||
protected abstract Matcher getClassNameExcludeMatcher();
|
||||
|
||||
/**
|
||||
* 方法名匹配
|
||||
*
|
||||
* @return 获取方法名匹配
|
||||
*/
|
||||
protected abstract Matcher getMethodNameMatcher();
|
||||
|
||||
/**
|
||||
* 获取监听器
|
||||
*
|
||||
* @return 返回监听器
|
||||
*/
|
||||
protected abstract AdviceListener getAdviceListener(ArthasStreamObserver arthasStreamObserver);
|
||||
|
||||
|
||||
public void enhance(ArthasStreamObserver arthasStreamObserver) {
|
||||
EnhancerAffect effect = null;
|
||||
try {
|
||||
Instrumentation inst = arthasStreamObserver.getInstrumentation();
|
||||
AdviceListener listener = getAdviceListener(arthasStreamObserver);
|
||||
if (listener == null) {
|
||||
logger.error("advice listener is null");
|
||||
String msg = "advice listener is null, check arthas log";
|
||||
// arthasStreamObserver.appendResult(new EnhancerModel(effect, false, msg));
|
||||
arthasStreamObserver.end(-1, msg);
|
||||
return;
|
||||
}
|
||||
boolean skipJDKTrace = false;
|
||||
if(listener instanceof AbstractTraceAdviceListener) {
|
||||
skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace();
|
||||
}
|
||||
|
||||
Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getClassNameExcludeMatcher(), getMethodNameMatcher());
|
||||
// 注册通知监听器
|
||||
arthasStreamObserver.register(listener, enhancer);
|
||||
effect = enhancer.enhance(inst, this.maxNumOfMatchedClass);
|
||||
if (effect.getThrowable() != null) {
|
||||
String msg = "error happens when enhancing class: "+effect.getThrowable().getMessage();
|
||||
// arthasStreamObserver.appendResult(new EnhancerModel(effect, false, msg));
|
||||
arthasStreamObserver.end(-1, msg + ", check arthas log: " + LogUtil.loggingFile());
|
||||
return;
|
||||
}
|
||||
|
||||
if (effect.cCnt() == 0 || effect.mCnt() == 0) {
|
||||
// no class effected
|
||||
if (!StringUtils.isEmpty(effect.getOverLimitMsg())) {
|
||||
String msg = "no class effected";
|
||||
// arthasStreamObserver.appendResult(new EnhancerModel(effect, false));
|
||||
arthasStreamObserver.end(-1, msg);
|
||||
return;
|
||||
}
|
||||
// might be method code too large
|
||||
// arthasStreamObserver.appendResult(new EnhancerModel(effect, false, "No class or method is affected"));
|
||||
|
||||
String smCommand = Ansi.ansi().fg(Ansi.Color.GREEN).a("sm CLASS_NAME METHOD_NAME").reset().toString();
|
||||
String optionsCommand = Ansi.ansi().fg(Ansi.Color.GREEN).a("options unsafe true").reset().toString();
|
||||
String javaPackage = Ansi.ansi().fg(Ansi.Color.GREEN).a("java.*").reset().toString();
|
||||
String resetCommand = Ansi.ansi().fg(Ansi.Color.GREEN).a("reset CLASS_NAME").reset().toString();
|
||||
String logStr = Ansi.ansi().fg(Ansi.Color.GREEN).a(LogUtil.loggingFile()).reset().toString();
|
||||
String issueStr = Ansi.ansi().fg(Ansi.Color.GREEN).a("https://github.com/alibaba/arthas/issues/47").reset().toString();
|
||||
String msg = "No class or method is affected, try:\n"
|
||||
+ "1. Execute `" + smCommand + "` to make sure the method you are tracing actually exists (it might be in your parent class).\n"
|
||||
+ "2. Execute `" + optionsCommand + "`, if you want to enhance the classes under the `" + javaPackage + "` package.\n"
|
||||
+ "3. Execute `" + resetCommand + "` and try again, your method body might be too large.\n"
|
||||
+ "4. Match the constructor, use `<init>`, for example: `watch demo.MathGame <init>`\n"
|
||||
+ "5. Check arthas log: " + logStr + "\n"
|
||||
+ "6. Visit " + issueStr + " for more details.";
|
||||
arthasStreamObserver.end(-1, msg);
|
||||
return;
|
||||
}
|
||||
arthasStreamObserver.appendResult(new EnhancerModel(effect, true));
|
||||
|
||||
//异步执行,在RpcAdviceListener中结束
|
||||
} catch (Throwable e) {
|
||||
String msg = "error happens when enhancing class: "+e.getMessage();
|
||||
logger.error(msg, e);
|
||||
// arthasStreamObserver.appendResult(new EnhancerModel(effect, false, msg));
|
||||
arthasStreamObserver.end(-1, msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.model;
|
||||
|
||||
import io.arthas.api.ArthasServices.WatchRequest;
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.advisor.AdviceWeaver;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.service.advisor.WatchRpcAdviceListener;
|
||||
|
||||
|
||||
public class WatchRequestModel extends EnhancerRequestModel {
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private String express;
|
||||
private String conditionExpress;
|
||||
private boolean isBefore = false;
|
||||
private boolean isFinish = false;
|
||||
private boolean isException = false;
|
||||
private boolean isSuccess = false;
|
||||
private Integer expand = 1;
|
||||
private Integer sizeLimit = 10 * 1024 * 1024;
|
||||
private boolean isRegEx = false;
|
||||
private int numberOfLimit = 100;
|
||||
private static final int MAX_EXPAND = 4;
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "WatchRequestModel{" +
|
||||
"classPattern='" + classPattern + '\'' +
|
||||
", methodPattern='" + methodPattern + '\'' +
|
||||
", express='" + express + '\'' +
|
||||
", conditionExpress='" + conditionExpress + '\'' +
|
||||
", isBefore=" + isBefore +
|
||||
", isFinish=" + isFinish +
|
||||
", isException=" + isException +
|
||||
", isSuccess=" + isSuccess +
|
||||
", expand=" + expand +
|
||||
", sizeLimit=" + sizeLimit +
|
||||
", isRegEx=" + isRegEx +
|
||||
", numberOfLimit=" + numberOfLimit +
|
||||
", excludeClassPattern='" + excludeClassPattern + '\'' +
|
||||
", jobId=" + jobId +
|
||||
", listenerId=" + listenerId +
|
||||
", verbose=" + verbose +
|
||||
", maxNumOfMatchedClass=" + maxNumOfMatchedClass +
|
||||
'}';
|
||||
}
|
||||
|
||||
public WatchRequestModel(WatchRequest watchRequest) {
|
||||
parseRequestParams(watchRequest);
|
||||
}
|
||||
|
||||
public Matcher getClassNameMatcher() {
|
||||
if (classNameMatcher == null) {
|
||||
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
|
||||
}
|
||||
return classNameMatcher;
|
||||
}
|
||||
|
||||
public Matcher getMethodNameMatcher() {
|
||||
if (methodNameMatcher == null) {
|
||||
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
|
||||
}
|
||||
return methodNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(ArthasStreamObserver arthasStreamObserver) {
|
||||
WatchRequestModel watchRequestModel = (WatchRequestModel) arthasStreamObserver.getRequestModel();
|
||||
if (watchRequestModel.getListenerId()!= 0) {
|
||||
AdviceListener listener = AdviceWeaver.listener(watchRequestModel.getListenerId());
|
||||
if (listener != null) {
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
return new WatchRpcAdviceListener(arthasStreamObserver, GlobalOptions.verbose || watchRequestModel.isVerbose());
|
||||
}
|
||||
|
||||
|
||||
public Matcher getClassNameExcludeMatcher() {
|
||||
if (classNameExcludeMatcher == null && getExcludeClassPattern() != null) {
|
||||
classNameExcludeMatcher = SearchUtils.classNameMatcher(getExcludeClassPattern(), isRegEx());
|
||||
}
|
||||
return classNameExcludeMatcher;
|
||||
}
|
||||
|
||||
public void parseRequestParams(WatchRequest watchRequest){
|
||||
this.classPattern = watchRequest.getClassPattern();
|
||||
this.methodPattern = watchRequest.getMethodPattern();
|
||||
if(StringUtils.isEmpty(watchRequest.getExpress())){
|
||||
this.express = "{params, target, returnObj}";
|
||||
}else {
|
||||
this.express = watchRequest.getExpress();
|
||||
}
|
||||
this.conditionExpress = watchRequest.getConditionExpress();
|
||||
this.isBefore = watchRequest.getIsBefore();
|
||||
this.isFinish = watchRequest.getIsFinish();
|
||||
this.isException = watchRequest.getIsException();
|
||||
this.isSuccess = watchRequest.getIsSuccess();
|
||||
if (!watchRequest.getIsBefore() && !watchRequest.getIsFinish() && !watchRequest.getIsException() && !watchRequest.getIsSuccess()) {
|
||||
this.isFinish = true;
|
||||
}
|
||||
if (watchRequest.getExpand() <= 0) {
|
||||
this.expand = 1;
|
||||
} else if (watchRequest.getExpand() > MAX_EXPAND){
|
||||
this.expand = MAX_EXPAND;
|
||||
} else {
|
||||
this.expand = watchRequest.getExpand();
|
||||
}
|
||||
if (watchRequest.getSizeLimit() == 0) {
|
||||
this.sizeLimit = 10 * 1024 * 1024;
|
||||
} else {
|
||||
this.sizeLimit = watchRequest.getSizeLimit();
|
||||
}
|
||||
this.isRegEx = watchRequest.getIsRegEx();
|
||||
if (watchRequest.getNumberOfLimit() == 0) {
|
||||
this.numberOfLimit = 100;
|
||||
} else {
|
||||
this.numberOfLimit = watchRequest.getNumberOfLimit();
|
||||
}
|
||||
if(watchRequest.getExcludeClassPattern().equals("")){
|
||||
this.excludeClassPattern = null;
|
||||
}else {
|
||||
this.excludeClassPattern = watchRequest.getExcludeClassPattern();
|
||||
}
|
||||
this.listenerId = watchRequest.getListenerId();
|
||||
this.verbose = watchRequest.getVerbose();
|
||||
if(watchRequest.getMaxNumOfMatchedClass() == 0){
|
||||
this.maxNumOfMatchedClass = 50;
|
||||
}else {
|
||||
this.maxNumOfMatchedClass = watchRequest.getMaxNumOfMatchedClass();
|
||||
}
|
||||
this.jobId = watchRequest.getJobId();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
public String getExpress() {
|
||||
return express;
|
||||
}
|
||||
|
||||
public void setExpress(String express) {
|
||||
this.express = express;
|
||||
}
|
||||
|
||||
public String getConditionExpress() {
|
||||
return conditionExpress;
|
||||
}
|
||||
|
||||
public void setConditionExpress(String conditionExpress) {
|
||||
this.conditionExpress = conditionExpress;
|
||||
}
|
||||
|
||||
public boolean isBefore() {
|
||||
return isBefore;
|
||||
}
|
||||
|
||||
public void setBefore(boolean before) {
|
||||
isBefore = before;
|
||||
}
|
||||
|
||||
public boolean isFinish() {
|
||||
return isFinish;
|
||||
}
|
||||
|
||||
public void setFinish(boolean finish) {
|
||||
isFinish = finish;
|
||||
}
|
||||
|
||||
public boolean isException() {
|
||||
return isException;
|
||||
}
|
||||
|
||||
public void setException(boolean exception) {
|
||||
isException = exception;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
isSuccess = success;
|
||||
}
|
||||
|
||||
public Integer getExpand() {
|
||||
return expand;
|
||||
}
|
||||
|
||||
public void setExpand(Integer expand) {
|
||||
this.expand = expand;
|
||||
}
|
||||
|
||||
public Integer getSizeLimit() {
|
||||
return sizeLimit;
|
||||
}
|
||||
|
||||
public void setSizeLimit(Integer sizeLimit) {
|
||||
this.sizeLimit = sizeLimit;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
public int getNumberOfLimit() {
|
||||
return numberOfLimit;
|
||||
}
|
||||
|
||||
public void setNumberOfLimit(int numberOfLimit) {
|
||||
this.numberOfLimit = numberOfLimit;
|
||||
}
|
||||
|
||||
public String getExcludeClassPattern() {
|
||||
return excludeClassPattern;
|
||||
}
|
||||
|
||||
public void setExcludeClassPattern(String excludeClassPattern) {
|
||||
this.excludeClassPattern = excludeClassPattern;
|
||||
}
|
||||
|
||||
public void setClassNameMatcher(Matcher classNameMatcher) {
|
||||
this.classNameMatcher = classNameMatcher;
|
||||
}
|
||||
|
||||
public void setClassNameExcludeMatcher(Matcher classNameExcludeMatcher) {
|
||||
this.classNameExcludeMatcher = classNameExcludeMatcher;
|
||||
}
|
||||
|
||||
public void setMethodNameMatcher(Matcher methodNameMatcher) {
|
||||
this.methodNameMatcher = methodNameMatcher;
|
||||
}
|
||||
|
||||
public long getListenerId() {
|
||||
return listenerId;
|
||||
}
|
||||
|
||||
public void setListenerId(long listenerId) {
|
||||
this.listenerId = listenerId;
|
||||
}
|
||||
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
}
|
||||
|
||||
public void setVerbose(boolean verbose) {
|
||||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
public int getMaxNumOfMatchedClass() {
|
||||
return maxNumOfMatchedClass;
|
||||
}
|
||||
|
||||
public void setMaxNumOfMatchedClass(int maxNumOfMatchedClass) {
|
||||
this.maxNumOfMatchedClass = maxNumOfMatchedClass;
|
||||
}
|
||||
|
||||
public long getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.model;
|
||||
|
||||
import com.taobao.arthas.core.command.model.WatchModel;
|
||||
|
||||
public class WatchResponseModel extends WatchModel {
|
||||
|
||||
private long resultId;
|
||||
|
||||
public long getResultId() {
|
||||
return resultId;
|
||||
}
|
||||
|
||||
public void setResultId(long resultId) {
|
||||
this.resultId = resultId;
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.objectUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
// ComplexObject.java
|
||||
public class ComplexObject {
|
||||
private int id;
|
||||
private String name;
|
||||
private double value;
|
||||
private int[] numbers;
|
||||
private Long[] longNumbers;
|
||||
private NestedObject nestedObject;
|
||||
private ComplexObject[] complexArray;
|
||||
private int[][] multiDimensionalArray;
|
||||
private String[] stringArray;
|
||||
|
||||
private Collection<String> collection;
|
||||
|
||||
List<String> stringList;
|
||||
|
||||
Map<String, Integer> stringIntegerMap;
|
||||
|
||||
private Double[] doubleArray;
|
||||
|
||||
public static class NestedObject {
|
||||
private int nestedId;
|
||||
private String nestedName;
|
||||
private boolean flag;
|
||||
|
||||
public int getNestedId() {
|
||||
return nestedId;
|
||||
}
|
||||
|
||||
public void setNestedId(int nestedId) {
|
||||
this.nestedId = nestedId;
|
||||
}
|
||||
|
||||
public String getNestedName() {
|
||||
return nestedName;
|
||||
}
|
||||
|
||||
public void setNestedName(String nestedName) {
|
||||
this.nestedName = nestedName;
|
||||
}
|
||||
|
||||
public boolean isFlag() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
public void setFlag(boolean flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Integer> getStringIntegerMap() {
|
||||
return stringIntegerMap;
|
||||
}
|
||||
|
||||
public void setStringIntegerMap(Map<String, Integer> stringIntegerMap) {
|
||||
this.stringIntegerMap = stringIntegerMap;
|
||||
}
|
||||
|
||||
public Double[] getDoubleArray() {
|
||||
return doubleArray;
|
||||
}
|
||||
|
||||
public void setDoubleArray(Double[] doubleArray) {
|
||||
this.doubleArray = doubleArray;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int[] getNumbers() {
|
||||
return numbers;
|
||||
}
|
||||
|
||||
public void setNumbers(int[] numbers) {
|
||||
this.numbers = numbers;
|
||||
}
|
||||
|
||||
public NestedObject getNestedObject() {
|
||||
return nestedObject;
|
||||
}
|
||||
|
||||
public void setNestedObject(NestedObject nestedObject) {
|
||||
this.nestedObject = nestedObject;
|
||||
}
|
||||
|
||||
public ComplexObject[] getComplexArray() {
|
||||
return complexArray;
|
||||
}
|
||||
|
||||
public void setComplexArray(ComplexObject[] complexArray) {
|
||||
this.complexArray = complexArray;
|
||||
}
|
||||
|
||||
public int[][] getMultiDimensionalArray() {
|
||||
return multiDimensionalArray;
|
||||
}
|
||||
|
||||
public void setMultiDimensionalArray(int[][] multiDimensionalArray) {
|
||||
this.multiDimensionalArray = multiDimensionalArray;
|
||||
}
|
||||
|
||||
public String[] getStringArray() {
|
||||
return stringArray;
|
||||
}
|
||||
|
||||
public void setStringArray(String[] stringArray) {
|
||||
this.stringArray = stringArray;
|
||||
}
|
||||
|
||||
public Long[] getLongNumbers() {
|
||||
return longNumbers;
|
||||
}
|
||||
|
||||
public void setLongNumbers(Long[] longNumbers) {
|
||||
this.longNumbers = longNumbers;
|
||||
}
|
||||
|
||||
public Collection<String> getCollection() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
public void setCollection(Collection<String> collection) {
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
public List<String> getStringList() {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
public void setStringList(List<String> stringList) {
|
||||
this.stringList = stringList;
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.objectUtils;
|
||||
|
||||
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 toJavaObjectWithExpand(Object obj, int expand){
|
||||
int depth;
|
||||
if(expand <= 0){
|
||||
depth = MAX_DEPTH - 1;
|
||||
}else if(expand >= MAX_DEPTH){
|
||||
depth = 0;
|
||||
}else {
|
||||
depth = MAX_DEPTH - expand;
|
||||
}
|
||||
return toJavaObject(obj, depth);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.observer;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.model.ResultModel;
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public interface ArthasStreamObserver<T> {
|
||||
|
||||
void onNext(T value);
|
||||
|
||||
void onError(Throwable t);
|
||||
|
||||
void onCompleted();
|
||||
|
||||
Instrumentation getInstrumentation();
|
||||
|
||||
ArthasStreamObserver write(String msg);
|
||||
|
||||
void appendResult(ResultModel result);
|
||||
|
||||
AtomicInteger times();
|
||||
|
||||
void register(AdviceListener listener, ClassFileTransformer transformer);
|
||||
|
||||
void unregister();
|
||||
|
||||
void end();
|
||||
|
||||
ExecStatus getPorcessStatus();
|
||||
|
||||
void setProcessStatus(ExecStatus execStatus);
|
||||
/**
|
||||
* End the process.
|
||||
*
|
||||
* @param status the exit status.
|
||||
*/
|
||||
void end(int status);
|
||||
/**
|
||||
* End the process.
|
||||
*
|
||||
* @param status the exit status.
|
||||
*/
|
||||
void end(int status, String message);
|
||||
|
||||
int getJobId();
|
||||
|
||||
Object getRequestModel();
|
||||
|
||||
void setRequestModel(Object requestModel);
|
||||
|
||||
AdviceListener getListener();
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.observer.impl;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.advisor.AdviceWeaver;
|
||||
import com.taobao.arthas.core.command.model.ResultModel;
|
||||
import com.taobao.arthas.core.command.model.StatusModel;
|
||||
import com.taobao.arthas.core.distribution.ResultDistributor;
|
||||
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import com.taobao.arthas.core.shell.system.ProcessAware;
|
||||
import com.taobao.arthas.grpcweb.grpc.DemoBootstrap;
|
||||
import com.taobao.arthas.grpcweb.grpc.distribution.GrpcResultDistributorImpl;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.service.GrpcJobController;
|
||||
import io.grpc.stub.ServerCallStreamObserver;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ArthasStreamObserverImpl<T> implements ArthasStreamObserver<T> {
|
||||
|
||||
private StreamObserver<T> streamObserver;
|
||||
|
||||
private AtomicInteger times = new AtomicInteger();
|
||||
|
||||
private GrpcProcess process;
|
||||
|
||||
private Object requestModel;
|
||||
private AdviceListener listener;
|
||||
|
||||
private ClassFileTransformer transformer;
|
||||
|
||||
private final int jobId;
|
||||
|
||||
|
||||
private ResultDistributor resultDistributor;
|
||||
|
||||
private GrpcJobController grpcJobController;
|
||||
|
||||
private Instrumentation instrumentation;
|
||||
|
||||
|
||||
public ArthasStreamObserverImpl(StreamObserver<T> streamObserver, Object requestModel, GrpcJobController grpcJobController){
|
||||
this.streamObserver = streamObserver;
|
||||
this.jobId = grpcJobController.generateGrpcJobId();
|
||||
this.instrumentation = grpcJobController.getInstrumentation();
|
||||
if (resultDistributor == null) {
|
||||
resultDistributor = new GrpcResultDistributorImpl(this, grpcJobController.getResultViewResolver());
|
||||
}
|
||||
this.process = new GrpcProcess();
|
||||
this.process.setProcessStatus(ExecStatus.READY);
|
||||
// 请求参数
|
||||
this.requestModel = requestModel;
|
||||
// 配置客户端取消事件
|
||||
this.setOnCancelHandler();
|
||||
this.grpcJobController = grpcJobController;
|
||||
this.grpcJobController.registerGrpcJob(jobId, this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onNext(T value) {
|
||||
streamObserver.onNext(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
streamObserver.onError(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
this.process.setProcessStatus(ExecStatus.TERMINATED);
|
||||
// grpcJobController.unRegisterGrpcJob(this.jobId);
|
||||
streamObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtomicInteger times() {
|
||||
return times;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void register(AdviceListener adviceListener, ClassFileTransformer transformer) {
|
||||
if (adviceListener instanceof ProcessAware) {
|
||||
ProcessAware processAware = (ProcessAware) adviceListener;
|
||||
// listener 有可能是其它 command 创建的
|
||||
if(processAware.getProcess() == null) {
|
||||
this.process.setProcessStatus(ExecStatus.RUNNING);
|
||||
processAware.setProcess(this.process);
|
||||
}
|
||||
}
|
||||
this.listener = adviceListener;
|
||||
AdviceWeaver.reg(listener);
|
||||
|
||||
this.transformer = transformer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
if (transformer != null) {
|
||||
DemoBootstrap.getRunningInstance().getTransformerManager().removeTransformer(transformer);
|
||||
}
|
||||
this.process.setProcessStatus(ExecStatus.TERMINATED);
|
||||
if (listener instanceof ProcessAware) {
|
||||
// listener有可能其它 command 创建的,所以不能unRge
|
||||
if (this.process.equals(((ProcessAware) listener).getProcess())) {
|
||||
AdviceWeaver.unReg(listener);
|
||||
}
|
||||
} else {
|
||||
AdviceWeaver.unReg(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
end(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecStatus getPorcessStatus() {
|
||||
return this.process.status();
|
||||
}
|
||||
@Override
|
||||
public void setProcessStatus(ExecStatus execStatus){
|
||||
this.process.setProcessStatus(execStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(int statusCode) {
|
||||
end(statusCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(int statusCode, String message) {
|
||||
terminate(statusCode, message);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ArthasStreamObserver write(String msg) {
|
||||
ResponseBody result = ResponseBody.newBuilder().setStringValue(msg).build();
|
||||
onNext((T) result);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendResult(ResultModel result) {
|
||||
if (process.status() != ExecStatus.RUNNING) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot write to standard output when " + process.status().name().toLowerCase());
|
||||
}
|
||||
result.setJobId(jobId);
|
||||
if (resultDistributor != null) {
|
||||
resultDistributor.appendResult(result);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public int getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getRequestModel() {
|
||||
return requestModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestModel(Object requestModel) {
|
||||
this.requestModel = requestModel;
|
||||
}
|
||||
|
||||
public void setOnCancelHandler() {
|
||||
ServerCallStreamObserver<T> observer = (ServerCallStreamObserver<T>) this.streamObserver;
|
||||
observer.setOnCancelHandler(() -> {
|
||||
this.end();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private synchronized boolean terminate(int exitCode, String message) {
|
||||
boolean flag;
|
||||
if (process.status() != ExecStatus.TERMINATED) {
|
||||
//add status message
|
||||
this.appendResult(new StatusModel(exitCode, message));
|
||||
if (process != null) {
|
||||
this.unregister();
|
||||
}
|
||||
flag = true;
|
||||
} else {
|
||||
flag = false;
|
||||
}
|
||||
this.onCompleted();
|
||||
return flag;
|
||||
}
|
||||
|
||||
|
||||
public AdviceListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instrumentation getInstrumentation() {
|
||||
return instrumentation;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.observer.impl;
|
||||
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import com.taobao.arthas.core.shell.system.Process;
|
||||
import com.taobao.arthas.core.shell.term.Tty;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class GrpcProcess implements Process {
|
||||
|
||||
private ExecStatus processStatus;
|
||||
|
||||
public void setProcessStatus(ExecStatus processStatus) {
|
||||
this.processStatus = processStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecStatus status() {
|
||||
return processStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer exitCode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Process setTty(Tty tty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tty getTty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Process setSession(Session session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Process terminatedHandler(Handler<Integer> handler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(boolean foreground) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt(Handler<Void> completionHandler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(boolean foreground) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(Handler<Void> completionHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(boolean foreground, Handler<Void> completionHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspend() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspend(Handler<Void> completionHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate(Handler<Void> completionHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBackground() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBackground(Handler<Void> completionHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toForeground() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toForeground(Handler<Void> completionHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int times() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date startTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJobId(int jobId) {
|
||||
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.server;
|
||||
|
||||
import arthas.VmTool;
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.SocketUtils;
|
||||
import com.taobao.arthas.core.advisor.TransformerManager;
|
||||
import com.taobao.arthas.grpcweb.grpc.service.*;
|
||||
import com.taobao.arthas.grpcweb.grpc.view.GrpcResultViewResolver;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
public class GrpcServer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
|
||||
|
||||
private int port;
|
||||
|
||||
private Server grpcServer;
|
||||
|
||||
private Instrumentation instrumentation;
|
||||
|
||||
private TransformerManager transformerManager;
|
||||
|
||||
public GrpcServer(int port, Instrumentation instrumentation, TransformerManager transformerManager) {
|
||||
if (port == 0) {
|
||||
this.port = SocketUtils.findAvailableTcpPort();
|
||||
} else {
|
||||
this.port = port;
|
||||
}
|
||||
this.instrumentation = instrumentation;
|
||||
this.transformerManager = transformerManager;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
GrpcResultViewResolver grpcResultViewResolver = new GrpcResultViewResolver();
|
||||
GrpcJobController grpcJobController = new GrpcJobController(this.instrumentation, this.transformerManager, grpcResultViewResolver);
|
||||
File path = new File(VmTool.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParentFile();
|
||||
String libPath = path.getAbsolutePath();
|
||||
|
||||
try {
|
||||
grpcServer = ServerBuilder.forPort(port)
|
||||
.addService(new ObjectService(grpcJobController,libPath))
|
||||
.addService(new PwdCommandService(grpcJobController))
|
||||
.addService(new SystemPropertyCommandService(grpcJobController))
|
||||
.addService(new WatchCommandService(grpcJobController))
|
||||
.build()
|
||||
.start();
|
||||
logger.info("Server started, listening on " + port);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread("grpc-server-shutdown") {
|
||||
@Override
|
||||
public void run() {
|
||||
if (grpcServer != null) {
|
||||
grpcServer.shutdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.server.httpServer;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
|
||||
public class NettyHttpInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private final String STATIC_LOCATION;
|
||||
|
||||
public NettyHttpInitializer(String staticLocation) {
|
||||
this.STATIC_LOCATION = staticLocation;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
//将请求和应答消息编码或解码为HTTP消息
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
//将HTTP消息的多个部分组合成一条完整的HTTP消息
|
||||
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
pipeline.addLast(new NettyHttpStaticFileHandler(this.STATIC_LOCATION));
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.server.httpServer;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
public class NettyHttpServer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
|
||||
|
||||
private int port;
|
||||
|
||||
private final String STATIC_LOCATION;
|
||||
|
||||
|
||||
public NettyHttpServer(int port, String staticLocation) {
|
||||
this.port = port;
|
||||
this.STATIC_LOCATION = staticLocation;
|
||||
}
|
||||
|
||||
public void start() throws InterruptedException {
|
||||
NioEventLoopGroup boss = new NioEventLoopGroup(1);
|
||||
NioEventLoopGroup work = new NioEventLoopGroup();
|
||||
try {
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(boss, work)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new NettyHttpInitializer(this.STATIC_LOCATION))
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
logger.info("start http server on port: {}", port);
|
||||
ChannelFuture future = serverBootstrap.bind(port).sync();
|
||||
future.channel().closeFuture().sync();
|
||||
} finally {
|
||||
work.shutdownGracefully();
|
||||
boss.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.server.httpServer;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpChunkedInput;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.stream.ChunkedFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
|
||||
public class NettyHttpStaticFileHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
|
||||
// 资源所在路径
|
||||
private final String STATIC_LOCATION;
|
||||
|
||||
public NettyHttpStaticFileHandler(String staticLocation){
|
||||
this.STATIC_LOCATION = staticLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws URISyntaxException, IOException {
|
||||
// 获取URI
|
||||
String uri = new URI(request.uri()).getPath();
|
||||
// 设置不支持favicon.ico文件
|
||||
if ("/favicon.ico".equals(uri)) {
|
||||
return;
|
||||
}
|
||||
if ("/".equals(uri)) {
|
||||
uri = "/index.html";
|
||||
}
|
||||
// 根据路径地址构建文件
|
||||
String path = Paths.get(this.STATIC_LOCATION, uri).toString();
|
||||
File file = new File(path);
|
||||
// 状态为1xx的话,继续请求
|
||||
if (HttpUtil.is100ContinueExpected(request)) {
|
||||
send100Continue(ctx);
|
||||
}
|
||||
// 当文件隐藏/不存在/是目录/非文件的时候,将资源指向NOT_FOUND
|
||||
if (file.isHidden() || !file.exists() || file.isDirectory() || !file.isFile()) {
|
||||
sendNotFound(ctx);
|
||||
return;
|
||||
}
|
||||
final RandomAccessFile randomAccessFile;
|
||||
try {
|
||||
randomAccessFile = new RandomAccessFile(file, "r");
|
||||
} catch (FileNotFoundException e) {
|
||||
sendNotFound(ctx);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
|
||||
|
||||
// 设置文件格式内容
|
||||
if (path.endsWith(".html")){
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
|
||||
}else if(path.endsWith(".js")){
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/x-javascript");
|
||||
}else if(path.endsWith(".css")){
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/css; charset=UTF-8");
|
||||
}else{
|
||||
MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimetypesFileTypeMap.getContentType(path));
|
||||
}
|
||||
|
||||
boolean keepAlive = HttpUtil.isKeepAlive(request);
|
||||
|
||||
if (keepAlive) {
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, randomAccessFile.length());
|
||||
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||
}
|
||||
ctx.write(response);
|
||||
|
||||
ChannelFuture sendFileFuture;
|
||||
ChannelFuture lastContentFuture;
|
||||
if (ctx.pipeline().get(SslHandler.class) == null) {
|
||||
sendFileFuture =
|
||||
ctx.write(new DefaultFileRegion(randomAccessFile.getChannel(), 0, randomAccessFile.length()), ctx.newProgressivePromise());
|
||||
// Write the end marker.
|
||||
lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
} else {
|
||||
sendFileFuture =
|
||||
ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(randomAccessFile, 0, randomAccessFile.length(), 10 * 1024 * 1024)),
|
||||
ctx.newProgressivePromise());
|
||||
// HttpChunkedInput will write the end marker (LastHttpContent) for us.
|
||||
lastContentFuture = sendFileFuture;
|
||||
}
|
||||
|
||||
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
|
||||
@Override
|
||||
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
|
||||
if (total < 0) { // total unknown
|
||||
logger.info(future.channel() + " Transfer progress: " + progress);
|
||||
} else {
|
||||
logger.info(future.channel() + " Transfer progress: " + progress + " / " + total);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelProgressiveFuture future) {
|
||||
logger.info(future.channel() + " Transfer complete.");
|
||||
}
|
||||
});
|
||||
|
||||
// Decide whether to close the connection or not.
|
||||
if (!HttpUtil.isKeepAlive(request)) {
|
||||
// Close the connection when the whole content is written out.
|
||||
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void send100Continue(ChannelHandlerContext ctx) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
|
||||
private static void sendNotFound(ChannelHandlerContext ctx){
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service;
|
||||
|
||||
|
||||
import com.taobao.arthas.core.advisor.TransformerManager;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.view.GrpcResultViewResolver;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class GrpcJobController{
|
||||
|
||||
private Map<Long/*JOB_ID*/, ArthasStreamObserver> jobs
|
||||
= new ConcurrentHashMap<Long, ArthasStreamObserver>();
|
||||
// private Map<Long/*JOB_ID*/, ArthasStreamObserver> jobs
|
||||
// = new HashMap<>();
|
||||
private final AtomicInteger idGenerator = new AtomicInteger(0);
|
||||
|
||||
private GrpcResultViewResolver resultViewResolver;
|
||||
|
||||
private Instrumentation instrumentation;
|
||||
|
||||
private TransformerManager transformerManager;
|
||||
|
||||
public GrpcJobController(Instrumentation instrumentation, TransformerManager transformerManager, GrpcResultViewResolver resultViewResolver){
|
||||
this.instrumentation = instrumentation;
|
||||
this.transformerManager = transformerManager;
|
||||
this.resultViewResolver = resultViewResolver;
|
||||
}
|
||||
|
||||
|
||||
public Set<Long> getJobIds(){
|
||||
return jobs.keySet();
|
||||
}
|
||||
|
||||
public void registerGrpcJob(long jobId,ArthasStreamObserver arthasStreamObserver){
|
||||
jobs.put(jobId, arthasStreamObserver);
|
||||
}
|
||||
|
||||
public void unRegisterGrpcJob(long jobId){
|
||||
if(jobs.containsKey(jobId)){
|
||||
jobs.remove(jobId);
|
||||
}
|
||||
}
|
||||
public boolean containsJob(long jobId){
|
||||
return jobs.containsKey(jobId);
|
||||
}
|
||||
|
||||
public ArthasStreamObserver getGrpcJob(long jobId){
|
||||
if(this.containsJob(jobId)){
|
||||
return jobs.get(jobId);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int generateGrpcJobId(){
|
||||
int jobId = idGenerator.incrementAndGet();
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public GrpcResultViewResolver getResultViewResolver() {
|
||||
return resultViewResolver;
|
||||
}
|
||||
|
||||
public Instrumentation getInstrumentation() {
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
public void setInstrumentation(Instrumentation instrumentation) {
|
||||
this.instrumentation = instrumentation;
|
||||
}
|
||||
|
||||
public TransformerManager getTransformerManager() {
|
||||
return transformerManager;
|
||||
}
|
||||
|
||||
public void setTransformerManager(TransformerManager transformerManager) {
|
||||
this.transformerManager = transformerManager;
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.taobao.arthas.core.command.express.Express;
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.command.express.ExpressFactory;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
import com.taobao.arthas.grpcweb.grpc.objectUtils.JavaObjectConverter;
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.VmToolUtils;
|
||||
|
||||
import arthas.VmTool;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.impl.ArthasStreamObserverImpl;
|
||||
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 ObjectService extends ObjectServiceImplBase {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
|
||||
|
||||
private VmTool vmTool;
|
||||
private Instrumentation inst;
|
||||
|
||||
private GrpcJobController grpcJobController;
|
||||
|
||||
|
||||
public ObjectService(GrpcJobController grpcJobController, String libDir) {
|
||||
this.inst = grpcJobController.getInstrumentation();
|
||||
this.grpcJobController = grpcJobController;
|
||||
|
||||
try {
|
||||
String detectLibName = VmToolUtils.detectLibName();
|
||||
String vmToolLibPath = Paths.get(libDir, detectLibName).toString();
|
||||
|
||||
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();
|
||||
}
|
||||
ArthasStreamObserver<ObjectQueryResult> arthasStreamObserver = new ArthasStreamObserverImpl<>(responseObserver, null,grpcJobController);
|
||||
String className = query.getClassName();
|
||||
String classLoaderHash = query.getClassLoaderHash();
|
||||
String classLoaderClass = query.getClassLoaderClass();
|
||||
int limit = query.getLimit();
|
||||
int depth = query.getDepth();
|
||||
String express = query.getExpress();
|
||||
String resultExpress = query.getResultExpress();
|
||||
|
||||
// 如果只传递了 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) {
|
||||
arthasStreamObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false)
|
||||
.setMessage("can not find class: " + className).build());
|
||||
arthasStreamObserver.onCompleted();
|
||||
return;
|
||||
} else if (foundClassList.size() > 1) {
|
||||
String message = "found more than one class: " + className;
|
||||
arthasStreamObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false).setMessage(message).build());
|
||||
arthasStreamObserver.onCompleted();
|
||||
return;
|
||||
} else { // 找到了指定的 类
|
||||
Object[] instances = vmTool.getInstances(foundClassList.get(0), limit);
|
||||
Builder builder = ObjectQueryResult.newBuilder().setSuccess(true);
|
||||
/**
|
||||
* 这里尝试使用express
|
||||
*/
|
||||
Object value = null;
|
||||
if (!isEmpty(express)) {
|
||||
Express unpooledExpress = ExpressFactory.unpooledExpress(foundClassList.get(0).getClassLoader());
|
||||
try {
|
||||
value = unpooledExpress.bind(new InstancesWrapper(instances)).get(express);
|
||||
} catch (ExpressException e) {
|
||||
logger.warn("ognl: failed execute express: " + express, e);
|
||||
}
|
||||
}
|
||||
if(value != null && !isEmpty(resultExpress)){
|
||||
try {
|
||||
value = ExpressFactory.threadLocalExpress(value).bind(Constants.COST_VARIABLE, 0.0).get(resultExpress);
|
||||
} catch (ExpressException e) {
|
||||
logger.warn("ognl: failed execute result express: " + express, e);
|
||||
}
|
||||
}
|
||||
JavaObject javaObject = JavaObjectConverter.toJavaObjectWithExpand(value, depth);
|
||||
builder.addObjects(javaObject);
|
||||
arthasStreamObserver.onNext(builder.build());
|
||||
arthasStreamObserver.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) {
|
||||
arthasStreamObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false)
|
||||
.setMessage("can not find class: " + className).build());
|
||||
arthasStreamObserver.onCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
Object[] instances = vmTool.getInstances(foundClass, limit);
|
||||
Builder builder = ObjectQueryResult.newBuilder().setSuccess(true);
|
||||
// for (Object obj : instances) {
|
||||
// JavaObject javaObject = JavaObjectConverter.toJavaObjectWithExpand(obj, depth);
|
||||
// builder.addObjects(javaObject);
|
||||
// }
|
||||
|
||||
Object value = null;
|
||||
if (!isEmpty(express)) {
|
||||
Express unpooledExpress = ExpressFactory.unpooledExpress(foundClass.getClassLoader());
|
||||
try {
|
||||
value = unpooledExpress.bind(new InstancesWrapper(instances)).get(express);
|
||||
} catch (ExpressException e) {
|
||||
logger.warn("ognl: failed execute express: " + express, e);
|
||||
}
|
||||
}
|
||||
if(value != null && !isEmpty(resultExpress)){
|
||||
try {
|
||||
value = ExpressFactory.threadLocalExpress(value).bind(Constants.COST_VARIABLE, 0.0).get(resultExpress);
|
||||
} catch (ExpressException e) {
|
||||
logger.warn("ognl: failed execute result express: " + express, e);
|
||||
}
|
||||
}
|
||||
JavaObject javaObject = JavaObjectConverter.toJavaObjectWithExpand(value, depth);
|
||||
builder.addObjects(javaObject);
|
||||
arthasStreamObserver.onNext(builder.build());
|
||||
arthasStreamObserver.onCompleted();
|
||||
}
|
||||
|
||||
public static boolean isEmpty(Object str) {
|
||||
return str == null || "".equals(str);
|
||||
}
|
||||
|
||||
static class InstancesWrapper {
|
||||
Object instances;
|
||||
|
||||
public InstancesWrapper(Object instances) {
|
||||
this.instances = instances;
|
||||
}
|
||||
|
||||
public Object getInstances() {
|
||||
return instances;
|
||||
}
|
||||
|
||||
public void setInstances(Object instances) {
|
||||
this.instances = instances;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service;
|
||||
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import io.arthas.api.PwdGrpc;
|
||||
import com.google.protobuf.Empty;
|
||||
import com.taobao.arthas.core.command.model.PwdModel;
|
||||
|
||||
import com.taobao.arthas.core.shell.session.SessionManager;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.impl.ArthasStreamObserverImpl;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
|
||||
public class PwdCommandService extends PwdGrpc.PwdImplBase{
|
||||
|
||||
private GrpcJobController grpcJobController;
|
||||
|
||||
|
||||
public PwdCommandService(GrpcJobController grpcJobController) {
|
||||
this.grpcJobController = grpcJobController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pwd(Empty empty, StreamObserver<ResponseBody> responseObserver){
|
||||
String path = new File("").getAbsolutePath();
|
||||
ArthasStreamObserver<ResponseBody> arthasStreamObserver = new ArthasStreamObserverImpl<>(responseObserver, null,grpcJobController);
|
||||
arthasStreamObserver.setProcessStatus(ExecStatus.RUNNING);
|
||||
arthasStreamObserver.appendResult(new PwdModel(path));
|
||||
arthasStreamObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service;
|
||||
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import io.arthas.api.ArthasServices.StringKey;
|
||||
import io.arthas.api.ArthasServices.StringStringMapValue;
|
||||
import io.arthas.api.SystemPropertyGrpc;
|
||||
import com.google.protobuf.Empty;
|
||||
import com.taobao.arthas.core.command.model.SystemPropertyModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.impl.ArthasStreamObserverImpl;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SystemPropertyCommandService extends SystemPropertyGrpc.SystemPropertyImplBase{
|
||||
|
||||
private GrpcJobController grpcJobController;
|
||||
|
||||
public SystemPropertyCommandService(GrpcJobController grpcJobController) {
|
||||
this.grpcJobController = grpcJobController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void get(Empty empty, StreamObserver<ResponseBody> responseObserver){
|
||||
ArthasStreamObserver<ResponseBody> arthasStreamObserver = new ArthasStreamObserverImpl<>(responseObserver, null, grpcJobController);
|
||||
arthasStreamObserver.setProcessStatus(ExecStatus.RUNNING);
|
||||
arthasStreamObserver.appendResult(new SystemPropertyModel(System.getProperties()));
|
||||
arthasStreamObserver.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByKey(StringKey request, StreamObserver<ResponseBody> responseObserver){
|
||||
String propertyName = request.getKey();
|
||||
ArthasStreamObserver<ResponseBody> arthasStreamObserver = new ArthasStreamObserverImpl<>(responseObserver,null, grpcJobController);
|
||||
arthasStreamObserver.setProcessStatus(ExecStatus.RUNNING);
|
||||
// view the specified system property
|
||||
String value = System.getProperty(propertyName);
|
||||
if (value == null) {
|
||||
arthasStreamObserver.end(-1, "There is no property with the key " + propertyName);
|
||||
return;
|
||||
} else {
|
||||
arthasStreamObserver.appendResult(new SystemPropertyModel(propertyName, value));
|
||||
arthasStreamObserver.end();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(StringStringMapValue request, StreamObserver<ResponseBody> responseObserver){
|
||||
// get properties from client
|
||||
Map<String, String> properties = request.getStringStringMapMap();
|
||||
String propertyName = "";
|
||||
String propertyValue = "";
|
||||
// change system property
|
||||
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||
propertyName = entry.getKey();
|
||||
propertyValue = entry.getValue();
|
||||
}
|
||||
ArthasStreamObserver<ResponseBody> arthasStreamObserver = new ArthasStreamObserverImpl<>(responseObserver,null, grpcJobController);
|
||||
arthasStreamObserver.setProcessStatus(ExecStatus.RUNNING);
|
||||
try {
|
||||
System.setProperty(propertyName, propertyValue);
|
||||
arthasStreamObserver.appendResult(new SystemPropertyModel(propertyName, System.getProperty(propertyName)));
|
||||
arthasStreamObserver.onCompleted();
|
||||
}catch (Throwable t) {
|
||||
arthasStreamObserver.end(-1, "Error during setting system property: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import io.arthas.api.ArthasServices.WatchRequest;
|
||||
import io.arthas.api.WatchGrpc;
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.core.advisor.AdviceWeaver;
|
||||
import com.taobao.arthas.core.command.model.MessageModel;
|
||||
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import com.taobao.arthas.grpcweb.grpc.DemoBootstrap;
|
||||
import com.taobao.arthas.grpcweb.grpc.model.WatchRequestModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.impl.ArthasStreamObserverImpl;
|
||||
import com.taobao.arthas.grpcweb.grpc.service.advisor.WatchRpcAdviceListener;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
public class WatchCommandService extends WatchGrpc.WatchImplBase {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WatchCommandService.class);
|
||||
|
||||
private WatchRequestModel watchRequestModel;
|
||||
|
||||
private ArthasStreamObserver arthasStreamObserver;
|
||||
|
||||
|
||||
private GrpcJobController grpcJobController;
|
||||
|
||||
public WatchCommandService(GrpcJobController grpcJobController) {
|
||||
this.grpcJobController = grpcJobController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(WatchRequest watchRequest, StreamObserver<ResponseBody> responseObserver){
|
||||
// 解析watchRequest 参数
|
||||
watchRequestModel = new WatchRequestModel(watchRequest);
|
||||
ArthasStreamObserverImpl<ResponseBody> newArthasStreamObserver = new ArthasStreamObserverImpl<>(responseObserver, watchRequestModel, grpcJobController);
|
||||
// arthasStreamObserver 传入到advisor中,实现异步传输数据
|
||||
if(grpcJobController.containsJob(watchRequestModel.getJobId())){
|
||||
arthasStreamObserver = grpcJobController.getGrpcJob(watchRequest.getJobId());
|
||||
if(arthasStreamObserver != null && arthasStreamObserver.getPorcessStatus() == ExecStatus.RUNNING){
|
||||
WatchRpcAdviceListener listener = (WatchRpcAdviceListener) AdviceWeaver.listener(arthasStreamObserver.getListener().id());
|
||||
watchRequestModel.setListenerId(listener.id());
|
||||
arthasStreamObserver.setRequestModel(watchRequestModel);
|
||||
listener.setArthasStreamObserver(arthasStreamObserver);
|
||||
arthasStreamObserver.appendResult(new MessageModel("SUCCESS CHANGE!!!!!!!!!!!"));
|
||||
newArthasStreamObserver.setProcessStatus(ExecStatus.RUNNING);
|
||||
newArthasStreamObserver.end(0,"修改成功!!!");
|
||||
return;
|
||||
}else {
|
||||
arthasStreamObserver = newArthasStreamObserver;
|
||||
}
|
||||
}else {
|
||||
arthasStreamObserver = newArthasStreamObserver;
|
||||
}
|
||||
// 创建watch任务
|
||||
WatchTask watchTask = new WatchTask();
|
||||
// 执行watch任务
|
||||
DemoBootstrap.getRunningInstance().execute(watchTask);
|
||||
}
|
||||
|
||||
private class WatchTask implements Runnable{
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
watchRequestModel.enhance(arthasStreamObserver);
|
||||
} catch (Throwable t) {
|
||||
logger.error("Error during processing the command:", t);
|
||||
arthasStreamObserver.end(-1, "Error during processing the command: " + t.getClass().getName() + ", message:" + t.getMessage()
|
||||
+ ", please check $HOME/logs/arthas/arthas.log for more details." );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service.advisor;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.concurrent.ConcurrentWeakKeyHashMap;
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import com.taobao.arthas.core.shell.system.Process;
|
||||
import com.taobao.arthas.core.shell.system.ProcessAware;
|
||||
import com.taobao.arthas.grpcweb.grpc.DemoBootstrap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* TODO line 的记录 listener方式? 还是有string为key,不过 classname|method|desc|num 这样子?
|
||||
* 判断是否已插入了,可以在两行中间查询,有没有 SpyAPI 的invoke?
|
||||
*
|
||||
* TODO trace的怎么搞? trace 只记录一次就可以了 classname|method|desc|trace ? 怎么避免 trace 到
|
||||
* SPY的invoke ?直接忽略?
|
||||
*
|
||||
* TODO trace命令可以动态的增加 新的函数进去不?只要关联上同一个 Listener应该是可以的。
|
||||
*
|
||||
* TODO 在SPY里放很多的 Object数组,然后动态的设置进去? 比如有新的 Listener来的时候。 这样子连查表都不用了。 甚至可以动态生成
|
||||
* 存放这些 Listener数组的类? 这样子的话,只要有 Binding那里,查询到一个具体分配好的类, 这样子就可以了?
|
||||
* 甚至每个ClassLoader里都动态生成这样子的 存放类,那么这样子不可以避免查 ClassLoader了么?
|
||||
*
|
||||
* 动态为每一个增强类,生成一个新的类,新的类里,有各种的 ID 数组,保存每一个类的每一种 trace 点的信息??
|
||||
*
|
||||
* 多个 watch命令 对同一个类,现在的逻辑是,每个watch都有一个自己的 TransForm,但不会重复增强,因为做了判断。
|
||||
* watch命令停止时,也没有去掉增强的代码。 只有reset时 才会去掉。
|
||||
*
|
||||
* 其实用户想查看局部变量,并不是想查看哪一行! 而是想看某个函数里子调用时的 局部变量的值! 所以实际上是想要一个新的命令,比如 watchinmethod
|
||||
* , 可以 在某个子调用里,
|
||||
*
|
||||
* TODO 现在的trace 可以输出行号,可能不是很精确,但是可以对应上的。 这个在新的方式里怎么支持? 增加一个 linenumber binding?
|
||||
* 从mehtodNode,向上查找到最近的行号?
|
||||
*
|
||||
* TODO 防止重复增强,最重要的应该还是动态增加 annotation,这个才是真正可以做到某一行,某一个子 invoke 都能识别出来的! 无论是
|
||||
* transform多少次! 字节码怎么动态加 annotation ? annotation里签名用 url ?的key/value方式表达!
|
||||
* 这样子可以有效还原信息
|
||||
*
|
||||
* TODO 是否考虑一个 trace /watch命令之后,得到一个具体的 Listener ID, 允许在另外的窗口里,再次
|
||||
* trace/watch时指定这个ID,就会查找到,并处理。 这样子的话,真正达到了动态灵活的,一层一层增加的trace !
|
||||
*
|
||||
*
|
||||
* @author hengyunabc 2020-04-24
|
||||
*
|
||||
*/
|
||||
public class AdviceListenerManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AdviceListenerManager.class);
|
||||
private static final FakeBootstrapClassLoader FAKEBOOTSTRAPCLASSLOADER = new FakeBootstrapClassLoader();
|
||||
|
||||
static {
|
||||
// 清理失效的 AdviceListener
|
||||
DemoBootstrap.getRunningInstance().getScheduledExecutorService().scheduleWithFixedDelay(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (Entry<ClassLoader, ClassLoaderAdviceListenerManager> entry : adviceListenerMap.entrySet()) {
|
||||
ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue();
|
||||
synchronized (adviceListenerManager) {
|
||||
for (Entry<String, List<AdviceListener>> eee : adviceListenerManager.map.entrySet()) {
|
||||
List<AdviceListener> listeners = eee.getValue();
|
||||
List<AdviceListener> newResult = new ArrayList<AdviceListener>();
|
||||
for (AdviceListener listener : listeners) {
|
||||
if (listener instanceof ProcessAware) {
|
||||
ProcessAware processAware = (ProcessAware) listener;
|
||||
Process process = processAware.getProcess();
|
||||
if (process == null) {
|
||||
continue;
|
||||
}
|
||||
ExecStatus status = process.status();
|
||||
if (!status.equals(ExecStatus.TERMINATED)) {
|
||||
newResult.add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newResult.size() != listeners.size()) {
|
||||
adviceListenerManager.map.put(eee.getKey(), newResult);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
logger.error("clean AdviceListener error", e);
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 3, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static final ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager> adviceListenerMap = new ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager>();
|
||||
|
||||
static class ClassLoaderAdviceListenerManager {
|
||||
private ConcurrentHashMap<String, List<AdviceListener>> map = new ConcurrentHashMap<String, List<AdviceListener>>();
|
||||
|
||||
private String key(String className, String methodName, String methodDesc) {
|
||||
return className + methodName + methodDesc;
|
||||
}
|
||||
|
||||
private String keyForTrace(String className, String owner, String methodName, String methodDesc) {
|
||||
return className + owner + methodName + methodDesc;
|
||||
}
|
||||
|
||||
public void registerAdviceListener(String className, String methodName, String methodDesc,
|
||||
AdviceListener listener) {
|
||||
synchronized (this) {
|
||||
className = className.replace('/', '.');
|
||||
String key = key(className, methodName, methodDesc);
|
||||
|
||||
List<AdviceListener> listeners = map.get(key);
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList<AdviceListener>();
|
||||
map.put(key, listeners);
|
||||
}
|
||||
if (!listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<AdviceListener> queryAdviceListeners(String className, String methodName, String methodDesc) {
|
||||
className = className.replace('/', '.');
|
||||
String key = key(className, methodName, methodDesc);
|
||||
|
||||
List<AdviceListener> listeners = map.get(key);
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void registerTraceAdviceListener(String className, String owner, String methodName, String methodDesc,
|
||||
AdviceListener listener) {
|
||||
|
||||
className = className.replace('/', '.');
|
||||
String key = keyForTrace(className, owner, methodName, methodDesc);
|
||||
|
||||
List<AdviceListener> listeners = map.get(key);
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList<AdviceListener>();
|
||||
map.put(key, listeners);
|
||||
}
|
||||
if (!listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public List<AdviceListener> queryTraceAdviceListeners(String className, String owner, String methodName,
|
||||
String methodDesc) {
|
||||
className = className.replace('/', '.');
|
||||
String key = keyForTrace(className, owner, methodName, methodDesc);
|
||||
|
||||
List<AdviceListener> listeners = map.get(key);
|
||||
|
||||
return listeners;
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName,
|
||||
String methodDesc, AdviceListener listener) {
|
||||
classLoader = wrap(classLoader);
|
||||
className = className.replace('/', '.');
|
||||
|
||||
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
|
||||
|
||||
if (manager == null) {
|
||||
manager = new ClassLoaderAdviceListenerManager();
|
||||
adviceListenerMap.put(classLoader, manager);
|
||||
}
|
||||
manager.registerAdviceListener(className, methodName, methodDesc, listener);
|
||||
}
|
||||
|
||||
public static void updateAdviceListeners() {
|
||||
|
||||
}
|
||||
|
||||
public static List<AdviceListener> queryAdviceListeners(ClassLoader classLoader, String className,
|
||||
String methodName, String methodDesc) {
|
||||
classLoader = wrap(classLoader);
|
||||
className = className.replace('/', '.');
|
||||
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
|
||||
|
||||
if (manager != null) {
|
||||
return manager.queryAdviceListeners(className, methodName, methodDesc);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void registerTraceAdviceListener(ClassLoader classLoader, String className, String owner,
|
||||
String methodName, String methodDesc, AdviceListener listener) {
|
||||
classLoader = wrap(classLoader);
|
||||
className = className.replace('/', '.');
|
||||
|
||||
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
|
||||
|
||||
if (manager == null) {
|
||||
manager = new ClassLoaderAdviceListenerManager();
|
||||
adviceListenerMap.put(classLoader, manager);
|
||||
}
|
||||
manager.registerTraceAdviceListener(className, owner, methodName, methodDesc, listener);
|
||||
}
|
||||
|
||||
public static List<AdviceListener> queryTraceAdviceListeners(ClassLoader classLoader, String className,
|
||||
String owner, String methodName, String methodDesc) {
|
||||
classLoader = wrap(classLoader);
|
||||
className = className.replace('/', '.');
|
||||
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
|
||||
|
||||
if (manager != null) {
|
||||
return manager.queryTraceAdviceListeners(className, owner, methodName, methodDesc);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ClassLoader wrap(ClassLoader classLoader) {
|
||||
if (classLoader != null) {
|
||||
return classLoader;
|
||||
}
|
||||
return FAKEBOOTSTRAPCLASSLOADER;
|
||||
}
|
||||
|
||||
private static class FakeBootstrapClassLoader extends ClassLoader {
|
||||
|
||||
}
|
||||
}
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service.advisor;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.alibaba.bytekit.asm.MethodProcessor;
|
||||
import com.alibaba.bytekit.asm.interceptor.InterceptorProcessor;
|
||||
import com.alibaba.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
|
||||
import com.alibaba.bytekit.asm.location.Location;
|
||||
import com.alibaba.bytekit.asm.location.LocationType;
|
||||
import com.alibaba.bytekit.asm.location.MethodInsnNodeWare;
|
||||
import com.alibaba.bytekit.asm.location.filter.GroupLocationFilter;
|
||||
import com.alibaba.bytekit.asm.location.filter.InvokeCheckLocationFilter;
|
||||
import com.alibaba.bytekit.asm.location.filter.InvokeContainLocationFilter;
|
||||
import com.alibaba.bytekit.asm.location.filter.LocationFilter;
|
||||
import com.alibaba.bytekit.utils.AsmOpUtils;
|
||||
import com.alibaba.bytekit.utils.AsmUtils;
|
||||
import com.alibaba.deps.org.objectweb.asm.ClassReader;
|
||||
import com.alibaba.deps.org.objectweb.asm.Opcodes;
|
||||
import com.alibaba.deps.org.objectweb.asm.Type;
|
||||
import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import com.alibaba.deps.org.objectweb.asm.tree.ClassNode;
|
||||
import com.alibaba.deps.org.objectweb.asm.tree.MethodInsnNode;
|
||||
import com.alibaba.deps.org.objectweb.asm.tree.MethodNode;
|
||||
import com.taobao.arthas.common.Pair;
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.advisor.SpyInterceptors.*;
|
||||
import com.taobao.arthas.core.util.ArthasCheckUtils;
|
||||
import com.taobao.arthas.core.util.ClassUtils;
|
||||
import com.taobao.arthas.core.util.FileUtils;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.grpcweb.grpc.DemoBootstrap;
|
||||
|
||||
import java.arthas.SpyAPI;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.*;
|
||||
|
||||
import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
|
||||
import static java.lang.System.arraycopy;
|
||||
|
||||
/**
|
||||
* 对类进行通知增强 Created by vlinux on 15/5/17.
|
||||
* @author hengyunabc
|
||||
*/
|
||||
public class Enhancer implements ClassFileTransformer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Enhancer.class);
|
||||
|
||||
private final AdviceListener listener;
|
||||
private final boolean isTracing;
|
||||
private final boolean skipJDKTrace;
|
||||
private final Matcher classNameMatcher;
|
||||
private final Matcher classNameExcludeMatcher;
|
||||
private final Matcher methodNameMatcher;
|
||||
private final EnhancerAffect affect;
|
||||
private Set<Class<?>> matchingClasses = null;
|
||||
private static final ClassLoader selfClassLoader = Enhancer.class.getClassLoader();
|
||||
|
||||
// 被增强的类的缓存
|
||||
private final static Map<Class<?>/* Class */, Object> classBytesCache = new WeakHashMap<Class<?>, Object>();
|
||||
private static SpyImpl spyImpl = new SpyImpl();
|
||||
|
||||
static {
|
||||
SpyAPI.setSpy(spyImpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param adviceId 通知编号
|
||||
* @param isTracing 可跟踪方法调用
|
||||
* @param skipJDKTrace 是否忽略对JDK内部方法的跟踪
|
||||
* @param matchingClasses 匹配中的类
|
||||
* @param methodNameMatcher 方法名匹配
|
||||
* @param affect 影响统计
|
||||
*/
|
||||
public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher,
|
||||
Matcher classNameExcludeMatcher,
|
||||
Matcher methodNameMatcher) {
|
||||
this.listener = listener;
|
||||
this.isTracing = isTracing;
|
||||
this.skipJDKTrace = skipJDKTrace;
|
||||
this.classNameMatcher = classNameMatcher;
|
||||
this.classNameExcludeMatcher = classNameExcludeMatcher;
|
||||
this.methodNameMatcher = methodNameMatcher;
|
||||
this.affect = new EnhancerAffect();
|
||||
affect.setListenerId(listener.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(final ClassLoader inClassLoader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
try {
|
||||
// 检查classloader能否加载到 SpyAPI,如果不能,则放弃增强
|
||||
try {
|
||||
if (inClassLoader != null) {
|
||||
inClassLoader.loadClass(SpyAPI.class.getName());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("the classloader can not load SpyAPI, ignore it. classloader: {}, className: {}",
|
||||
inClassLoader.getClass().getName(), className, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 这里要再次过滤一次,为啥?因为在transform的过程中,有可能还会再诞生新的类
|
||||
// 所以需要将之前需要转换的类集合传递下来,再次进行判断
|
||||
if (matchingClasses != null && !matchingClasses.contains(classBeingRedefined)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//keep origin class reader for bytecode optimizations, avoiding JVM metaspace OOM.
|
||||
ClassNode classNode = new ClassNode(Opcodes.ASM9);
|
||||
ClassReader classReader = AsmUtils.toClassNode(classfileBuffer, classNode);
|
||||
// remove JSR https://github.com/alibaba/arthas/issues/1304
|
||||
classNode = AsmUtils.removeJSRInstructions(classNode);
|
||||
|
||||
// 生成增强字节码
|
||||
DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
|
||||
|
||||
final List<InterceptorProcessor> interceptorProcessors = new ArrayList<InterceptorProcessor>();
|
||||
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor1.class));
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor2.class));
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor3.class));
|
||||
|
||||
if (this.isTracing) {
|
||||
if (!this.skipJDKTrace) {
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor1.class));
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor2.class));
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor3.class));
|
||||
} else {
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor1.class));
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor2.class));
|
||||
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor3.class));
|
||||
}
|
||||
}
|
||||
|
||||
List<MethodNode> matchedMethods = new ArrayList<MethodNode>();
|
||||
for (MethodNode methodNode : classNode.methods) {
|
||||
if (!isIgnore(methodNode, methodNameMatcher)) {
|
||||
matchedMethods.add(methodNode);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/alibaba/arthas/issues/1690
|
||||
if (AsmUtils.isEnhancerByCGLIB(className)) {
|
||||
for (MethodNode methodNode : matchedMethods) {
|
||||
if (AsmUtils.isConstructor(methodNode)) {
|
||||
AsmUtils.fixConstructorExceptionTable(methodNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用于检查是否已插入了 spy函数,如果已有则不重复处理
|
||||
GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
|
||||
|
||||
LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter",
|
||||
LocationType.ENTER);
|
||||
LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit",
|
||||
LocationType.EXIT);
|
||||
LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class),
|
||||
"atExceptionExit", LocationType.EXCEPTION_EXIT);
|
||||
|
||||
groupLocationFilter.addFilter(enterFilter);
|
||||
groupLocationFilter.addFilter(existFilter);
|
||||
groupLocationFilter.addFilter(exceptionFilter);
|
||||
|
||||
LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
|
||||
"atBeforeInvoke", LocationType.INVOKE);
|
||||
LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
|
||||
"atInvokeException", LocationType.INVOKE_COMPLETED);
|
||||
LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
|
||||
"atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT);
|
||||
groupLocationFilter.addFilter(invokeBeforeFilter);
|
||||
groupLocationFilter.addFilter(invokeAfterFilter);
|
||||
groupLocationFilter.addFilter(invokeExceptionFilter);
|
||||
|
||||
for (MethodNode methodNode : matchedMethods) {
|
||||
if (AsmUtils.isNative(methodNode)) {
|
||||
logger.info("ignore native method: {}",
|
||||
AsmUtils.methodDeclaration(Type.getObjectType(classNode.name), methodNode));
|
||||
continue;
|
||||
}
|
||||
// 先查找是否有 atBeforeInvoke 函数,如果有,则说明已经有trace了,则直接不再尝试增强,直接插入 listener
|
||||
if(AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) {
|
||||
for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
|
||||
.getNext()) {
|
||||
if (insnNode instanceof MethodInsnNode) {
|
||||
final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
|
||||
if(this.skipJDKTrace) {
|
||||
if(methodInsnNode.owner.startsWith("java/")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 原始类型的box类型相关的都跳过
|
||||
if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))) {
|
||||
continue;
|
||||
}
|
||||
AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
|
||||
methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
|
||||
for (InterceptorProcessor interceptor : interceptorProcessors) {
|
||||
try {
|
||||
List<Location> locations = interceptor.process(methodProcessor);
|
||||
for (Location location : locations) {
|
||||
if (location instanceof MethodInsnNodeWare) {
|
||||
MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location;
|
||||
MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode();
|
||||
|
||||
AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
|
||||
methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enter/exist 总是要插入 listener
|
||||
AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc,
|
||||
listener);
|
||||
affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc);
|
||||
}
|
||||
|
||||
// https://github.com/alibaba/arthas/issues/1223 , V1_5 的major version是49
|
||||
if (AsmUtils.getMajorVersion(classNode.version) < 49) {
|
||||
classNode.version = AsmUtils.setMajorVersion(classNode.version, 49);
|
||||
}
|
||||
|
||||
byte[] enhanceClassByteArray = AsmUtils.toBytes(classNode, inClassLoader, classReader);
|
||||
|
||||
// 增强成功,记录类
|
||||
classBytesCache.put(classBeingRedefined, new Object());
|
||||
|
||||
// dump the class
|
||||
dumpClassIfNecessary(className, enhanceClassByteArray, affect);
|
||||
|
||||
// 成功计数
|
||||
affect.cCnt(1);
|
||||
|
||||
return enhanceClassByteArray;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t);
|
||||
affect.setThrowable(t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否抽象属性
|
||||
*/
|
||||
private boolean isAbstract(int access) {
|
||||
return (Opcodes.ACC_ABSTRACT & access) == Opcodes.ACC_ABSTRACT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要忽略
|
||||
*/
|
||||
private boolean isIgnore(MethodNode methodNode, Matcher methodNameMatcher) {
|
||||
return null == methodNode || isAbstract(methodNode.access) || !methodNameMatcher.matching(methodNode.name)
|
||||
|| ArthasCheckUtils.isEquals(methodNode.name, "<clinit>");
|
||||
}
|
||||
|
||||
/**
|
||||
* dump class to file
|
||||
*/
|
||||
private static void dumpClassIfNecessary(String className, byte[] data, EnhancerAffect affect) {
|
||||
if (!GlobalOptions.isDump) {
|
||||
return;
|
||||
}
|
||||
final File dumpClassFile = new File("./arthas-class-dump/" + className + ".class");
|
||||
final File classPath = new File(dumpClassFile.getParent());
|
||||
|
||||
// 创建类所在的包路径
|
||||
if (!classPath.mkdirs() && !classPath.exists()) {
|
||||
logger.warn("create dump classpath:{} failed.", classPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 将类字节码写入文件
|
||||
try {
|
||||
FileUtils.writeByteArrayToFile(dumpClassFile, data);
|
||||
affect.addClassDumpFile(dumpClassFile);
|
||||
if (GlobalOptions.verbose) {
|
||||
logger.info("dump enhanced class: {}, path: {}", className, dumpClassFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("dump class:{} to file {} failed.", className, dumpClassFile, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要过滤的类
|
||||
*
|
||||
* @param classes 类集合
|
||||
*/
|
||||
private List<Pair<Class<?>, String>> filter(Set<Class<?>> classes) {
|
||||
List<Pair<Class<?>, String>> filteredClasses = new ArrayList<Pair<Class<?>, String>>();
|
||||
final Iterator<Class<?>> it = classes.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Class<?> clazz = it.next();
|
||||
boolean removeFlag = false;
|
||||
if (null == clazz) {
|
||||
removeFlag = true;
|
||||
}
|
||||
// else if (isSelf(clazz)) {
|
||||
// filteredClasses.add(new Pair<Class<?>, String>(clazz, "class loaded by arthas itself"));
|
||||
// removeFlag = true;
|
||||
// }
|
||||
else if (isUnsafeClass(clazz)) {
|
||||
filteredClasses.add(new Pair<Class<?>, String>(clazz, "class loaded by Bootstrap Classloader, try to execute `options unsafe true`"));
|
||||
removeFlag = true;
|
||||
} else if (isExclude(clazz)) {
|
||||
filteredClasses.add(new Pair<Class<?>, String>(clazz, "class is excluded"));
|
||||
removeFlag = true;
|
||||
} else {
|
||||
Pair<Boolean, String> unsupportedResult = isUnsupportedClass(clazz);
|
||||
if (unsupportedResult.getFirst()) {
|
||||
filteredClasses.add(new Pair<Class<?>, String>(clazz, unsupportedResult.getSecond()));
|
||||
removeFlag = true;
|
||||
}
|
||||
}
|
||||
if (removeFlag) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return filteredClasses;
|
||||
}
|
||||
|
||||
private boolean isExclude(Class<?> clazz) {
|
||||
if (this.classNameExcludeMatcher != null) {
|
||||
return classNameExcludeMatcher.matching(clazz.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤Arthas加载的类
|
||||
*/
|
||||
private static boolean isSelf(Class<?> clazz) {
|
||||
return null != clazz && isEquals(clazz.getClassLoader(), selfClassLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤unsafe类
|
||||
*/
|
||||
private static boolean isUnsafeClass(Class<?> clazz) {
|
||||
return !GlobalOptions.isUnsafe && clazz.getClassLoader() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤目前暂不支持的类
|
||||
*/
|
||||
private static Pair<Boolean, String> isUnsupportedClass(Class<?> clazz) {
|
||||
if (ClassUtils.isLambdaClass(clazz)) {
|
||||
return new Pair<Boolean, String>(Boolean.TRUE, "class is lambda");
|
||||
}
|
||||
|
||||
if (clazz.isInterface() && !GlobalOptions.isSupportDefaultMethod) {
|
||||
return new Pair<Boolean, String>(Boolean.TRUE, "class is interface");
|
||||
}
|
||||
|
||||
if (clazz.equals(Integer.class)) {
|
||||
return new Pair<Boolean, String>(Boolean.TRUE, "class is java.lang.Integer");
|
||||
}
|
||||
|
||||
if (clazz.equals(Class.class)) {
|
||||
return new Pair<Boolean, String>(Boolean.TRUE, "class is java.lang.Class");
|
||||
}
|
||||
|
||||
if (clazz.equals(Method.class)) {
|
||||
return new Pair<Boolean, String>(Boolean.TRUE, "class is java.lang.Method");
|
||||
}
|
||||
|
||||
if (clazz.isArray()) {
|
||||
return new Pair<Boolean, String>(Boolean.TRUE, "class is array");
|
||||
}
|
||||
return new Pair<Boolean, String>(Boolean.FALSE, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象增强
|
||||
*
|
||||
* @param inst inst
|
||||
* @param maxNumOfMatchedClass 匹配的class最大数量
|
||||
* @return 增强影响范围
|
||||
* @throws UnmodifiableClassException 增强失败
|
||||
*/
|
||||
public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNumOfMatchedClass) throws UnmodifiableClassException {
|
||||
// 获取需要增强的类集合
|
||||
this.matchingClasses = GlobalOptions.isDisableSubClass
|
||||
? SearchUtils.searchClass(inst, classNameMatcher)
|
||||
: SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher));
|
||||
|
||||
if (matchingClasses.size() > maxNumOfMatchedClass) {
|
||||
affect.setOverLimitMsg("The number of matched classes is " +matchingClasses.size()+ ", greater than the limit value " + maxNumOfMatchedClass + ". Try to change the limit with option '-m <arg>'.");
|
||||
return affect;
|
||||
}
|
||||
// 过滤掉无法被增强的类
|
||||
List<Pair<Class<?>, String>> filtedList = filter(matchingClasses);
|
||||
if (!filtedList.isEmpty()) {
|
||||
for (Pair<Class<?>, String> filted : filtedList) {
|
||||
logger.info("ignore class: {}, reason: {}", filted.getFirst().getName(), filted.getSecond());
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("enhance matched classes: {}", matchingClasses);
|
||||
|
||||
affect.setTransformer(this);
|
||||
|
||||
try {
|
||||
DemoBootstrap.getRunningInstance().getTransformerManager().addTransformer(this, isTracing);
|
||||
|
||||
// 批量增强
|
||||
if (GlobalOptions.isBatchReTransform) {
|
||||
final int size = matchingClasses.size();
|
||||
final Class<?>[] classArray = new Class<?>[size];
|
||||
arraycopy(matchingClasses.toArray(), 0, classArray, 0, size);
|
||||
if (classArray.length > 0) {
|
||||
inst.retransformClasses(classArray);
|
||||
logger.info("Success to batch transform classes: " + Arrays.toString(classArray));
|
||||
}
|
||||
} else {
|
||||
// for each 增强
|
||||
for (Class<?> clazz : matchingClasses) {
|
||||
try {
|
||||
inst.retransformClasses(clazz);
|
||||
logger.info("Success to transform class: " + clazz);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("retransform {} failed.", clazz, t);
|
||||
if (t instanceof UnmodifiableClassException) {
|
||||
throw (UnmodifiableClassException) t;
|
||||
} else if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
} else {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("Enhancer error, matchingClasses: {}", matchingClasses, e);
|
||||
affect.setThrowable(e);
|
||||
}
|
||||
|
||||
return affect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置指定的Class
|
||||
*
|
||||
* @param inst inst
|
||||
* @param classNameMatcher 类名匹配
|
||||
* @return 增强影响范围
|
||||
* @throws UnmodifiableClassException
|
||||
*/
|
||||
public static synchronized EnhancerAffect reset(final Instrumentation inst, final Matcher classNameMatcher)
|
||||
throws UnmodifiableClassException {
|
||||
|
||||
final EnhancerAffect affect = new EnhancerAffect();
|
||||
final Set<Class<?>> enhanceClassSet = new HashSet<Class<?>>();
|
||||
|
||||
for (Class<?> classInCache : classBytesCache.keySet()) {
|
||||
if (classNameMatcher.matching(classInCache.getName())) {
|
||||
enhanceClassSet.add(classInCache);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
enhance(inst, enhanceClassSet);
|
||||
logger.info("Success to reset classes: " + enhanceClassSet);
|
||||
} finally {
|
||||
for (Class<?> resetClass : enhanceClassSet) {
|
||||
classBytesCache.remove(resetClass);
|
||||
affect.cCnt(1);
|
||||
}
|
||||
}
|
||||
|
||||
return affect;
|
||||
}
|
||||
|
||||
// 批量增强
|
||||
private static void enhance(Instrumentation inst, Set<Class<?>> classes)
|
||||
throws UnmodifiableClassException {
|
||||
int size = classes.size();
|
||||
Class<?>[] classArray = new Class<?>[size];
|
||||
arraycopy(classes.toArray(), 0, classArray, 0, size);
|
||||
if (classArray.length > 0) {
|
||||
inst.retransformClasses(classArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service.advisor;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.advisor.InvokeTraceable;
|
||||
import com.taobao.arthas.core.shell.system.ExecStatus;
|
||||
import com.taobao.arthas.core.shell.system.ProcessAware;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import java.arthas.SpyAPI.AbstractSpy;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 怎么从 className|methodDesc 到 id 对应起来??
|
||||
* 当id少时,可以id自己来判断是否符合?
|
||||
*
|
||||
* 如果是每个 className|methodDesc 为 key ,是否
|
||||
* </pre>
|
||||
*
|
||||
* @author hengyunabc 2020-04-24
|
||||
*
|
||||
*/
|
||||
public class SpyImpl extends AbstractSpy {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpyImpl.class);
|
||||
|
||||
@Override
|
||||
public void atEnter(Class<?> clazz, String methodInfo, Object target, Object[] args) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
|
||||
String[] info = StringUtils.splitMethodInfo(methodInfo);
|
||||
String methodName = info[0];
|
||||
String methodDesc = info[1];
|
||||
// TODO listener 只用查一次,放到 thread local里保存起来就可以了!
|
||||
List<AdviceListener> listeners = com.taobao.arthas.grpcweb.grpc.service.advisor.AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
|
||||
methodName, methodDesc);
|
||||
if (listeners != null) {
|
||||
for (AdviceListener adviceListener : listeners) {
|
||||
try {
|
||||
if (skipAdviceListener(adviceListener)) {
|
||||
continue;
|
||||
}
|
||||
adviceListener.before(clazz, methodName, methodDesc, target, args);
|
||||
} catch (Throwable e) {
|
||||
logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atExit(Class<?> clazz, String methodInfo, Object target, Object[] args, Object returnObject) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
|
||||
String[] info = StringUtils.splitMethodInfo(methodInfo);
|
||||
String methodName = info[0];
|
||||
String methodDesc = info[1];
|
||||
|
||||
List<AdviceListener> listeners = com.taobao.arthas.grpcweb.grpc.service.advisor.AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
|
||||
methodName, methodDesc);
|
||||
if (listeners != null) {
|
||||
for (AdviceListener adviceListener : listeners) {
|
||||
try {
|
||||
if (skipAdviceListener(adviceListener)) {
|
||||
continue;
|
||||
}
|
||||
adviceListener.afterReturning(clazz, methodName, methodDesc, target, args, returnObject);
|
||||
} catch (Throwable e) {
|
||||
logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atExceptionExit(Class<?> clazz, String methodInfo, Object target, Object[] args, Throwable throwable) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
|
||||
String[] info = StringUtils.splitMethodInfo(methodInfo);
|
||||
String methodName = info[0];
|
||||
String methodDesc = info[1];
|
||||
|
||||
List<AdviceListener> listeners = com.taobao.arthas.grpcweb.grpc.service.advisor.AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
|
||||
methodName, methodDesc);
|
||||
if (listeners != null) {
|
||||
for (AdviceListener adviceListener : listeners) {
|
||||
try {
|
||||
if (skipAdviceListener(adviceListener)) {
|
||||
continue;
|
||||
}
|
||||
adviceListener.afterThrowing(clazz, methodName, methodDesc, target, args, throwable);
|
||||
} catch (Throwable e) {
|
||||
logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atBeforeInvoke(Class<?> clazz, String invokeInfo, Object target) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
String[] info = StringUtils.splitInvokeInfo(invokeInfo);
|
||||
String owner = info[0];
|
||||
String methodName = info[1];
|
||||
String methodDesc = info[2];
|
||||
|
||||
List<AdviceListener> listeners = com.taobao.arthas.grpcweb.grpc.service.advisor.AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
|
||||
owner, methodName, methodDesc);
|
||||
|
||||
if (listeners != null) {
|
||||
for (AdviceListener adviceListener : listeners) {
|
||||
try {
|
||||
if (skipAdviceListener(adviceListener)) {
|
||||
continue;
|
||||
}
|
||||
final InvokeTraceable listener = (InvokeTraceable) adviceListener;
|
||||
listener.invokeBeforeTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3]));
|
||||
} catch (Throwable e) {
|
||||
logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atAfterInvoke(Class<?> clazz, String invokeInfo, Object target) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
String[] info = StringUtils.splitInvokeInfo(invokeInfo);
|
||||
String owner = info[0];
|
||||
String methodName = info[1];
|
||||
String methodDesc = info[2];
|
||||
List<AdviceListener> listeners = com.taobao.arthas.grpcweb.grpc.service.advisor.AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
|
||||
owner, methodName, methodDesc);
|
||||
|
||||
if (listeners != null) {
|
||||
for (AdviceListener adviceListener : listeners) {
|
||||
try {
|
||||
if (skipAdviceListener(adviceListener)) {
|
||||
continue;
|
||||
}
|
||||
final InvokeTraceable listener = (InvokeTraceable) adviceListener;
|
||||
listener.invokeAfterTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3]));
|
||||
} catch (Throwable e) {
|
||||
logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atInvokeException(Class<?> clazz, String invokeInfo, Object target, Throwable throwable) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
String[] info = StringUtils.splitInvokeInfo(invokeInfo);
|
||||
String owner = info[0];
|
||||
String methodName = info[1];
|
||||
String methodDesc = info[2];
|
||||
|
||||
List<AdviceListener> listeners = com.taobao.arthas.grpcweb.grpc.service.advisor.AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
|
||||
owner, methodName, methodDesc);
|
||||
|
||||
if (listeners != null) {
|
||||
for (AdviceListener adviceListener : listeners) {
|
||||
try {
|
||||
if (skipAdviceListener(adviceListener)) {
|
||||
continue;
|
||||
}
|
||||
final InvokeTraceable listener = (InvokeTraceable) adviceListener;
|
||||
listener.invokeThrowTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3]));
|
||||
} catch (Throwable e) {
|
||||
logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean skipAdviceListener(AdviceListener adviceListener) {
|
||||
if (adviceListener instanceof ProcessAware) {
|
||||
ProcessAware processAware = (ProcessAware) adviceListener;
|
||||
ExecStatus status = processAware.getProcess().status();
|
||||
if (status.equals(ExecStatus.TERMINATED) || status.equals(ExecStatus.STOPPED)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service.advisor;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.core.advisor.AccessPoint;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.AdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.command.model.MessageModel;
|
||||
import com.taobao.arthas.core.command.model.ObjectVO;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.ThreadLocalWatch;
|
||||
import com.taobao.arthas.grpcweb.grpc.model.WatchRequestModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.model.WatchResponseModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class WatchRpcAdviceListener extends AdviceListenerAdapter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WatchRpcAdviceListener.class);
|
||||
private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
|
||||
|
||||
private final AtomicInteger idGenerator = new AtomicInteger(0);
|
||||
|
||||
private Map<Long/*RESULT_ID*/, Object> results = new HashMap<>();
|
||||
private WatchRequestModel watchRequestModel;
|
||||
|
||||
private ArthasStreamObserver arthasStreamObserver;
|
||||
|
||||
public WatchRpcAdviceListener(ArthasStreamObserver arthasStreamObserver, boolean verbose) {
|
||||
this.arthasStreamObserver = arthasStreamObserver;
|
||||
this.watchRequestModel = (WatchRequestModel) arthasStreamObserver.getRequestModel();
|
||||
super.setVerbose(verbose);
|
||||
}
|
||||
|
||||
public void setArthasStreamObserver(ArthasStreamObserver arthasStreamObserver) {
|
||||
this.arthasStreamObserver = arthasStreamObserver;
|
||||
this.watchRequestModel = (WatchRequestModel) arthasStreamObserver.getRequestModel();
|
||||
}
|
||||
|
||||
private boolean isFinish() {
|
||||
return watchRequestModel.isFinish() || !watchRequestModel.isBefore() && !watchRequestModel.isException() && !watchRequestModel.isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
// 开始计算本次方法调用耗时
|
||||
threadLocalWatch.start();
|
||||
if (watchRequestModel.isBefore()) {
|
||||
watching(Advice.newForBefore(loader, clazz, method, target, args));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
Advice advice = Advice.newForAfterReturning(loader, clazz, method, target, args, returnObject);
|
||||
if (watchRequestModel.isSuccess()) {
|
||||
watching(advice);
|
||||
}
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Throwable throwable) {
|
||||
Advice advice = Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable);
|
||||
if (watchRequestModel.isException()) {
|
||||
watching(advice);
|
||||
}
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
private void finishing(Advice advice) {
|
||||
if (isFinish()) {
|
||||
watching(advice);
|
||||
}
|
||||
}
|
||||
|
||||
private void watching(Advice advice) {
|
||||
try {
|
||||
// 本次调用的耗时
|
||||
System.out.println("************job: "+ arthasStreamObserver.getJobId() + " rpc watch advice开始正式执行,执行信息如下*****************");
|
||||
System.out.println("listener ID: + " + arthasStreamObserver.getListener().id());
|
||||
System.out.println("参数: \n" + watchRequestModel.toString());
|
||||
System.out.println("###################***************** \n\n");
|
||||
double cost = threadLocalWatch.costInMillis();
|
||||
boolean conditionResult = isConditionMet(watchRequestModel.getConditionExpress(), advice, cost);
|
||||
if (this.isVerbose()) {
|
||||
String msg = "Condition express: " + watchRequestModel.getConditionExpress() + " , result: " + conditionResult + "\n";
|
||||
arthasStreamObserver.appendResult(new MessageModel(msg));
|
||||
}
|
||||
if (conditionResult) {
|
||||
long resultId = idGenerator.incrementAndGet();
|
||||
results.put(resultId, advice);
|
||||
Object value = getExpressionResult(watchRequestModel.getExpress(), advice, cost);
|
||||
|
||||
WatchResponseModel model = new WatchResponseModel();
|
||||
model.setResultId(resultId);
|
||||
model.setTs(LocalDateTime.now());
|
||||
model.setCost(cost);
|
||||
model.setValue(new ObjectVO(value, watchRequestModel.getExpand()));
|
||||
model.setSizeLimit(watchRequestModel.getSizeLimit());
|
||||
model.setClassName(advice.getClazz().getName());
|
||||
model.setMethodName(advice.getMethod().getName());
|
||||
if (advice.isBefore()) {
|
||||
model.setAccessPoint(AccessPoint.ACCESS_BEFORE.getKey());
|
||||
} else if (advice.isAfterReturning()) {
|
||||
model.setAccessPoint(AccessPoint.ACCESS_AFTER_RETUNING.getKey());
|
||||
} else if (advice.isAfterThrowing()) {
|
||||
model.setAccessPoint(AccessPoint.ACCESS_AFTER_THROWING.getKey());
|
||||
}
|
||||
arthasStreamObserver.appendResult(model);
|
||||
arthasStreamObserver.times().incrementAndGet();
|
||||
if (isLimitExceeded(watchRequestModel.getNumberOfLimit(), arthasStreamObserver.times().get())) {
|
||||
String msg = "Command execution times exceed limit: " + watchRequestModel.getNumberOfLimit()
|
||||
+ ", so command will exit.\n";
|
||||
arthasStreamObserver.end();
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.warn("watch failed.", e);
|
||||
arthasStreamObserver.end(-1, "watch failed, condition is: " + watchRequestModel.getConditionExpress() + ", express is: "
|
||||
+ watchRequestModel.getExpress() + ", " + e.getMessage() + ", visit " + LogUtil.loggingFile()
|
||||
+ " for more details.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import com.taobao.arthas.core.command.model.EnhancerModel;
|
||||
import com.taobao.arthas.core.command.view.ViewRenderUtil;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
/**
|
||||
* Term grpc view for EnhancerModel
|
||||
* @author xuyang 2023/8/15
|
||||
*/
|
||||
public class GrpcEnhancerView extends GrpcResultView<EnhancerModel> {
|
||||
@Override
|
||||
public void draw(ArthasStreamObserver arthasStreamObserver, EnhancerModel result) {
|
||||
if (result.getEffect() != null) {
|
||||
String msg = ViewRenderUtil.renderEnhancerAffect(result.getEffect());
|
||||
ResponseBody responseBody = ResponseBody.newBuilder()
|
||||
.setJobId(result.getJobId())
|
||||
.setType(result.getType())
|
||||
.setStringValue(msg)
|
||||
.build();
|
||||
arthasStreamObserver.onNext(responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import com.taobao.arthas.core.command.model.MessageModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
public class GrpcMessageView extends GrpcResultView<MessageModel> {
|
||||
@Override
|
||||
public void draw(ArthasStreamObserver arthasStreamObserver, MessageModel result) {
|
||||
ResponseBody responseBody = ResponseBody.newBuilder()
|
||||
.setJobId(result.getJobId())
|
||||
.setType(result.getType())
|
||||
.setStringValue(result.getMessage())
|
||||
.build();
|
||||
arthasStreamObserver.onNext(responseBody);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import io.arthas.api.ArthasServices.StringStringMapValue;
|
||||
import com.taobao.arthas.core.command.model.PwdModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
/**
|
||||
* @author xuyang 2023/8/15
|
||||
*/
|
||||
public class GrpcPwdView extends GrpcResultView<PwdModel> {
|
||||
|
||||
|
||||
@Override
|
||||
public void draw(ArthasStreamObserver arthasStreamObserver, PwdModel result) {
|
||||
StringStringMapValue stringStringMapValue = StringStringMapValue.newBuilder()
|
||||
.putStringStringMap("workingDir", result.getWorkingDir()).build();
|
||||
ResponseBody responseBody = ResponseBody.newBuilder()
|
||||
.setJobId(result.getJobId())
|
||||
.setType(result.getType())
|
||||
.setStringStringMapValue(stringStringMapValue)
|
||||
.build();
|
||||
arthasStreamObserver.onNext(responseBody);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import com.taobao.arthas.core.command.model.ResultModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
/**
|
||||
* Command result view for grpc client.
|
||||
* Note: Result view is a reusable and stateless instance
|
||||
*
|
||||
* @author xuyang 2023/8/15
|
||||
*/
|
||||
public abstract class GrpcResultView<T extends ResultModel> {
|
||||
|
||||
/**
|
||||
* formatted printing data to grpc client
|
||||
*
|
||||
* @param arthasStreamObserver
|
||||
*/
|
||||
public abstract void draw(ArthasStreamObserver arthasStreamObserver, T result);
|
||||
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.core.command.model.ResultModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Result view resolver for term
|
||||
*
|
||||
* @author xuyang 2023/8/15
|
||||
*/
|
||||
public class GrpcResultViewResolver {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GrpcResultViewResolver.class);
|
||||
|
||||
// modelClass -> view
|
||||
private Map<Class, GrpcResultView> resultViewMap = new ConcurrentHashMap<Class, GrpcResultView>();
|
||||
|
||||
public GrpcResultViewResolver() {
|
||||
initResultViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要调用此方法初始化注册ResultView
|
||||
*/
|
||||
private void initResultViews() {
|
||||
try {
|
||||
// registerView(RowAffectView.class);
|
||||
|
||||
//basic1000
|
||||
registerView(GrpcStatusView.class);
|
||||
// registerView(VersionView.class);
|
||||
registerView(GrpcMessageView.class);
|
||||
// registerView(HelpView.class);
|
||||
//registerView(HistoryView.class);
|
||||
// registerView(EchoView.class);
|
||||
// registerView(CatView.class);
|
||||
// registerView(Base64View.class);
|
||||
// registerView(OptionsView.class);
|
||||
registerView(GrpcSystemPropertyView.class);
|
||||
// registerView(SystemEnvView.class);
|
||||
registerView(GrpcPwdView.class);
|
||||
// registerView(VMOptionView.class);
|
||||
// registerView(SessionView.class);
|
||||
// registerView(ResetView.class);
|
||||
// registerView(ShutdownView.class);
|
||||
|
||||
//klass100
|
||||
// registerView(ClassLoaderView.class);
|
||||
// registerView(DumpClassView.class);
|
||||
// registerView(GetStaticView.class);
|
||||
// registerView(JadView.class);
|
||||
// registerView(MemoryCompilerView.class);
|
||||
// registerView(OgnlView.class);
|
||||
// registerView(RedefineView.class);
|
||||
// registerView(RetransformView.class);
|
||||
// registerView(SearchClassView.class);
|
||||
// registerView(SearchMethodView.class);
|
||||
|
||||
//logger
|
||||
// registerView(LoggerView.class);
|
||||
|
||||
//monitor2000
|
||||
// registerView(DashboardView.class);
|
||||
// registerView(JvmView.class);
|
||||
// registerView(MemoryView.class);
|
||||
// registerView(MBeanView.class);
|
||||
// registerView(PerfCounterView.class);
|
||||
// registerView(ThreadView.class);
|
||||
// registerView(ProfilerView.class);
|
||||
registerView(GrpcEnhancerView.class);
|
||||
// registerView(MonitorView.class);
|
||||
// registerView(StackView.class);
|
||||
// registerView(TimeTunnelView.class);
|
||||
// registerView(TraceView.class);
|
||||
registerView(GrpcWatchView.class);
|
||||
// registerView(VmToolView.class);
|
||||
// registerView(JFRView.class);
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.error("register result view failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public GrpcResultView getResultView(ResultModel model) {
|
||||
return resultViewMap.get(model.getClass());
|
||||
}
|
||||
|
||||
public GrpcResultViewResolver registerView(Class modelClass, GrpcResultView view) {
|
||||
//TODO 检查model的type是否重复,避免复制代码带来的bug
|
||||
this.resultViewMap.put(modelClass, view);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GrpcResultViewResolver registerView(GrpcResultView view) {
|
||||
Class modelClass = getModelClass(view);
|
||||
if (modelClass == null) {
|
||||
throw new NullPointerException("model class is null");
|
||||
}
|
||||
return this.registerView(modelClass, view);
|
||||
}
|
||||
|
||||
public void registerView(Class<? extends GrpcResultView> viewClass) {
|
||||
GrpcResultView view = null;
|
||||
try {
|
||||
view = viewClass.newInstance();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("create view instance failure, viewClass:" + viewClass, e);
|
||||
}
|
||||
this.registerView(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model class of result view
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static <V extends GrpcResultView> Class getModelClass(V view) {
|
||||
//类反射获取子类的draw方法第二个参数的ResultModel具体类型
|
||||
Class<? extends GrpcResultView> viewClass = view.getClass();
|
||||
Method[] declaredMethods = viewClass.getDeclaredMethods();
|
||||
for (int i = 0; i < declaredMethods.length; i++) {
|
||||
Method method = declaredMethods[i];
|
||||
if (method.getName().equals("draw")) {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
if (parameterTypes.length == 2
|
||||
&& parameterTypes[0] == ArthasStreamObserver.class
|
||||
&& parameterTypes[1] != ResultModel.class
|
||||
&& ResultModel.class.isAssignableFrom(parameterTypes[1])) {
|
||||
return parameterTypes[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import com.taobao.arthas.core.command.model.StatusModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
/**
|
||||
* @author xuyang 2023/8/15
|
||||
*/
|
||||
public class GrpcStatusView extends GrpcResultView<StatusModel> {
|
||||
|
||||
@Override
|
||||
public void draw(ArthasStreamObserver arthasStreamObserver, StatusModel result) {
|
||||
if (result.getMessage() != null) {
|
||||
ResponseBody responseBody = ResponseBody.newBuilder()
|
||||
.setJobId(result.getJobId())
|
||||
.setType(result.getType())
|
||||
.setStringValue(result.getMessage())
|
||||
.build();
|
||||
arthasStreamObserver.onNext(responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import io.arthas.api.ArthasServices.StringStringMapValue;
|
||||
import com.taobao.arthas.core.command.model.SystemPropertyModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
public class GrpcSystemPropertyView extends GrpcResultView<SystemPropertyModel>{
|
||||
|
||||
@Override
|
||||
public void draw(ArthasStreamObserver arthasStreamObserver, SystemPropertyModel result) {
|
||||
StringStringMapValue stringStringMapValue = StringStringMapValue.newBuilder()
|
||||
.putAllStringStringMap(result.getProps()).build();
|
||||
ResponseBody responseBody = ResponseBody.newBuilder()
|
||||
.setJobId(result.getJobId())
|
||||
.setType(result.getType())
|
||||
.setStringStringMapValue(stringStringMapValue)
|
||||
.build();
|
||||
arthasStreamObserver.onNext(responseBody);
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.view;
|
||||
|
||||
import com.taobao.arthas.core.view.ObjectView;
|
||||
import com.taobao.arthas.grpcweb.grpc.model.WatchRequestModel;
|
||||
import io.arthas.api.ArthasServices.JavaObject;
|
||||
import io.arthas.api.ArthasServices.ResponseBody;
|
||||
import io.arthas.api.ArthasServices.WatchResponse;
|
||||
import com.taobao.arthas.core.command.model.ObjectVO;
|
||||
import com.taobao.arthas.core.util.DateUtils;
|
||||
import com.taobao.arthas.grpcweb.grpc.model.WatchResponseModel;
|
||||
import com.taobao.arthas.grpcweb.grpc.observer.ArthasStreamObserver;
|
||||
|
||||
import static com.taobao.arthas.grpcweb.grpc.objectUtils.JavaObjectConverter.toJavaObjectWithExpand;
|
||||
|
||||
/**
|
||||
* Term view for WatchModel
|
||||
*
|
||||
* @author xuyang 2023/8/15
|
||||
*/
|
||||
public class GrpcWatchView extends GrpcResultView<WatchResponseModel> {
|
||||
|
||||
@Override
|
||||
public void draw(ArthasStreamObserver arthasStreamObserver, WatchResponseModel model) {
|
||||
ObjectVO objectVO = model.getValue();
|
||||
// Object obj = objectVO.needExpand() ? new ObjectView(model.getSizeLimit(), objectVO).draw() : objectVO.getObject();
|
||||
JavaObject javaObject = toJavaObjectWithExpand(objectVO.getObject(), objectVO.getExpand());
|
||||
WatchResponse watchResponse = WatchResponse.newBuilder()
|
||||
.setAccessPoint(model.getAccessPoint())
|
||||
.setClassName(model.getClassName())
|
||||
.setCost(model.getCost())
|
||||
.setMethodName(model.getMethodName())
|
||||
.setSizeLimit(model.getSizeLimit())
|
||||
.setTs(DateUtils.formatDateTime(model.getTs()))
|
||||
.setValue(javaObject)
|
||||
.build();
|
||||
ResponseBody responseBody = ResponseBody.newBuilder()
|
||||
.setJobId(model.getJobId())
|
||||
.setResultId(model.getResultId())
|
||||
.setType(model.getType())
|
||||
.setWatchResponse(watchResponse)
|
||||
.build();
|
||||
arthasStreamObserver.onNext(responseBody);
|
||||
}
|
||||
}
|
||||
+4
@@ -41,4 +41,8 @@ public class GrpcServiceConnectionManager {
|
||||
Channel getChannelWithClientInterceptor(GrpcWebClientInterceptor interceptor) {
|
||||
return ClientInterceptors.intercept(channel, interceptor);
|
||||
}
|
||||
|
||||
public ManagedChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-5
@@ -17,6 +17,7 @@ package com.taobao.arthas.grpcweb.proxy;
|
||||
|
||||
import com.taobao.arthas.common.Pair;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
@@ -94,10 +95,10 @@ public class GrpcWebRequestHandler {
|
||||
if (deframer.processInput(in, contentType)) {
|
||||
inObj = MessageUtils.getInputProtobufObj(asyncStubCall, deframer.getMessageBytes());
|
||||
}
|
||||
|
||||
ManagedChannel managedChannel = grpcServiceConnectionManager.getChannel();
|
||||
// Invoke the rpc call
|
||||
asyncStubCall.invoke(asyncStub, inObj, new GrpcCallResponseReceiver(sendResponse, latch));
|
||||
if (!latch.await(500 * 1000, TimeUnit.MILLISECONDS)) {
|
||||
asyncStubCall.invoke(asyncStub, inObj, new GrpcCallResponseReceiver(sendResponse, latch,managedChannel));
|
||||
if (!latch.await( 1000, TimeUnit.MILLISECONDS)) {
|
||||
logger.warn("grpc call took too long!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -155,16 +156,23 @@ public class GrpcWebRequestHandler {
|
||||
private final SendGrpcWebResponse sendResponse;
|
||||
private final CountDownLatch latch;
|
||||
|
||||
GrpcCallResponseReceiver(SendGrpcWebResponse s, CountDownLatch c) {
|
||||
private final ManagedChannel channel;
|
||||
|
||||
GrpcCallResponseReceiver(SendGrpcWebResponse s, CountDownLatch c, ManagedChannel channel) {
|
||||
sendResponse = s;
|
||||
latch = c;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(java.lang.Object resp) {
|
||||
// TODO verify that the resp object is of Class instance returnedCls.
|
||||
byte[] outB = ((com.google.protobuf.GeneratedMessageV3) resp).toByteArray();
|
||||
sendResponse.writeResponse(outB);
|
||||
if(!sendResponse.writeResponse(outB)){
|
||||
// 这里需要断开grpc
|
||||
this.channel.shutdownNow();
|
||||
logger.error("Grpc shutdown from grpc web proxy client");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+24
-5
@@ -18,6 +18,8 @@ package com.taobao.arthas.grpcweb.proxy;
|
||||
import com.taobao.arthas.grpcweb.proxy.MessageUtils.ContentType;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.handler.stream.ChunkedStream;
|
||||
@@ -68,6 +70,11 @@ class SendGrpcWebResponse {
|
||||
*/
|
||||
private boolean isTrailerSent = false;
|
||||
|
||||
/**
|
||||
* 客户端主动断开连接后,需要断开相应的grpc连接, grpc服务端才能停止监听
|
||||
*/
|
||||
private Boolean isSuccessSendData = true;
|
||||
|
||||
private ChannelHandlerContext ctx;
|
||||
|
||||
SendGrpcWebResponse(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
@@ -146,14 +153,14 @@ class SendGrpcWebResponse {
|
||||
writeEndChunk();
|
||||
}
|
||||
|
||||
synchronized void writeResponse(byte[] out) {
|
||||
writeResponse(out, MessageFramer.Type.DATA);
|
||||
synchronized boolean writeResponse(byte[] out) {
|
||||
return writeResponse(out, MessageFramer.Type.DATA);
|
||||
}
|
||||
|
||||
private void writeResponse(byte[] out, MessageFramer.Type type) {
|
||||
private boolean writeResponse(byte[] out, MessageFramer.Type type) {
|
||||
if (isTrailerSent) {
|
||||
logger.error("grpcweb trailer sented, writeResponse can not be called, framer type: {}", type);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -176,10 +183,22 @@ class SendGrpcWebResponse {
|
||||
InputStream dataStream = new ByteArrayInputStream(byteArray);
|
||||
ChunkedStream chunkedStream = new ChunkedStream(dataStream);
|
||||
SingleHttpChunkedInput httpChunkedInput = new SingleHttpChunkedInput(chunkedStream);
|
||||
ctx.writeAndFlush(httpChunkedInput);
|
||||
ChannelFuture channelFuture = ctx.writeAndFlush(httpChunkedInput);
|
||||
ChannelFutureListener channelFutureListener = new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (!future.isSuccess()) {
|
||||
// 写入操作失败
|
||||
isSuccessSendData = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
channelFuture.addListener(channelFutureListener);
|
||||
return isSuccessSendData;
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("write grpcweb response error, framer type: {}", type, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
package io.arthas.api;
|
||||
|
||||
service ObjectService {
|
||||
rpc query(ObjectQuery) returns (ObjectQueryResult);
|
||||
}
|
||||
|
||||
message ObjectRequest {
|
||||
int32 jobId = 1;
|
||||
int64 resultId = 2;
|
||||
string type = 3;
|
||||
string express = 4;
|
||||
int32 expand = 5;
|
||||
}
|
||||
|
||||
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;
|
||||
int32 jobId = 7;
|
||||
int64 resultId = 8;
|
||||
string resultExpress = 9;
|
||||
}
|
||||
|
||||
message ObjectQueryResult {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
repeated JavaObject objects = 3;
|
||||
}
|
||||
|
||||
service SystemProperty {
|
||||
rpc get(google.protobuf.Empty) returns (ResponseBody);
|
||||
rpc getByKey(StringKey) returns (ResponseBody);
|
||||
rpc update(StringStringMapValue) returns (ResponseBody);
|
||||
}
|
||||
|
||||
service Pwd{
|
||||
rpc pwd(google.protobuf.Empty) returns (ResponseBody);
|
||||
}
|
||||
|
||||
service Watch{
|
||||
rpc watch(WatchRequest) returns (stream ResponseBody);
|
||||
}
|
||||
|
||||
message StringKey {
|
||||
string key = 1;
|
||||
}
|
||||
|
||||
message StringValue {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message StringStringMapValue {
|
||||
map<string, string> stringStringMap = 1;
|
||||
}
|
||||
|
||||
message WatchRequest {
|
||||
string classPattern = 1;
|
||||
string methodPattern = 2;
|
||||
string express = 3;
|
||||
string conditionExpress = 4;
|
||||
bool isBefore = 5;
|
||||
bool isFinish = 6;
|
||||
bool isException = 7;
|
||||
bool isSuccess = 8;
|
||||
int32 expand = 9;
|
||||
int32 sizeLimit = 10;
|
||||
bool isRegEx = 11;
|
||||
int32 numberOfLimit = 12;
|
||||
string excludeClassPattern = 13;
|
||||
int64 listenerId = 14;
|
||||
bool verbose = 15;
|
||||
int32 maxNumOfMatchedClass = 16;
|
||||
int64 jobId = 17;
|
||||
}
|
||||
|
||||
message WatchResponse {
|
||||
string ts = 1;
|
||||
double cost = 2;
|
||||
JavaObject value = 3;
|
||||
int32 sizeLimit = 4;
|
||||
string className = 5;
|
||||
string methodName = 6;
|
||||
string accessPoint = 7;
|
||||
}
|
||||
|
||||
message ResponseBody {
|
||||
int32 jobId = 1;
|
||||
string type = 2;
|
||||
int64 resultId = 3;
|
||||
|
||||
oneof data {
|
||||
StringStringMapValue stringStringMapValue = 4;
|
||||
string stringValue = 5;
|
||||
WatchResponse watchResponse = 6;
|
||||
JavaObject javaObject = 7;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package grpc.gateway.testing;
|
||||
|
||||
message Empty {}
|
||||
|
||||
message EchoRequest {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message EchoResponse {
|
||||
string message = 1;
|
||||
int32 message_count = 2;
|
||||
}
|
||||
|
||||
// Request type for server side streaming echo.
|
||||
message ServerStreamingEchoRequest {
|
||||
// Message string for server streaming request.
|
||||
string message = 1;
|
||||
|
||||
// The total number of messages to be generated before the server
|
||||
// closes the stream; default is 10.
|
||||
int32 message_count = 2;
|
||||
|
||||
// The interval (ms) between two server messages. The server implementation
|
||||
// may enforce some minimum interval (e.g. 100ms) to avoid message overflow.
|
||||
int32 message_interval = 3;
|
||||
}
|
||||
|
||||
// Response type for server streaming response.
|
||||
message ServerStreamingEchoResponse {
|
||||
// Response message.
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
// Request type for client side streaming echo.
|
||||
message ClientStreamingEchoRequest {
|
||||
// A special value "" indicates that there's no further messages.
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
// Response type for client side streaming echo.
|
||||
message ClientStreamingEchoResponse {
|
||||
// Total number of client messages that have been received.
|
||||
int32 message_count = 1;
|
||||
}
|
||||
|
||||
// A simple echo service.
|
||||
service EchoService {
|
||||
// One request followed by one response
|
||||
// The server returns the client message as-is.
|
||||
rpc Echo(EchoRequest) returns (EchoResponse);
|
||||
|
||||
// Sends back abort status.
|
||||
rpc EchoAbort(EchoRequest) returns (EchoResponse) {}
|
||||
|
||||
// One empty request, ZERO processing, followed by one empty response
|
||||
// (minimum effort to do message serialization).
|
||||
rpc NoOp(Empty) returns (Empty);
|
||||
|
||||
// One request followed by a sequence of responses (streamed download).
|
||||
// The server will return the same client message repeatedly.
|
||||
rpc ServerStreamingEcho(ServerStreamingEchoRequest)
|
||||
returns (stream ServerStreamingEchoResponse);
|
||||
|
||||
// One request followed by a sequence of responses (streamed download).
|
||||
// The server abort directly.
|
||||
rpc ServerStreamingEchoAbort(ServerStreamingEchoRequest)
|
||||
returns (stream ServerStreamingEchoResponse) {}
|
||||
|
||||
// A sequence of requests followed by one response (streamed upload).
|
||||
// The server returns the total number of messages as the result.
|
||||
rpc ClientStreamingEcho(stream ClientStreamingEchoRequest)
|
||||
returns (ClientStreamingEchoResponse);
|
||||
|
||||
// A sequence of requests with each message echoed by the server immediately.
|
||||
// The server returns the same client messages in order.
|
||||
// E.g. this is how the speech API works.
|
||||
rpc FullDuplexEcho(stream EchoRequest) returns (stream EchoResponse);
|
||||
|
||||
// A sequence of requests followed by a sequence of responses.
|
||||
// The server buffers all the client messages and then returns the same
|
||||
// client messages one by one after the client half-closes the stream.
|
||||
// This is how an image recognition API may work.
|
||||
rpc HalfDuplexEcho(stream EchoRequest) returns (stream EchoResponse);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2020 The gRPC Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// =======================================
|
||||
//
|
||||
// DO NOT EDIT
|
||||
// this is copy of
|
||||
// https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/
|
||||
// examples/helloworld/helloworld.proto
|
||||
//
|
||||
// TODO: can the original be directly used without making copy here
|
||||
// =======================================
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "grpcweb.examples.greeter";
|
||||
|
||||
package grpcweb.examples.greeter;
|
||||
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package helloworld;
|
||||
|
||||
service Greeter {
|
||||
// unary call
|
||||
rpc SayHello(HelloRequest) returns (HelloReply);
|
||||
// server streaming call
|
||||
rpc SayRepeatHello(RepeatHelloRequest) returns (stream HelloReply);
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message RepeatHelloRequest {
|
||||
string name = 1;
|
||||
int32 count = 2;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
package com.taobao.arthas.grpcweb.grpc.service;
|
||||
|
||||
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 com.taobao.arthas.grpcweb.grpc.objectUtils.JavaObjectConverter;
|
||||
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 testToJavaObjectWithMultiDimensionalArray() {
|
||||
int[][] multiDimensionalArray = { { 1, 2, 3 }, { 4, 5, 6 } };
|
||||
|
||||
JavaObject javaObject = JavaObjectConverter.toJavaObjectWithExpand(multiDimensionalArray,2);
|
||||
assertNotNull(javaObject);
|
||||
assertEquals("[[I", javaObject.getClassName());
|
||||
assertTrue(javaObject.hasArrayValue());
|
||||
ArrayValue arrayValue = javaObject.getArrayValue();
|
||||
assertNotNull(arrayValue);
|
||||
assertEquals("[I", arrayValue.getClassName());
|
||||
assertEquals(2, arrayValue.getElementsCount());
|
||||
|
||||
ArrayElement element = arrayValue.getElements(0);
|
||||
assertTrue(element.hasArrayValue());
|
||||
|
||||
ArrayValue arrayValue1 = element.getArrayValue();
|
||||
assertEquals("int", arrayValue1.getClassName());
|
||||
ArrayElement element1 = arrayValue1.getElements(0);
|
||||
assertEquals(3, arrayValue1.getElementsCount());
|
||||
|
||||
assertTrue(element1.hasBasicValue());
|
||||
BasicValue basicValue = element1.getBasicValue();
|
||||
assertEquals(1, basicValue.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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,24 @@
|
||||
# grpc_web_demo
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
+19264
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "grpc_web_demo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"google-protobuf": "^3.21.2",
|
||||
"grpc-web": "^1.4.2",
|
||||
"view-ui-plus": "^1.3.14",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"vue": "^3.2.13"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<!-- <img alt="Vue logo" src="./assets/logo.png">-->
|
||||
<DemoUI/>
|
||||
<router-view></router-view>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DemoUI from './components/DemoUI.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
DemoUI
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,188 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
package io.arthas.api;
|
||||
|
||||
service ObjectService {
|
||||
rpc query(ObjectQuery) returns (ObjectQueryResult);
|
||||
}
|
||||
|
||||
message ObjectRequest {
|
||||
int32 jobId = 1;
|
||||
int64 resultId = 2;
|
||||
string type = 3;
|
||||
string express = 4;
|
||||
int32 expand = 5;
|
||||
}
|
||||
|
||||
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;
|
||||
int32 jobId = 7;
|
||||
int64 resultId = 8;
|
||||
string resultExpress = 9;
|
||||
}
|
||||
|
||||
message ObjectQueryResult {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
repeated JavaObject objects = 3;
|
||||
}
|
||||
|
||||
service SystemProperty {
|
||||
rpc get(google.protobuf.Empty) returns (ResponseBody);
|
||||
rpc getByKey(StringKey) returns (ResponseBody);
|
||||
rpc update(StringStringMapValue) returns (ResponseBody);
|
||||
}
|
||||
|
||||
service Pwd{
|
||||
rpc pwd(google.protobuf.Empty) returns (ResponseBody);
|
||||
}
|
||||
|
||||
service Watch{
|
||||
rpc watch(WatchRequest) returns (stream ResponseBody);
|
||||
}
|
||||
|
||||
message StringKey {
|
||||
string key = 1;
|
||||
}
|
||||
|
||||
message StringValue {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message StringStringMapValue {
|
||||
map<string, string> stringStringMap = 1;
|
||||
}
|
||||
|
||||
message WatchRequest {
|
||||
string classPattern = 1;
|
||||
string methodPattern = 2;
|
||||
string express = 3;
|
||||
string conditionExpress = 4;
|
||||
bool isBefore = 5;
|
||||
bool isFinish = 6;
|
||||
bool isException = 7;
|
||||
bool isSuccess = 8;
|
||||
int32 expand = 9;
|
||||
int32 sizeLimit = 10;
|
||||
bool isRegEx = 11;
|
||||
int32 numberOfLimit = 12;
|
||||
string excludeClassPattern = 13;
|
||||
int64 listenerId = 14;
|
||||
bool verbose = 15;
|
||||
int32 maxNumOfMatchedClass = 16;
|
||||
int64 jobId = 17;
|
||||
}
|
||||
|
||||
message WatchResponse {
|
||||
string ts = 1;
|
||||
double cost = 2;
|
||||
JavaObject value = 3;
|
||||
int32 sizeLimit = 4;
|
||||
string className = 5;
|
||||
string methodName = 6;
|
||||
string accessPoint = 7;
|
||||
}
|
||||
|
||||
message ResponseBody {
|
||||
int32 jobId = 1;
|
||||
string type = 2;
|
||||
int64 resultId = 3;
|
||||
|
||||
oneof data {
|
||||
StringStringMapValue stringStringMapValue = 4;
|
||||
string stringValue = 5;
|
||||
WatchResponse watchResponse = 6;
|
||||
JavaObject javaObject = 7;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
/**
|
||||
* @fileoverview gRPC-Web generated client stub for io.arthas.api
|
||||
* @enhanceable
|
||||
* @public
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-grpc-web. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-grpc-web v1.4.1
|
||||
// protoc v3.19.1
|
||||
// source: ArthasServices.proto
|
||||
|
||||
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
|
||||
|
||||
const grpc = {};
|
||||
grpc.web = require('grpc-web');
|
||||
|
||||
|
||||
var google_protobuf_empty_pb = require('google-protobuf/google/protobuf/empty_pb.js')
|
||||
const proto = {};
|
||||
proto.io = {};
|
||||
proto.io.arthas = {};
|
||||
proto.io.arthas.api = require('./ArthasServices_pb.js');
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.ObjectServiceClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.ObjectServicePromiseClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.io.arthas.api.ObjectQuery,
|
||||
* !proto.io.arthas.api.ObjectQueryResult>}
|
||||
*/
|
||||
const methodDescriptor_ObjectService_query = new grpc.web.MethodDescriptor(
|
||||
'/io.arthas.api.ObjectService/query',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.io.arthas.api.ObjectQuery,
|
||||
proto.io.arthas.api.ObjectQueryResult,
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.ObjectQuery} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.io.arthas.api.ObjectQueryResult.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.ObjectQuery} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.io.arthas.api.ObjectQueryResult)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ObjectQueryResult>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.ObjectServiceClient.prototype.query =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/io.arthas.api.ObjectService/query',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ObjectService_query,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.ObjectQuery} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.io.arthas.api.ObjectQueryResult>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.io.arthas.api.ObjectServicePromiseClient.prototype.query =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/io.arthas.api.ObjectService/query',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ObjectService_query);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyPromiseClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.google.protobuf.Empty,
|
||||
* !proto.io.arthas.api.ResponseBody>}
|
||||
*/
|
||||
const methodDescriptor_SystemProperty_get = new grpc.web.MethodDescriptor(
|
||||
'/io.arthas.api.SystemProperty/get',
|
||||
grpc.web.MethodType.UNARY,
|
||||
google_protobuf_empty_pb.Empty,
|
||||
proto.io.arthas.api.ResponseBody,
|
||||
/**
|
||||
* @param {!proto.google.protobuf.Empty} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.io.arthas.api.ResponseBody.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.google.protobuf.Empty} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.io.arthas.api.ResponseBody)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ResponseBody>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyClient.prototype.get =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/io.arthas.api.SystemProperty/get',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_SystemProperty_get,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.google.protobuf.Empty} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.io.arthas.api.ResponseBody>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyPromiseClient.prototype.get =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/io.arthas.api.SystemProperty/get',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_SystemProperty_get);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.io.arthas.api.StringKey,
|
||||
* !proto.io.arthas.api.ResponseBody>}
|
||||
*/
|
||||
const methodDescriptor_SystemProperty_getByKey = new grpc.web.MethodDescriptor(
|
||||
'/io.arthas.api.SystemProperty/getByKey',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.io.arthas.api.StringKey,
|
||||
proto.io.arthas.api.ResponseBody,
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.StringKey} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.io.arthas.api.ResponseBody.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.StringKey} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.io.arthas.api.ResponseBody)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ResponseBody>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyClient.prototype.getByKey =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/io.arthas.api.SystemProperty/getByKey',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_SystemProperty_getByKey,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.StringKey} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.io.arthas.api.ResponseBody>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyPromiseClient.prototype.getByKey =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/io.arthas.api.SystemProperty/getByKey',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_SystemProperty_getByKey);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.io.arthas.api.StringStringMapValue,
|
||||
* !proto.io.arthas.api.ResponseBody>}
|
||||
*/
|
||||
const methodDescriptor_SystemProperty_update = new grpc.web.MethodDescriptor(
|
||||
'/io.arthas.api.SystemProperty/update',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.io.arthas.api.StringStringMapValue,
|
||||
proto.io.arthas.api.ResponseBody,
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.StringStringMapValue} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.io.arthas.api.ResponseBody.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.StringStringMapValue} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.io.arthas.api.ResponseBody)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ResponseBody>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyClient.prototype.update =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/io.arthas.api.SystemProperty/update',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_SystemProperty_update,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.StringStringMapValue} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.io.arthas.api.ResponseBody>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.io.arthas.api.SystemPropertyPromiseClient.prototype.update =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/io.arthas.api.SystemProperty/update',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_SystemProperty_update);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.PwdClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.PwdPromiseClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.google.protobuf.Empty,
|
||||
* !proto.io.arthas.api.ResponseBody>}
|
||||
*/
|
||||
const methodDescriptor_Pwd_pwd = new grpc.web.MethodDescriptor(
|
||||
'/io.arthas.api.Pwd/pwd',
|
||||
grpc.web.MethodType.UNARY,
|
||||
google_protobuf_empty_pb.Empty,
|
||||
proto.io.arthas.api.ResponseBody,
|
||||
/**
|
||||
* @param {!proto.google.protobuf.Empty} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.io.arthas.api.ResponseBody.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.google.protobuf.Empty} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.io.arthas.api.ResponseBody)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ResponseBody>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.PwdClient.prototype.pwd =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/io.arthas.api.Pwd/pwd',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_Pwd_pwd,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.google.protobuf.Empty} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.io.arthas.api.ResponseBody>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.io.arthas.api.PwdPromiseClient.prototype.pwd =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/io.arthas.api.Pwd/pwd',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_Pwd_pwd);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.WatchClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.io.arthas.api.WatchPromiseClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.io.arthas.api.WatchRequest,
|
||||
* !proto.io.arthas.api.ResponseBody>}
|
||||
*/
|
||||
const methodDescriptor_Watch_watch = new grpc.web.MethodDescriptor(
|
||||
'/io.arthas.api.Watch/watch',
|
||||
grpc.web.MethodType.SERVER_STREAMING,
|
||||
proto.io.arthas.api.WatchRequest,
|
||||
proto.io.arthas.api.ResponseBody,
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.WatchRequest} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.io.arthas.api.ResponseBody.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.WatchRequest} request The request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ResponseBody>}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.WatchClient.prototype.watch =
|
||||
function(request, metadata) {
|
||||
return this.client_.serverStreaming(this.hostname_ +
|
||||
'/io.arthas.api.Watch/watch',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_Watch_watch);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.io.arthas.api.WatchRequest} request The request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.io.arthas.api.ResponseBody>}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.io.arthas.api.WatchPromiseClient.prototype.watch =
|
||||
function(request, metadata) {
|
||||
return this.client_.serverStreaming(this.hostname_ +
|
||||
'/io.arthas.api.Watch/watch',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_Watch_watch);
|
||||
};
|
||||
|
||||
|
||||
module.exports = proto.io.arthas.api;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Menu mode="horizontal" :theme="theme1" @on-select="turnUrl" active-name="$route.name">
|
||||
<MenuItem name="vmtool">
|
||||
<Icon type="ios-construct" />
|
||||
Vmtool
|
||||
</MenuItem>
|
||||
<MenuItem name="watch">
|
||||
<Icon type="ios-paper" />
|
||||
Watch
|
||||
</MenuItem>
|
||||
<MenuItem name="sysprop">
|
||||
<Icon type="ios-construct" />
|
||||
Sysprop
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem name="pwd">
|
||||
<Icon type="ios-construct" />
|
||||
Pwd
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'DemoUI',
|
||||
data(){
|
||||
return {
|
||||
theme1: 'light'
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
created() {
|
||||
this.$router.push('/vmtool');
|
||||
},
|
||||
|
||||
methods:{
|
||||
turnUrl(name){
|
||||
this.$router.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createApp } from 'vue'
|
||||
import ViewUIPlus from 'view-ui-plus'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import 'view-ui-plus/dist/styles/viewuiplus.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(ViewUIPlus)
|
||||
.use(router)
|
||||
.provide("apiHost","http://localhost:8567")
|
||||
.mount('#app')
|
||||
@@ -0,0 +1,9 @@
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import routes from './routes'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,25 @@
|
||||
const routes = [
|
||||
{
|
||||
name: 'watch',
|
||||
path: '/watch',
|
||||
component: () => import('@/view/watchView')
|
||||
},
|
||||
{
|
||||
name: 'vmtool',
|
||||
path: '/vmtool',
|
||||
component: () => import('@/view/vmtoolView')
|
||||
},
|
||||
{
|
||||
name: 'pwd',
|
||||
path: '/pwd',
|
||||
component: () => import('@/view/pwdView')
|
||||
},
|
||||
{
|
||||
name: 'sysprop',
|
||||
path: '/sysprop',
|
||||
component: () => import('@/view/syspropView')
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export default routes
|
||||
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<Row>
|
||||
<Col span="5">
|
||||
<Card style="width:300px ">
|
||||
<div style="text-align:center">
|
||||
<h3>JobId</h3>
|
||||
<h3>{{ jobId }}</h3>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
</Col>
|
||||
<Col span="19">
|
||||
<Card style="width:300px">
|
||||
<div style="text-align:center">
|
||||
<h3>working dir</h3>
|
||||
<h3>{{ pwdResponse }}</h3>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入自动生成的grpc_web相关的文件
|
||||
|
||||
import {
|
||||
|
||||
PwdClient,
|
||||
} from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
|
||||
|
||||
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
||||
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'pwd',
|
||||
inject: ['apiHost'],
|
||||
data(){
|
||||
return {
|
||||
pwdClient: null,
|
||||
jobId: 0,
|
||||
pwdResponse: "www",
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
created() {
|
||||
let hostname = this.apiHost;
|
||||
this.pwdClient = new PwdClient(hostname);
|
||||
this.sendPwdRequest();
|
||||
this.metadata = {"Content-Type": "application/grpc-web-text"};
|
||||
},
|
||||
|
||||
methods:{
|
||||
sendPwdRequest(){
|
||||
var pwdRequest = new Empty();
|
||||
this.pwdClient.pwd(pwdRequest, {}, (error, response) => {
|
||||
if (!error) {
|
||||
// 处理成功响应
|
||||
this.jobId = response.getJobid();
|
||||
const type = response.getType();
|
||||
if(type == "pwd" && response.hasStringstringmapvalue()){
|
||||
var stringstringmapvalue = response.getStringstringmapvalue();
|
||||
var result = stringstringmapvalue.getStringstringmapMap().get("workingDir")
|
||||
this.pwdResponse = result
|
||||
}
|
||||
} else {
|
||||
// 处理错误
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 10px; /* 标签与输入框之间的右边距 */
|
||||
align-items: flex-start; /* 左对齐 */
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<Row>
|
||||
<Col span="5">
|
||||
<Card style="width:300px ">
|
||||
<div style="text-align:center">
|
||||
<h3>JobId</h3>
|
||||
<h3>{{ jobId }}</h3>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
</Col>
|
||||
<Col span="19">
|
||||
<Form :model="sysGetByKeyModel" :label-width="150" style=" margin-top: 20px; justify-content: center;">
|
||||
<FormItem label="key">
|
||||
<Input v-model="sysGetByKeyModel.key" placeholder="Enter key..."></Input>
|
||||
</FormItem>
|
||||
<FormItem label="value">
|
||||
<Input disabled v-model="sysGetByKeyModel.value" placeholder="result value..."></Input>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button type="primary" @click="getByKey()">查询</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>操作</th> <!-- 添加操作栏的表头 -->
|
||||
<th>key</th>
|
||||
<th>value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in tableData" :key="index">
|
||||
<td>{{ item.index }}</td>
|
||||
<td>
|
||||
<Button type="primary" @click="handleClick(item)">修改</Button> <!-- 添加按钮,并绑定点击事件 -->
|
||||
</td>
|
||||
<td>{{ item.key }}</td>
|
||||
<td>{{ item.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<Modal
|
||||
v-model="modal"
|
||||
title="修改"
|
||||
@on-ok = "sysUpdteByKey"
|
||||
@on-cancel="modalCancel">
|
||||
key: {{sysUpdateModel.key}}
|
||||
<Input v-model="sysUpdateModel.value" placeholder="Enter new value..."></Input>
|
||||
|
||||
</Modal>
|
||||
</table>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入自动生成的grpc_web相关的文件
|
||||
|
||||
import {
|
||||
SystemPropertyClient,
|
||||
} from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
|
||||
|
||||
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
||||
import { StringKey, StringStringMapValue } from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'pwd',
|
||||
inject: ['apiHost'],
|
||||
data(){
|
||||
return {
|
||||
sysPropClient: null,
|
||||
jobId: 0,
|
||||
sysPropResponse: "www",
|
||||
tableData: [],
|
||||
sysGetByKeyModel:{
|
||||
key: "",
|
||||
value: "",
|
||||
},
|
||||
modal: false,
|
||||
sysUpdateModel:{
|
||||
key: "",
|
||||
value: ""
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
created() {
|
||||
let hostname = this.apiHost;
|
||||
this.sysPropClient = new SystemPropertyClient(hostname);
|
||||
this.sendSysPropRequest();
|
||||
this.metadata = {"Content-Type": "application/grpc-web-text"};
|
||||
},
|
||||
|
||||
methods:{
|
||||
|
||||
modalCancel () {
|
||||
this.modal = false;
|
||||
},
|
||||
handleClick(item){
|
||||
this.modal = true;
|
||||
this.sysUpdateModel.key = item.key
|
||||
this.sysUpdateModel.value = item.value
|
||||
console.log(item);
|
||||
},
|
||||
sysUpdteByKey(){
|
||||
var sysPropRequest = new StringStringMapValue();
|
||||
sysPropRequest.getStringstringmapMap()
|
||||
.set(this.sysUpdateModel.key, this.sysUpdateModel.value);
|
||||
const _this = this;
|
||||
this.sysPropClient.update(sysPropRequest, {}, (error, response) => {
|
||||
if (!error) {
|
||||
// 处理成功响应
|
||||
_this.jobId = response.getJobid();
|
||||
const type = response.getType();
|
||||
if(type == "sysprop" && response.hasStringstringmapvalue()){
|
||||
var stringstringmapvalue = response.getStringstringmapvalue();
|
||||
var value = stringstringmapvalue.getStringstringmapMap().get(this.sysUpdateModel.key)
|
||||
if(_this.sysUpdateModel.value == value){
|
||||
this.$Notice.open({
|
||||
title: '修改成功',
|
||||
desc: this.sysUpdateModel.key + " " + "成功修改为: " +this.sysUpdateModel.value
|
||||
});
|
||||
_this.sendSysPropRequest();
|
||||
_this.modal = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 处理错误
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
getByKey(){
|
||||
var sysPropRequest = new StringKey();
|
||||
this.sysGetByKeyModel.key = this.sysGetByKeyModel.key.trim();
|
||||
sysPropRequest.setKey(this.sysGetByKeyModel.key);
|
||||
const _this = this;
|
||||
this.sysPropClient.getByKey(sysPropRequest, {}, (error, response) => {
|
||||
if (!error) {
|
||||
// 处理成功响应
|
||||
_this.jobId = response.getJobid();
|
||||
const type = response.getType();
|
||||
if(type == "sysprop" && response.hasStringstringmapvalue()){
|
||||
var stringstringmapvalue = response.getStringstringmapvalue();
|
||||
_this.sysGetByKeyModel.value = stringstringmapvalue.getStringstringmapMap().get(this.sysGetByKeyModel.key)
|
||||
}
|
||||
} else {
|
||||
// 处理错误
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
sendSysPropRequest(){
|
||||
var sysPropRequest = new Empty();
|
||||
this.tableData = []
|
||||
const _this = this;
|
||||
this.sysPropClient.get(sysPropRequest, {}, (error, response) => {
|
||||
if (!error) {
|
||||
// 处理成功响应
|
||||
_this.jobId = response.getJobid();
|
||||
const type = response.getType();
|
||||
if(type == "sysprop" && response.hasStringstringmapvalue()){
|
||||
var stringstringmapvalue = response.getStringstringmapvalue();
|
||||
var sysPropResponse = stringstringmapvalue.getStringstringmapMap();
|
||||
var index = 1;
|
||||
sysPropResponse.forEach((value, key) => {
|
||||
var cur_dir = {}
|
||||
cur_dir['index'] = index;
|
||||
cur_dir['key'] = key;
|
||||
cur_dir['value'] = value;
|
||||
_this.tableData.push(cur_dir)
|
||||
index = index + 1;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 处理错误
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 10px; /* 标签与输入框之间的右边距 */
|
||||
align-items: flex-start; /* 左对齐 */
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
|
||||
|
||||
<Form :model="objectQueryModel" :label-width="150" style=" margin-top: 20px; justify-content: center;">
|
||||
<div>
|
||||
<Row>
|
||||
<Col span="6">
|
||||
<FormItem label="className">
|
||||
<Input v-model="objectQueryModel.className" placeholder="Enter className..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="ClassLoaderHash">
|
||||
<Input v-model="objectQueryModel.classLoaderHash" placeholder="Enter classLoaderHash..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="classLoaderClass">
|
||||
<Input v-model="objectQueryModel.classLoaderClass" placeholder="Enter classLoaderClass..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="limit">
|
||||
<Input type="number" v-model="objectQueryModel.limit" placeholder="Enter className..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Row>
|
||||
<Col span="12">
|
||||
<FormItem label="depth">
|
||||
<Input type="number" v-model="objectQueryModel.depth" placeholder="Enter depth..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="12">
|
||||
<FormItem label="express">
|
||||
<Input v-model="objectQueryModel.express" placeholder="Enter express..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<!-- <FormItem label="listenerId">-->
|
||||
<!-- <Input type="number" v-model="watchRequestModel.listenerId" placeholder="Enter listenerId..."></Input>-->
|
||||
<!-- </FormItem>-->
|
||||
|
||||
<!-- <FormItem label="jobId">-->
|
||||
<!-- <Input type="number" v-model="watchRequestModel.jobId" placeholder="Enter jobId..."></Input>-->
|
||||
<!-- </FormItem>-->
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" @click="sendObjectRequest">查询</Button>
|
||||
<Button style="margin-left: 8px" @click="clear">清除结果</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<Row>
|
||||
<Col span="8"></Col>
|
||||
<Col span="8">
|
||||
<Tree id="tree" :data="this.treeData"></Tree>
|
||||
</Col>
|
||||
<Col span="8"></Col>
|
||||
</Row>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入自动生成的grpc_web相关的文件
|
||||
|
||||
import {
|
||||
ObjectServiceClient
|
||||
} from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
|
||||
import { ObjectQuery } from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'vmtool',
|
||||
inject: ['apiHost'],
|
||||
data(){
|
||||
return {
|
||||
objectClient: null,
|
||||
metadata: {},
|
||||
objectQueryModel: {
|
||||
className: "com.taobao.arthas.grpcweb.grpc.objectUtils.ComplexObject",
|
||||
classLoaderHash: 0,
|
||||
classLoaderClass: "",
|
||||
express: "instances",
|
||||
depth: 2,
|
||||
limit: 3,
|
||||
},
|
||||
treeData: [],
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
created() {
|
||||
let hostname = this.apiHost;
|
||||
this.objectClient = new ObjectServiceClient(hostname)
|
||||
this.metadata = {"Content-Type": "application/grpc-web-text"};
|
||||
},
|
||||
|
||||
methods:{
|
||||
clear(){
|
||||
this.treeData=[];
|
||||
},
|
||||
resetAllRequestParams(){
|
||||
this.objectQueryModel.className = "demo.MathGame"
|
||||
this.objectQueryModel.classLoaderClass = "";
|
||||
this.objectQueryModel.classLoaderHash = "";
|
||||
this.objectQueryModel.express = "instances[0]"
|
||||
},
|
||||
|
||||
sendObjectRequest(){
|
||||
const objectRequest = new ObjectQuery();
|
||||
objectRequest.setClassname(this.objectQueryModel.className)
|
||||
.setLimit(this.objectQueryModel.limit)
|
||||
.setDepth(this.objectQueryModel.depth)
|
||||
.setJobid(this.objectQueryModel.jobId)
|
||||
.setResultid(this.objectQueryModel.resultId)
|
||||
.setExpress(this.objectQueryModel.express)
|
||||
.setResultexpress(this.objectQueryModel.resultExpress)
|
||||
|
||||
this.objectClient.query(objectRequest, {}, (error, response) => {
|
||||
if (!error) {
|
||||
this.treeData = []
|
||||
// 处理成功响应
|
||||
console.log("response", response)
|
||||
console.log("response.sucess", response.getSuccess())
|
||||
console.log("response.message", response.getMessage())
|
||||
const objectList = response.getObjectsList()
|
||||
objectList.forEach(item =>{
|
||||
const data = this.getObject(item);
|
||||
data['expand'] = true;
|
||||
this.treeData.push(data)
|
||||
})
|
||||
|
||||
} else {
|
||||
// 处理错误
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
getBasicvalue(obj){
|
||||
const aMap = {}
|
||||
let value;
|
||||
let type;
|
||||
if(obj.hasInt()){
|
||||
value = obj.getInt();
|
||||
type = "java.lang.Integer";
|
||||
}else if(obj.hasLong()){
|
||||
value = obj.getLong()
|
||||
type = "java.lang.Long";
|
||||
}else if(obj.hasFloat()){
|
||||
value = obj.getFloat()
|
||||
type= "java.lang.Float";
|
||||
}else if(obj.hasDouble()){
|
||||
value =obj.getDouble()
|
||||
type = "java.lang.Double";
|
||||
}else if(obj.hasBoolean()){
|
||||
value = obj.getBoolean()
|
||||
type = "java.lang.Boolean"
|
||||
}else if(obj.hasString()){
|
||||
value =obj.getString()
|
||||
type = "java.lang.String"
|
||||
}
|
||||
aMap['title'] = value + " (@" +type;
|
||||
return aMap
|
||||
},
|
||||
|
||||
getArrayElements(obj){
|
||||
const aMap = {}
|
||||
let title = "element";
|
||||
try {
|
||||
title = obj.getName()
|
||||
}catch (e){
|
||||
try {
|
||||
title = obj.getClassname()
|
||||
}catch (e){
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
if(obj.hasObjectvalue()){
|
||||
aMap['title'] = title + " (@Object";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getObject(obj.getObjectvalue()))
|
||||
} else if(obj.hasBasicvalue()){
|
||||
const basicValue = obj.getBasicvalue()
|
||||
if(title == "element" || this.isBasicType(title)){
|
||||
aMap['title'] = this.getBasicvalue(basicValue)['title'];
|
||||
}else{
|
||||
aMap['title'] = title + " (@Basic";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getBasicvalue(basicValue))
|
||||
}
|
||||
}else if(obj.hasArrayvalue()){
|
||||
const arrayValue = obj.getArrayvalue()
|
||||
aMap['title'] = title + " (@ArrayList";
|
||||
aMap['children'] = []
|
||||
const elementsList = arrayValue.getElementsList();
|
||||
elementsList.forEach(item=>{
|
||||
aMap['children'].push(this.getArrayElements(item))
|
||||
})
|
||||
}else if(obj.hasNullvalue()){
|
||||
aMap['title'] = title + " (@null";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push({"title":"(null)" + obj.getNullvalue().getClassname()})
|
||||
}else if(obj.hasUnexpandedobject()){
|
||||
aMap['title'] = title +" (@Unexpand";
|
||||
aMap['children'] = [];
|
||||
aMap['children'].push({"title":" (Unexpand) " +obj.getUnexpandedobject().getClassname()})
|
||||
}
|
||||
return aMap;
|
||||
},
|
||||
|
||||
|
||||
getObject(obj){
|
||||
const aMap = {}
|
||||
let title = "";
|
||||
try {
|
||||
title = obj.getName()
|
||||
}catch (e){
|
||||
try {
|
||||
title = obj.getClassname()
|
||||
}catch (e){
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
if(obj.hasObjectvalue()){
|
||||
aMap['title'] = title + " (@Object";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getObject(obj.getObjectvalue()))
|
||||
} else if(obj.hasBasicvalue()){
|
||||
const basicValue = obj.getBasicvalue()
|
||||
if(title == "" || this.isBasicType(title)){
|
||||
aMap['title'] = this.getBasicvalue(basicValue)['title'];
|
||||
} else{
|
||||
aMap['title'] = title+ " (@Basic";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getBasicvalue(basicValue))
|
||||
}
|
||||
}else if(obj.hasArrayvalue()){
|
||||
const arrayValue = obj.getArrayvalue()
|
||||
aMap['title'] =title + " (@ArrayList";
|
||||
aMap['children'] = []
|
||||
const elementsList = arrayValue.getElementsList();
|
||||
elementsList.forEach(item=>{
|
||||
aMap['children'].push(this.getArrayElements(item))
|
||||
})
|
||||
}else if(obj.hasNullvalue()){
|
||||
aMap['title'] = title + " (@null";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push({"title":"(@null)" + obj.getNullvalue().getClassname()})
|
||||
}else if(obj.hasCollection()){
|
||||
aMap['title'] = title+ " (@Collection";
|
||||
aMap['children'] = []
|
||||
const javaObjectList = obj.getCollection().getElementsList()
|
||||
javaObjectList.forEach(item =>{
|
||||
aMap['children'].push(this.getObject(item))
|
||||
})
|
||||
}else if(obj.hasMap()){
|
||||
aMap['title'] = title + " (@Map";
|
||||
aMap['children'] = [];
|
||||
const entriesList = obj.getMap().getEntriesList()
|
||||
entriesList.forEach(item =>{
|
||||
const bMap = {}
|
||||
const keyMap = {}
|
||||
const valueMap = {}
|
||||
bMap['title'] = "Entry"
|
||||
bMap['children'] = []
|
||||
keyMap['title'] = "key"
|
||||
keyMap['children']= []
|
||||
keyMap['children'].push(this.getObject(item.getKey()))
|
||||
bMap['children'].push(keyMap)
|
||||
valueMap['title'] = "value"
|
||||
valueMap['children']= []
|
||||
valueMap['children'].push(this.getObject(item.getValue()))
|
||||
bMap['children'].push(valueMap)
|
||||
aMap['children'].push(bMap)
|
||||
})
|
||||
}else if(obj.hasUnexpandedobject()){
|
||||
aMap['title'] = title +" (@Unexpand";
|
||||
aMap['children'] = [];
|
||||
aMap['children'].push({"title":" (@Unexpand) " + obj.getUnexpandedobject().getClassname()})
|
||||
}else if(obj.hasFields()){
|
||||
aMap['title'] = title;
|
||||
aMap['children'] = []
|
||||
const fieldsList = obj.getFields().getFieldsList();
|
||||
fieldsList.forEach(item =>{
|
||||
aMap['children'].push(this.getObject(item))
|
||||
})
|
||||
}
|
||||
return aMap;
|
||||
},
|
||||
|
||||
isBasicType(type){
|
||||
if(type == "java.lang.String" || type == "java.lang.Integer" || type == "java.lang.Long"
|
||||
|| type == "java.lang.Float" || type == "java.lang.Double" || type == "java.lang.Boolean"){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 10px; /* 标签与输入框之间的右边距 */
|
||||
align-items: flex-start; /* 左对齐 */
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
#tree{
|
||||
margin-left: 50px;
|
||||
white-space: pre-wrap; /* 保留换行符并折叠连续的空白字符 */
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,696 @@
|
||||
<template>
|
||||
|
||||
<Form :model="watchRequestModel" :label-width="150" style=" margin-top: 20px; justify-content: center;">
|
||||
<div>
|
||||
<Row>
|
||||
<Col span="6">
|
||||
<FormItem label="classPattern">
|
||||
<Input v-model="watchRequestModel.classPattern" placeholder="Enter className..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="methodPattern">
|
||||
<Input v-model="watchRequestModel.methodPattern" placeholder="Enter metheName..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="express">
|
||||
<Input v-model="watchRequestModel.express" placeholder="Enter express..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="conditionExpress">
|
||||
<Input v-model="watchRequestModel.conditionExpress" placeholder="Enter conditionExpress..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Row>
|
||||
<Col span="8">
|
||||
<FormItem label="situation">
|
||||
<RadioGroup v-model="watchRequestModel.situation">
|
||||
<Radio label="isBefore">isBefore</Radio>
|
||||
<Radio label="isFinish">isFinish</Radio>
|
||||
<Radio label="isException">isException</Radio>
|
||||
<Radio label="isSuccess">isSuccess</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="expand">
|
||||
<Input type="number" v-model="watchRequestModel.expand" placeholder="Enter expand..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="sizeLimit">
|
||||
<Input type="number" v-model="watchRequestModel.sizeLimit" placeholder="Enter sizeLimit..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="4">
|
||||
<FormItem label="isRegEx">
|
||||
<i-switch v-model="watchRequestModel.isRegEx" size="large">
|
||||
<template #open>
|
||||
<span>true</span>
|
||||
</template>
|
||||
<template #close>
|
||||
<span>false</span>
|
||||
</template>
|
||||
</i-switch>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Row>
|
||||
<Col span="6">
|
||||
<FormItem label="numberOfLimit">
|
||||
<Input type="number" v-model="watchRequestModel.numberOfLimit" placeholder="Enter numberOfLimit..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<FormItem label="excludeClassPattern">
|
||||
<Input type="text" v-model="watchRequestModel.excludeClassPattern" placeholder="Enter excludeClassPattern..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="3">
|
||||
<FormItem label="verbose">
|
||||
<i-switch v-model="watchRequestModel.verbose" size="large">
|
||||
<template #open>
|
||||
<span>true</span>
|
||||
</template>
|
||||
<template #close>
|
||||
<span>false</span>
|
||||
</template>
|
||||
</i-switch>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="9">
|
||||
<FormItem label="maxNumOfMatchedClass">
|
||||
<Input type="number" v-model="watchRequestModel.maxNumOfMatchedClass" placeholder="Enter maxNumOfMatchedClass..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
<!-- <FormItem label="listenerId">-->
|
||||
<!-- <Input type="number" v-model="watchRequestModel.listenerId" placeholder="Enter listenerId..."></Input>-->
|
||||
<!-- </FormItem>-->
|
||||
|
||||
<!-- <FormItem label="jobId">-->
|
||||
<!-- <Input type="number" v-model="watchRequestModel.jobId" placeholder="Enter jobId..."></Input>-->
|
||||
<!-- </FormItem>-->
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" @click="watch" v-bind:disabled="!this.watchEnable">{{this.submitText}}</Button>
|
||||
<Button style="margin-left: 8px" @click="stopWatchRequest">Cancel</Button>
|
||||
<Button style="margin-left: 8px" @click="clear">清除结果</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>jobid</th>
|
||||
<th>resultid</th>
|
||||
<th>ts</th>
|
||||
<th>accessPoint</th>
|
||||
<th>className</th>
|
||||
<th>methodName</th>
|
||||
<th>cost</th>
|
||||
<th>value</th>
|
||||
<th>查看当前结果信息</th> <!-- 添加操作栏的表头 -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in tableData" :key="index">
|
||||
<td>{{ item.jobId }}</td>
|
||||
<td>{{ item.resultId }}</td>
|
||||
<td>{{ item.ts }}</td>
|
||||
<td>{{ item.accessPoint }}</td>
|
||||
<td>{{ item.className }}</td>
|
||||
<td>{{ item.methodName }}</td>
|
||||
<td>{{ item.cost }}</td>
|
||||
<!-- <td class="preserve-whitespace">-->
|
||||
<td>
|
||||
<Tree id="tree" :data="item.value"></Tree>
|
||||
</td>
|
||||
<td>
|
||||
<Button type="primary" @click="handleClick(item)">查看结果</Button> <!-- 添加按钮,并绑定点击事件 -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<Modal
|
||||
v-model="modal"
|
||||
title="信息查看"
|
||||
@on-cancel="modalCancel">
|
||||
|
||||
<Tag color="primary">JobId: {{ this.objectQueryModel.jobId }}</Tag>
|
||||
<Tag color="primary">resultId: {{ this.objectQueryModel.resultId }}</Tag>
|
||||
|
||||
<Form>
|
||||
<div>
|
||||
<Row>
|
||||
<Col span="12">
|
||||
<FormItem label="express">
|
||||
<Input v-model="this.objectQueryModel.resultExpress" placeholder="Enter express..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span="12">
|
||||
<FormItem label="expand">
|
||||
<Input type="number" v-model="this.objectQueryModel.depth" placeholder="Enter conditionExpress..."></Input>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Form>
|
||||
<Button type="warning" @click="sendObjectRequest">重新查询</Button> <!-- 添加按钮,并绑定点击事件 -->
|
||||
<h3>value结果:(可以点击重新查询按钮反复筛选查看)</h3>
|
||||
<Tree id="treeInModal" :data="this.treeData"></Tree>
|
||||
|
||||
</Modal>
|
||||
</table>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入自动生成的grpc_web相关的文件
|
||||
|
||||
import {
|
||||
WatchClient,
|
||||
ObjectServiceClient
|
||||
} from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
|
||||
import { WatchRequest, ObjectQuery } from '@/assets/proto/ArthasServices_grpc_web_pb';
|
||||
import {Col} from "view-ui-plus";
|
||||
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'watchView',
|
||||
components: {Col},
|
||||
inject: ['apiHost'],
|
||||
data(){
|
||||
return {
|
||||
watchClient: null,
|
||||
objectClient: null,
|
||||
|
||||
modal: false,
|
||||
objectQueryModel: {
|
||||
className: "demo.MathGame",
|
||||
classLoaderHash: 0,
|
||||
classLoaderClass: "",
|
||||
express: "instances[0]",
|
||||
depth: 2,
|
||||
limit: 1,
|
||||
|
||||
jobId: 0,
|
||||
resultId: 0,
|
||||
resultExpress: "",
|
||||
},
|
||||
|
||||
metadata: {},
|
||||
watchStream: null,
|
||||
changeWatchStream: null,
|
||||
isWatching: false,
|
||||
submitText:"开始watch",
|
||||
watchEnable:true,
|
||||
watchRequestModel: {
|
||||
classPattern: "demo.MathGame",
|
||||
methodPattern: "primeFactors",
|
||||
express: "{params, target, returnObj}",
|
||||
conditionExpress: "",
|
||||
isBefore: false,
|
||||
isFinish: true,
|
||||
isException: false,
|
||||
isSuccess: false,
|
||||
situation: "isFinish",
|
||||
expand: 2,
|
||||
sizeLimit: 10 * 1024 * 1024,
|
||||
isRegEx: false,
|
||||
numberOfLimit: 10,
|
||||
excludeClassPattern: "",
|
||||
listenerId: 0,
|
||||
verbose: false,
|
||||
maxNumOfMatchedClass: 50,
|
||||
jobId: 0
|
||||
},
|
||||
|
||||
tableData: [], // 存储表格数据的数组
|
||||
treeData: [],
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
created() {
|
||||
let hostname = this.apiHost;
|
||||
this.watchClient = new WatchClient(hostname);
|
||||
this.objectClient = new ObjectServiceClient(hostname)
|
||||
this.metadata = {"Content-Type": "application/grpc-web-text"};
|
||||
},
|
||||
|
||||
methods:{
|
||||
modalCancel () {
|
||||
this.modal = false;
|
||||
},
|
||||
|
||||
|
||||
handleClick(item) {
|
||||
// 在这里处理按钮点击事件,可以使用item对象获取当前行的信息
|
||||
this.objectQueryModel.className = "com.taobao.arthas.grpcweb.grpc.service.GrpcJobController"
|
||||
this.objectQueryModel.resultExpress = "{params, target, returnObj}"
|
||||
this.objectQueryModel.jobId = item.jobId;
|
||||
this.objectQueryModel.resultId = item.resultId;
|
||||
this.objectQueryModel.type = item.type;
|
||||
this.objectQueryModel.express = "instances[0].{jobs}.get(0).get(" + item.jobId + "L).{listener}.{results}.get(0).get(" + item.resultId + "L)"
|
||||
let value = item.value[0];
|
||||
let copiedObject = JSON.parse(JSON.stringify(value));
|
||||
this.treeData = [copiedObject];
|
||||
this.modal = true;
|
||||
},
|
||||
|
||||
|
||||
watch(){
|
||||
if(this.isWatching){
|
||||
this.changeWatchRequest();
|
||||
}else {
|
||||
this.sendWatchRequest();
|
||||
}
|
||||
},
|
||||
|
||||
sendWatchRequest(){
|
||||
this.watchRequestModel.isBefore = false;
|
||||
this.watchRequestModel.isFinish = false;
|
||||
this.watchRequestModel.isSuccess = false;
|
||||
this.watchRequestModel.isException = false;
|
||||
if(this.watchRequestModel.situation == "isBefore"){
|
||||
this.watchRequestModel.isBefore = true;
|
||||
}else if(this.watchRequestModel.situation == "isFinish"){
|
||||
this.watchRequestModel.isFinish = true;
|
||||
}else if(this.watchRequestModel.situation == "isSuccess"){
|
||||
this.watchRequestModel.isSuccess = true;
|
||||
}else {
|
||||
this.watchRequestModel.isException = true;
|
||||
}
|
||||
const watchRequest = new WatchRequest();
|
||||
watchRequest.setClasspattern(this.watchRequestModel.classPattern)
|
||||
.setMethodpattern(this.watchRequestModel.methodPattern)
|
||||
.setExpress(this.watchRequestModel.express)
|
||||
.setConditionexpress(this.watchRequestModel.conditionExpress)
|
||||
.setIsbefore(this.watchRequestModel.isBefore)
|
||||
.setIsfinish(this.watchRequestModel.isFinish)
|
||||
.setIsexception(this.watchRequestModel.isException)
|
||||
.setIssuccess(this.watchRequestModel.isSuccess)
|
||||
.setExpand(this.watchRequestModel.expand)
|
||||
.setSizelimit(this.watchRequestModel.sizeLimit)
|
||||
.setIsregex(this.watchRequestModel.isRegEx)
|
||||
.setNumberoflimit(this.watchRequestModel.numberOfLimit)
|
||||
.setExcludeclasspattern(this.watchRequestModel.excludeClassPattern)
|
||||
.setListenerid(this.watchRequestModel.listenerId)
|
||||
.setVerbose(this.watchRequestModel.verbose)
|
||||
.setMaxnumofmatchedclass(this.watchRequestModel.maxNumOfMatchedClass)
|
||||
.setJobid(this.watchRequestModel.jobId);
|
||||
|
||||
this.watchStream = this.watchClient.watch(watchRequest,{});
|
||||
let _this = this
|
||||
// 持续获取流数据并处理
|
||||
this.watchStream.on('data', function(response) {
|
||||
const jobId = response.getJobid();
|
||||
const type = response.getType();
|
||||
const resultId = response.getResultid();
|
||||
if(type == "watch" && response.hasWatchresponse()){
|
||||
_this.isWatching = true;
|
||||
const watchResponse = response.getWatchresponse();
|
||||
var data = _this.getObject(watchResponse.getValue());
|
||||
data['expand'] = true;
|
||||
var newData = {
|
||||
jobId: jobId,
|
||||
resultId: resultId,
|
||||
type: type,
|
||||
ts: watchResponse.getTs(),
|
||||
accessPoint: watchResponse.getAccesspoint(),
|
||||
className: watchResponse.getClassname(),
|
||||
methodName: watchResponse.getMethodname(),
|
||||
cost: watchResponse.getCost(),
|
||||
value: [data],
|
||||
};
|
||||
_this.tableData.unshift(newData);
|
||||
_this.watchRequestModel.jobId = jobId;
|
||||
_this.submitText = "动态修改条件"
|
||||
// _this.watchStream = stream;
|
||||
}else {
|
||||
console.log("收到的不是watchResponse: ----->")
|
||||
console.log('type:', type);
|
||||
console.log('message:', response.getStringvalue());
|
||||
_this.$Notice.info({
|
||||
title: 'watch tips',
|
||||
desc: response.getStringvalue()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.watchStream.on('status', function(status) {
|
||||
console.log("status.code " + status.code);
|
||||
console.log("status.details " + status.details);
|
||||
console.log("status.metadata " + status.metadata.toString());
|
||||
});
|
||||
|
||||
this.watchStream.on('end', function(end) {
|
||||
console.log("end: " + end)
|
||||
// stream end signal
|
||||
_this.watchStream.cancel()
|
||||
_this.isWatching = false
|
||||
_this.submitText = "开始watch"
|
||||
_this.$Notice.info({
|
||||
title: 'watch结束',
|
||||
desc: 'watch结束'
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
changeWatchRequest(){
|
||||
this.watchRequestModel.isBefore = false;
|
||||
this.watchRequestModel.isFinish = false;
|
||||
this.watchRequestModel.isSuccess = false;
|
||||
this.watchRequestModel.isException = false;
|
||||
if(this.watchRequestModel.situation == "isBefore"){
|
||||
this.watchRequestModel.isBefore = true;
|
||||
}else if(this.watchRequestModel.situation == "isFinish"){
|
||||
this.watchRequestModel.isFinish = true;
|
||||
}else if(this.watchRequestModel.situation == "isSuccess"){
|
||||
this.watchRequestModel.isSuccess = true;
|
||||
}else {
|
||||
this.watchRequestModel.isException = true;
|
||||
}
|
||||
const watchRequest = new WatchRequest();
|
||||
watchRequest.setClasspattern(this.watchRequestModel.classPattern)
|
||||
.setMethodpattern(this.watchRequestModel.methodPattern)
|
||||
.setExpress(this.watchRequestModel.express)
|
||||
.setConditionexpress(this.watchRequestModel.conditionExpress)
|
||||
.setIsbefore(this.watchRequestModel.isBefore)
|
||||
.setIsfinish(this.watchRequestModel.isFinish)
|
||||
.setIsexception(this.watchRequestModel.isException)
|
||||
.setIssuccess(this.watchRequestModel.isSuccess)
|
||||
.setExpand(this.watchRequestModel.expand)
|
||||
.setSizelimit(this.watchRequestModel.sizeLimit)
|
||||
.setIsregex(this.watchRequestModel.isRegEx)
|
||||
.setNumberoflimit(this.watchRequestModel.numberOfLimit)
|
||||
.setExcludeclasspattern(this.watchRequestModel.excludeClassPattern)
|
||||
.setListenerid(this.watchRequestModel.listenerId)
|
||||
.setVerbose(this.watchRequestModel.verbose)
|
||||
.setMaxnumofmatchedclass(this.watchRequestModel.maxNumOfMatchedClass)
|
||||
.setJobid(this.watchRequestModel.jobId);
|
||||
|
||||
this.changeWatchStream = this.watchClient.watch(watchRequest,{});
|
||||
let _this = this
|
||||
// 持续获取流数据并处理
|
||||
this.changeWatchStream.on('data', function(response) {
|
||||
const jobId = response.getJobid();
|
||||
const type = response.getType();
|
||||
const resultId = response.getResultid();
|
||||
if(type != "watch" ){
|
||||
console.log('jobId:', jobId);
|
||||
console.log('resultId:', resultId);
|
||||
console.log('message:', response.getStringvalue());
|
||||
_this.$Notice.info({
|
||||
title: 'SUCCESS',
|
||||
desc: '修改成功'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.changeWatchStream.on('status', function(status) {
|
||||
console.log("status.code " + status.code);
|
||||
console.log("status.details " + status.details);
|
||||
console.log("status.metadata " + status.metadata.toString());
|
||||
});
|
||||
|
||||
this.changeWatchStream.on('end', function(end) {
|
||||
console.log("end: " + end)
|
||||
// stream end signal
|
||||
_this.changeWatchStream.cancel()
|
||||
});
|
||||
},
|
||||
|
||||
stopWatchRequest(){
|
||||
if(this.isWatching && this.watchStream!=null){
|
||||
this.watchStream.cancel();
|
||||
this.isWatching = false;
|
||||
this.$Notice.warning({
|
||||
title: 'Notification',
|
||||
desc: '手动停止watch'
|
||||
});
|
||||
}
|
||||
this.submitText = "开始watch"
|
||||
},
|
||||
|
||||
clear(){
|
||||
this.tableData = []
|
||||
},
|
||||
|
||||
sendObjectRequest(){
|
||||
const objectRequest = new ObjectQuery();
|
||||
objectRequest.setClassname(this.objectQueryModel.className)
|
||||
.setLimit(this.objectQueryModel.limit)
|
||||
.setDepth(this.objectQueryModel.depth)
|
||||
.setJobid(this.objectQueryModel.jobId)
|
||||
.setResultid(this.objectQueryModel.resultId)
|
||||
.setExpress(this.objectQueryModel.express)
|
||||
.setResultexpress(this.objectQueryModel.resultExpress)
|
||||
|
||||
this.objectClient.query(objectRequest, {}, (error, response) => {
|
||||
if (!error) {
|
||||
this.treeData = []
|
||||
// 处理成功响应
|
||||
console.log("response", response)
|
||||
console.log("response.sucess", response.getSuccess())
|
||||
console.log("response.message", response.getMessage())
|
||||
const objectList = response.getObjectsList()
|
||||
objectList.forEach(item =>{
|
||||
const data = this.getObject(item);
|
||||
data['expand'] = true;
|
||||
this.treeData.push(data)
|
||||
})
|
||||
|
||||
} else {
|
||||
// 处理错误
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
getBasicvalue(obj){
|
||||
const aMap = {}
|
||||
let value;
|
||||
let type;
|
||||
if(obj.hasInt()){
|
||||
value = obj.getInt();
|
||||
type = "java.lang.Integer";
|
||||
}else if(obj.hasLong()){
|
||||
value = obj.getLong()
|
||||
type = "java.lang.Long";
|
||||
}else if(obj.hasFloat()){
|
||||
value = obj.getFloat()
|
||||
type= "java.lang.Float";
|
||||
}else if(obj.hasDouble()){
|
||||
value =obj.getDouble()
|
||||
type = "java.lang.Double";
|
||||
}else if(obj.hasBoolean()){
|
||||
value = obj.getBoolean()
|
||||
type = "java.lang.Boolean"
|
||||
}else if(obj.hasString()){
|
||||
value =obj.getString()
|
||||
type = "java.lang.String"
|
||||
}
|
||||
aMap['title'] = value + " (@" +type;
|
||||
return aMap
|
||||
},
|
||||
|
||||
getArrayElements(obj){
|
||||
const aMap = {}
|
||||
let title = "element";
|
||||
try {
|
||||
title = obj.getName()
|
||||
}catch (e){
|
||||
try {
|
||||
title = obj.getClassname()
|
||||
}catch (e){
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
if(obj.hasObjectvalue()){
|
||||
aMap['title'] = title + " (@Object";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getObject(obj.getObjectvalue()))
|
||||
} else if(obj.hasBasicvalue()){
|
||||
const basicValue = obj.getBasicvalue()
|
||||
if(title == "element" || this.isBasicType(title)){
|
||||
aMap['title'] = this.getBasicvalue(basicValue)['title'];
|
||||
}else{
|
||||
aMap['title'] = title + " (@Basic";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getBasicvalue(basicValue))
|
||||
}
|
||||
}else if(obj.hasArrayvalue()){
|
||||
const arrayValue = obj.getArrayvalue()
|
||||
aMap['title'] = title + " (@ArrayList";
|
||||
aMap['children'] = []
|
||||
const elementsList = arrayValue.getElementsList();
|
||||
elementsList.forEach(item=>{
|
||||
aMap['children'].push(this.getArrayElements(item))
|
||||
})
|
||||
}else if(obj.hasNullvalue()){
|
||||
aMap['title'] = title + " (@null";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push({"title":"(null)" + obj.getNullvalue().getClassname()})
|
||||
}else if(obj.hasUnexpandedobject()){
|
||||
aMap['title'] = title +" (@Unexpand";
|
||||
aMap['children'] = [];
|
||||
aMap['children'].push({"title":" (Unexpand) " +obj.getUnexpandedobject().getClassname()})
|
||||
}
|
||||
return aMap;
|
||||
},
|
||||
|
||||
|
||||
getObject(obj){
|
||||
const aMap = {}
|
||||
let title = "";
|
||||
try {
|
||||
title = obj.getName()
|
||||
}catch (e){
|
||||
try {
|
||||
title = obj.getClassname()
|
||||
}catch (e){
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
if(obj.hasObjectvalue()){
|
||||
aMap['title'] = title + " (@Object";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getObject(obj.getObjectvalue()))
|
||||
} else if(obj.hasBasicvalue()){
|
||||
const basicValue = obj.getBasicvalue()
|
||||
if(title == "" || this.isBasicType(title)){
|
||||
aMap['title'] = this.getBasicvalue(basicValue)['title'];
|
||||
} else{
|
||||
aMap['title'] = title+ " (@Basic";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push(this.getBasicvalue(basicValue))
|
||||
}
|
||||
}else if(obj.hasArrayvalue()){
|
||||
const arrayValue = obj.getArrayvalue()
|
||||
aMap['title'] =title + " (@ArrayList";
|
||||
aMap['children'] = []
|
||||
const elementsList = arrayValue.getElementsList();
|
||||
elementsList.forEach(item=>{
|
||||
aMap['children'].push(this.getArrayElements(item))
|
||||
})
|
||||
}else if(obj.hasNullvalue()){
|
||||
aMap['title'] = title + " (@null";
|
||||
aMap['children'] = []
|
||||
aMap['children'].push({"title":"(@null)" + obj.getNullvalue().getClassname()})
|
||||
}else if(obj.hasCollection()){
|
||||
aMap['title'] = title+ " (@Collection";
|
||||
aMap['children'] = []
|
||||
const javaObjectList = obj.getCollection().getElementsList()
|
||||
javaObjectList.forEach(item =>{
|
||||
aMap['children'].push(this.getObject(item))
|
||||
})
|
||||
}else if(obj.hasMap()){
|
||||
aMap['title'] = title + " (@Map";
|
||||
aMap['children'] = [];
|
||||
const entriesList = obj.getMap().getEntriesList()
|
||||
entriesList.forEach(item =>{
|
||||
const bMap = {}
|
||||
const keyMap = {}
|
||||
const valueMap = {}
|
||||
bMap['title'] = "Entry"
|
||||
bMap['children'] = []
|
||||
keyMap['title'] = "key"
|
||||
keyMap['children']= []
|
||||
keyMap['children'].push(this.getObject(item.getKey()))
|
||||
bMap['children'].push(keyMap)
|
||||
valueMap['title'] = "value"
|
||||
valueMap['children']= []
|
||||
valueMap['children'].push(this.getObject(item.getValue()))
|
||||
bMap['children'].push(valueMap)
|
||||
aMap['children'].push(bMap)
|
||||
})
|
||||
}else if(obj.hasUnexpandedobject()){
|
||||
aMap['title'] = title +" (@Unexpand";
|
||||
aMap['children'] = [];
|
||||
aMap['children'].push({"title":" (@Unexpand) " + obj.getUnexpandedobject().getClassname()})
|
||||
}else if(obj.hasFields()){
|
||||
aMap['title'] = title;
|
||||
aMap['children'] = []
|
||||
const fieldsList = obj.getFields().getFieldsList();
|
||||
fieldsList.forEach(item =>{
|
||||
aMap['children'].push(this.getObject(item))
|
||||
})
|
||||
}
|
||||
return aMap;
|
||||
},
|
||||
|
||||
isBasicType(type){
|
||||
if(type == "java.lang.String" || type == "java.lang.Integer" || type == "java.lang.Long"
|
||||
|| type == "java.lang.Float" || type == "java.lang.Double" || type == "java.lang.Boolean"){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 10px; /* 标签与输入框之间的右边距 */
|
||||
align-items: flex-start; /* 左对齐 */
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
#tree{
|
||||
margin-left: 50px;
|
||||
white-space: pre-wrap; /* 保留换行符并折叠连续的空白字符 */
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,4 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user