arthas grpc service prototype (#2694)

This commit is contained in:
xuyang
2023-10-25 14:57:49 +08:00
committed by GitHub
parent 8d9af06592
commit 85244e9e54
65 changed files with 38787 additions and 19 deletions
+35 -3
View File
@@ -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
+126 -6
View File
@@ -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>
@@ -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();
}
}
@@ -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() {
}
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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) {
}
}
@@ -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);
}
}
}
@@ -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));
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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());
}
}
}
@@ -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." );
}
}
}
}
@@ -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 {
}
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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.");
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -41,4 +41,8 @@ public class GrpcServiceConnectionManager {
Channel getChannelWithClientInterceptor(GrpcWebClientInterceptor interceptor) {
return ClientInterceptors.intercept(channel, interceptor);
}
public ManagedChannel getChannel() {
return channel;
}
}
@@ -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
@@ -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;
}
@@ -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());
}
}
+23
View File
@@ -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?
+24
View File
@@ -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/).
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
File diff suppressed because it is too large Load Diff
+47
View File
@@ -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>
+29
View File
@@ -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
@@ -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>
+13
View File
@@ -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>
+4
View File
@@ -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