diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e197d32a2..38b7418e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ Tip: you can use `--versions` to list all available versions. -## issue +## Issue 欢迎在issue里对arthas做反馈,分享使用技巧,排查问题的经历。 diff --git a/README.md b/README.md index 2f439c102..be78b1cf4 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,14 @@ Arthas was built to solve these issues. A developer can troubleshoot your produc * Supports Linux/Mac/Windows. -### Online Tutorials(Recommend) +### Online Tutorials(Recommended) * [Arthas Basics](https://alibaba.github.io/arthas/arthas-tutorials?language=en&id=arthas-basics) * [Arthas Advanced](https://alibaba.github.io/arthas/arthas-tutorials?language=en&id=arthas-advanced) ### Quick start -#### Use `arthas-boot`(Recommend) +#### Use `arthas-boot`(Recommended) Download`arthas-boot.jar`,Start with `java` command: @@ -79,7 +79,7 @@ You can enter its interactive interface by executing `as.sh`, or execute `as.sh ### Documentation -* [Online Tutorials(Recommend)](https://alibaba.github.io/arthas/arthas-tutorials?language=en) +* [Online Tutorials(Recommended)](https://alibaba.github.io/arthas/arthas-tutorials?language=en) * [User manual](https://alibaba.github.io/arthas/en) * [Installation](https://alibaba.github.io/arthas/en/install-detail.html) * [Download](https://alibaba.github.io/arthas/en/download.html) @@ -106,7 +106,7 @@ You can enter its interactive interface by executing `as.sh`, or execute `as.sh * https://alibaba.github.io/arthas/en/thread -See what is eating your cpu (ranked by top cpu usage) and what is going on there in one glance: +See what is eating your CPU (ranked by top CPU usage) and what is going on there in one glance: ```bash $ thread -n 3 @@ -510,17 +510,18 @@ Welcome to register the company name in this issue: https://github.com/alibaba/a ![vipkid](static/vipkid.png) ![宇中科技](static/yuzhong.png) ![蘑菇财富](static/mogu.jpg) +![喔趣科技](static/woqu.png) ### Derivative Projects * [Bistoury: A project that integrates Arthas](https://github.com/qunarcorp/bistoury) * [A fork of arthas using MVEL](https://github.com/XhinLiang/arthas) -### Credit +### Credits #### Contributors -This project exists thanks to all the people who contribute. +This project exists, thanks to all the people who contributed. diff --git a/README_CN.md b/README_CN.md index a154535b3..5578bb29b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -499,6 +499,7 @@ OK ![vipkid](static/vipkid.png) ![宇中科技](static/yuzhong.png) ![蘑菇财富](static/mogu.jpg) +![喔趣科技](static/woqu.png) ### 洐生项目 diff --git a/bin/as.sh b/bin/as.sh index 9c70e3755..7974e2701 100755 --- a/bin/as.sh +++ b/bin/as.sh @@ -120,6 +120,9 @@ HEIGHT= # arthas-client terminal width WIDTH= +# select target process by classname or JARfilename +SELECT= + # Verbose, print debug info. VERBOSE=false @@ -423,6 +426,7 @@ Options and Arguments: --debug-attach Debug attach agent --tunnel-server Remote tunnel server url --agent-id Special agent id + --select select target process by classname or JARfilename -c,--command Command to execute, multiple commands separated by ; -f,--batch-file The batch file to execute @@ -443,6 +447,7 @@ EXAMPLES: ./as.sh --use-version 3.2.0 ./as.sh --session-timeout 3600 ./as.sh --attach-only + ./as.sh --select arthas-demo ./as.sh --repo-mirror aliyun --use-http WIKI: https://alibaba.github.io/arthas @@ -583,6 +588,11 @@ parse_arguments() shift # past argument shift # past value ;; + --select) + SELECT="$2" + shift # past argument + shift # past value + ;; -v|--verbose) VERBOSE=true shift # past argument @@ -634,6 +644,15 @@ parse_arguments() fi fi + # try to find target pid by --select option + if [ -z ${TARGET_PID} ] && [ ${SELECT} ]; then + local IFS=$'\n' + CANDIDATES=($(call_jps | grep -v sun.tools.jps.Jps | grep "${SELECT}" | awk '{print $0}')) + if [ ${#CANDIDATES[@]} -eq 1 ]; then + TARGET_PID=${CANDIDATES[0]} + fi + fi + # check pid if [ -z ${TARGET_PID} ] && [ ${BATCH_MODE} = false ]; then # interactive mode diff --git a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java index a447ac150..77c1ed04e 100644 --- a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java +++ b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java @@ -49,6 +49,7 @@ import com.taobao.middleware.cli.annotations.Summary; + " java -jar arthas-boot.jar -f batch.as \n" + " java -jar arthas-boot.jar --use-version 3.2.0\n" + " java -jar arthas-boot.jar --versions\n" + + " java -jar arthas-boot.jar --select arthas-demo\n" + " java -jar arthas-boot.jar --session-timeout 3600\n" + " java -jar arthas-boot.jar --attach-only\n" + " java -jar arthas-boot.jar --repo-mirror aliyun --use-http\n" + "WIKI:\n" + " https://alibaba.github.io/arthas\n") @@ -114,7 +115,9 @@ public class Bootstrap { private String statUrl; - static { + private String select; + + static { ARTHAS_LIB_DIR = new File( System.getProperty("user.home") + File.separator + ".arthas" + File.separator + "lib"); try { @@ -256,6 +259,12 @@ public class Bootstrap { this.statUrl = statUrl; } + @Option(longName = "select") + @Description("select target process by classname or JARfilename") + public void setSelect(String select) { + this.select = select; + } + public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { @@ -333,7 +342,7 @@ public class Bootstrap { // select pid if (pid < 0) { try { - pid = ProcessUtils.select(bootstrap.isVerbose(), telnetPortPid); + pid = ProcessUtils.select(bootstrap.isVerbose(), telnetPortPid, bootstrap.getSelect()); } catch (InputMismatchException e) { System.out.println("Please input an integer to select pid."); System.exit(1); @@ -690,4 +699,8 @@ public class Bootstrap { public String getStatUrl() { return statUrl; } + + public String getSelect() { + return select; + } } diff --git a/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java b/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java index 592ba7181..cd52a9897 100644 --- a/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java +++ b/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java @@ -10,6 +10,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Scanner; import java.util.InputMismatchException; @@ -28,7 +29,7 @@ public class ProcessUtils { private static String FOUND_JAVA_HOME = null; @SuppressWarnings("resource") - public static long select(boolean v, long telnetPortPid) throws InputMismatchException { + public static long select(boolean v, long telnetPortPid, String select) throws InputMismatchException { Map processMap = listProcessByJps(v); // Put the port that is already listening at the first if (telnetPortPid > 0 && processMap.containsKey(telnetPortPid)) { @@ -45,6 +46,28 @@ public class ProcessUtils { return -1; } + // select target process by the '--select' option when match only one process + if (select != null && !select.trim().isEmpty()) { + int matchedSelectCount = 0; + Long matchedPid = null; + for (Entry entry : processMap.entrySet()) { + if (entry.getValue().contains(select)) { + matchedSelectCount++; + matchedPid = entry.getKey(); + } + } + if (matchedSelectCount == 1) { + return matchedPid; + } + } + + if (processMap.size() == 1) { + Entry entry = processMap.entrySet().iterator().next(); + if (entry.getValue().contains(select)) { + return entry.getKey(); + } + } + AnsiLog.info("Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER."); // print list int count = 1; diff --git a/bytekit/README.md b/bytekit/README.md new file mode 100644 index 000000000..92a56d99e --- /dev/null +++ b/bytekit/README.md @@ -0,0 +1,222 @@ + + +## ByteKit + + +## 目标 + +1. 之前的Arthas里的字节码增强,是通过asm来处理的,代码逻辑不好修改,理解困难 +1. 基于ASM提供更高层的字节码处理能力,面向诊断/APM领域,不是通用的字节码库 +1. ByteKit期望能提供一套简洁的API,让开发人员可以比较轻松的完成字节码增强 + +## 对比 + +| 功能 | 函数Enter/Exit注入点 | 绑定数据 | inline | 防止重复增强 | 避免装箱/拆箱开销 |origin调用替换 | `@ExceptionHandler` | +| ---- | ---- |---- | :----: |:----: | :----: |:----: | :----: | +| ByteKit | `@AtEnter`
`@AtExit`
`@AtExceptionExit`
`@AtFieldAccess`
`@AtInvoke`
`@AtInvokeException`
`@AtLine`
`@AtSyncEnter`
`@AtSyncExit`
`@AtThrow`| this/args/return/throw
field
locals
子调用入参/返回值/子调用异常
line number|✓|✓|✓|✓|✓| +| ByteBuddy | `OnMethodEnter`
`@OnMethodExit`
`@OnMethodExit#onThrowable()`| this/args/return/throw
field
locals|✓|✗|✓|✓|✓| +| 传统AOP | `Enter`
`Exit`
`Exception` |this/args/return/throw|✗|✗|✗|✗|✗ + + +## 特性 + +### 1. 丰富的注入点支持 + +* `@AtEnter` 函数入口 +* `@AtExit` 函数退出 +* `@AtExceptionExit` 函数抛出异常 +* `@AtFieldAccess` 访问field +* `@AtInvoke` 在method里的子函数调用 +* `@AtInvokeException` 在method里的子函数调用抛出异常 +* `@AtLine` 在指定行号 +* `@AtSyncEnter` 进入同步块,比如`synchronized`块 +* `@AtSyncExit` 退出同步块 +* `@AtThrow` 代码里显式`throw`异常点 + + +### 2. 动态的Binding + +* `@Binding.This` this对象 +* `@@Binding.Class` Class对象 +* `@Binding.Method` 函数调用的 Method 对象 +* `@Binding.MethodName` 函数的名字 +* `@Binding.MethodDesc` 函数的desc +* `@Binding.Return` 函数调用的返回值 +* `@Binding.Throwable` 函数里抛出的异常 +* `@Binding.Args` 函数调用的入参 +* `@Binding.ArgNames` 函数调用的入参的名字 + + +* `@Binding.LocalVars` 局部变量 +* `@Binding.LocalVarNames` 局部变量的名字 +* `@Binding.Field` field对象属性字段 + + +* `@Binding.InvokeArgs` method里的子函数调用的入参 +* `@Binding.InvokeReturn` method里的子函数调用的返回值 +* `@Binding.InvokeMethodName` method里的子函数调用的名字 +* `@Binding.InvokeMethodOwner` method里的子函数调用的类名 +* `@Binding.InvokeMethodDeclaration` method里的子函数调用的desc + + +* `@Binding.Line` 行号 +* `@Binding.Monitor` 同步块里监控的对象 + + +### 3. 可编程的异常处理 + +* `@ExceptionHandler` 在插入的增强代码,可以用`try/catch`块包围起来 + +### 4. inline支持 + +增强的代码 和 异常处理代码都可以通过 inline技术内联到原来的类里,达到最理想的增强效果。 + +### 5. invokeOrigin 技术 + +通常,我们要增强一个类,就想要办法在函数前后插入一个static的回调函数,但这样子局限太大。那么有没有更灵活的方式呢? + +比如有一个 hello() 函数: + +```java + public String hello(String str) { + return "hello " + str; + } +``` + +我们想对它做增强,那么可以编写下面的代码: + +```java + public String hello(String str) { + System.out.println("before"); + Object value = InstrumentApi.invokeOrigin(); + System.out.println("after, result: " + value); + return object; + } +``` + +增强后的结果是: + +```java + public String hello(String str) { + System.out.println("before"); + Object value = "hello " + str; + System.out.println("after, result: " + value); + return object; + } +``` + +**这种方式可以随意插入代码,非常灵活。** + +## 示例 + +以`ByteKitDemo.java`为例说明,[[查看源文件](src/test/java/com/example/ByteKitDemo.java)]。 + +### 1. 定义注入点和Binding数据 + +* 在下面的 SampleInterceptor 时定义了要注入 `@AtEnter`/`@AtExit`/`@AtExceptionExit`三个地方, +* 用`@Binding`绑定了不同的数据 +* 在`@AtEnter`里配置了 `inline = true`,则说明插入的`SampleInterceptor#atEnter`函数本身会被inline掉 +* 配置了 `suppress = RuntimeException.class` 和 `suppressHandler = PrintExceptionSuppressHandler.class`,说明插入的代码会被 `try/catch` 包围 + +```java + public static class SampleInterceptor { + + @AtEnter(inline = true, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class) + public static void atEnter(@Binding.This Object object, + @Binding.Class Object clazz, + @Binding.Args Object[] args, + @Binding.MethodName String methodName, + @Binding.MethodDesc String methodDesc) { + System.out.println("atEnter, args[0]: " + args[0]); + } + + @AtExit(inline = true) + public static void atExit(@Binding.Return Object returnObject) { + System.out.println("atExit, returnObject: " + returnObject); + } + + @AtExceptionExit(inline = true, onException = RuntimeException.class) + public static void atExceptionExit(@Binding.Throwable RuntimeException ex, + @Binding.Field(name = "exceptionCount") int exceptionCount) { + System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount); + } + } +``` + +### 2. @ExceptionHandler + +在上面的 `@AtEnter`配置里,生成的代码会被 try/catch 包围,那么具体的内容是在`PrintExceptionSuppressHandler`里 + +```java + public static class PrintExceptionSuppressHandler { + + @ExceptionHandler(inline = true) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.out.println("exception handler: " + clazz); + e.printStackTrace(); + } + } +``` + +### 3. 查看反编译结果 + +原始的Sample类是: + +```java + public static class Sample { + private int exceptionCount = 0; + + public String hello(String str, boolean exception) { + if (exception) { + exceptionCount++; + throw new RuntimeException("test exception, str: " + str); + } + return "hello " + str; + } + } +``` + +增强后的字节码,再反编译: + +```java +package com.example; + +public static class ByteKitDemo.Sample { + private int exceptionCount = 0; + + public String hello(String string, boolean bl) { + try { + String string2 = "(Ljava/lang/String;Z)Ljava/lang/String;"; + String string3 = "hello"; + Object[] arrobject = new Object[]{string, new Boolean(bl)}; + Class class_ = ByteKitDemo.Sample.class; + ByteKitDemo.Sample sample = this; + System.out.println("atEnter, args[0]: " + arrobject[0]); + } + catch (RuntimeException runtimeException) { + Class class_ = ByteKitDemo.Sample.class; + RuntimeException runtimeException2 = runtimeException; + System.out.println("exception handler: " + class_); + runtimeException2.printStackTrace(); + } + try { + String string4; + void str; + void exception; + if (exception != false) { + ++this.exceptionCount; + throw new RuntimeException("test exception, str: " + (String)str); + } + String string5 = string4 = "hello " + (String)str; + System.out.println("atExit, returnObject: " + string5); + return string4; + } + catch (RuntimeException runtimeException) { + int n = this.exceptionCount; + RuntimeException runtimeException3 = runtimeException; + System.out.println("atExceptionExit, ex: " + runtimeException3.getMessage() + ", field exceptionCount: " + n); + throw runtimeException; + } + } +} +``` diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java index b8558e1b9..84ee37e82 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java @@ -7,8 +7,6 @@ import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; -import com.alibaba.arthas.deps.org.objectweb.asm.Type; - import net.bytebuddy.agent.ByteBuddyAgent; public class AgentUtils { @@ -17,6 +15,11 @@ public class AgentUtils { static final Instrumentation instance = ByteBuddyAgent.install(); } + public static Instrumentation install() { + return InstrumentationHolder.instance; + + } + public static void redefine(Class clazz, byte[] classFile) throws ClassNotFoundException, UnmodifiableClassException { ClassDefinition classDefinition = new ClassDefinition(clazz, classFile); diff --git a/bytekit/src/test/java/com/example/ByteKitDemo.java b/bytekit/src/test/java/com/example/ByteKitDemo.java new file mode 100644 index 000000000..bd6139f00 --- /dev/null +++ b/bytekit/src/test/java/com/example/ByteKitDemo.java @@ -0,0 +1,124 @@ +package com.example; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.utils.AgentUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; + +/** + * + * @author hengyunabc 2020-05-21 + * + */ +public class ByteKitDemo { + + public static class Sample { + private int exceptionCount = 0; + + public String hello(String str, boolean exception) { + if (exception) { + exceptionCount++; + throw new RuntimeException("test exception, str: " + str); + } + return "hello " + str; + } + } + + public static class PrintExceptionSuppressHandler { + + @ExceptionHandler(inline = true) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.out.println("exception handler: " + clazz); + e.printStackTrace(); + } + } + + public static class SampleInterceptor { + + @AtEnter(inline = true, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class) + public static void atEnter(@Binding.This Object object, + @Binding.Class Object clazz, + @Binding.Args Object[] args, + @Binding.MethodName String methodName, + @Binding.MethodDesc String methodDesc) { + System.out.println("atEnter, args[0]: " + args[0]); + } + + @AtExit(inline = true) + public static void atExit(@Binding.Return Object returnObject) { + System.out.println("atExit, returnObject: " + returnObject); + } + + @AtExceptionExit(inline = true, onException = RuntimeException.class) + public static void atExceptionExit(@Binding.Throwable RuntimeException ex, + @Binding.Field(name = "exceptionCount") int exceptionCount) { + System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount); + } + } + + public static void main(String[] args) throws Exception { + AgentUtils.install(); + + // 启动Sample,不断执行 + final Sample sample = new Sample(); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 100; ++i) { + try { + TimeUnit.SECONDS.sleep(3); + String result = sample.hello("" + i, (i % 3) == 0); + System.out.println("call hello result: " + result); + } catch (Throwable e) { + // ignore + System.out.println("call hello exception: " + e.getMessage()); + } + } + } + }); + t.start(); + + // 解析定义的 Interceptor类 和相关的注解 + DefaultInterceptorClassParser interceptorClassParser = new DefaultInterceptorClassParser(); + List processors = interceptorClassParser.parse(SampleInterceptor.class); + + // 加载字节码 + ClassNode classNode = AsmUtils.loadClass(Sample.class); + + // 对加载到的字节码做增强处理 + for (MethodNode methodNode : classNode.methods) { + if (methodNode.name.equals("hello")) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + for (InterceptorProcessor interceptor : processors) { + interceptor.process(methodProcessor); + } + } + } + + // 获取增强后的字节码 + byte[] bytes = AsmUtils.toBytes(classNode); + + // 查看反编译结果 + System.out.println(Decompiler.decompile(bytes)); + + // 等待,查看未增强里的输出结果 + TimeUnit.SECONDS.sleep(10); + + // 通过 reTransform 增强类 + AgentUtils.reTransform(Sample.class, bytes); + System.in.read(); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java index bcfefee0e..fa7511b19 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java @@ -6,6 +6,8 @@ package com.taobao.arthas.core.advisor; */ public interface AdviceListener { + long id(); + /** * 监听器创建
* 监听器被注册时触发 diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java index cf1012e55..21315bac3 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java @@ -1,5 +1,7 @@ package com.taobao.arthas.core.advisor; +import java.util.concurrent.atomic.AtomicLong; + import com.taobao.arthas.core.command.express.ExpressException; import com.taobao.arthas.core.command.express.ExpressFactory; import com.taobao.arthas.core.shell.command.CommandProcess; @@ -14,7 +16,14 @@ import com.taobao.arthas.core.util.StringUtils; * */ public abstract class AdviceListenerAdapter implements AdviceListener, ProcessAware { + private static final AtomicLong ID_GENERATOR = new AtomicLong(0); private Process process; + private long id = ID_GENERATOR.addAndGet(1); + + @Override + public long id() { + return id; + } @Override public void create() { diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java index bb3aca3d7..ab710776d 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java @@ -3,13 +3,15 @@ package com.taobao.arthas.core.advisor; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; -import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +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.server.ArthasBootstrap; import com.taobao.arthas.core.shell.system.ExecStatus; +import com.taobao.arthas.core.shell.system.Process; import com.taobao.arthas.core.shell.system.ProcessAware; /** @@ -49,43 +51,53 @@ import com.taobao.arthas.core.shell.system.ProcessAware; * */ public class AdviceListenerManager { - - private static Timer timer = ArthasBootstrap.getInstance().getTimer(); + private static final Logger logger = LoggerFactory.getLogger(AdviceListenerManager.class); private static final FakeBootstrapClassLoader FAKEBOOTSTRAPCLASSLOADER = new FakeBootstrapClassLoader(); static { - timer.scheduleAtFixedRate(new TimerTask() { - + // 清理失效的 AdviceListener + ArthasBootstrap.getInstance().getScheduledExecutorService().scheduleWithFixedDelay(new Runnable() { @Override public void run() { - if (adviceListenerMap != null) { - for (Entry entry : adviceListenerMap.entrySet()) { - ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue(); - synchronized (adviceListenerManager) { - for (Entry> eee : adviceListenerManager.map.entrySet()) { - List listeners = eee.getValue(); - List newResult = new ArrayList(); - for (AdviceListener listener : listeners) { - if (listener instanceof ProcessAware) { - ProcessAware processAware = (ProcessAware) listener; - ExecStatus status = processAware.getProcess().status(); - if (!status.equals(ExecStatus.TERMINATED)) { - newResult.add(listener); + try { + if (adviceListenerMap != null) { + for (Entry entry : adviceListenerMap.entrySet()) { + ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue(); + synchronized (adviceListenerManager) { + for (Entry> eee : adviceListenerManager.map.entrySet()) { + List listeners = eee.getValue(); + List newResult = new ArrayList(); + 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); - } + 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 + } } } - - }, 3000, 3000); + }, 3, 3, TimeUnit.SECONDS); } static private ConcurrentWeakKeyHashMap adviceListenerMap = new ConcurrentWeakKeyHashMap(); diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java index 2b607da6f..aadcbca68 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java @@ -19,8 +19,8 @@ public class AdviceWeaver { private static final Logger logger = LoggerFactory.getLogger(AdviceWeaver.class); // 通知监听器集合 - private final static Map advices - = new ConcurrentHashMap(); + private final static Map advices + = new ConcurrentHashMap(); /** * 注册监听器 @@ -28,13 +28,13 @@ public class AdviceWeaver { * @param adviceId 通知ID * @param listener 通知监听器 */ - public static void reg(int adviceId, AdviceListener listener) { + public static void reg(AdviceListener listener) { // 触发监听器创建 listener.create(); // 注册监听器 - advices.put(adviceId, listener); + advices.put(listener.id(), listener); } /** @@ -42,28 +42,28 @@ public class AdviceWeaver { * * @param adviceId 通知ID */ - public static void unReg(int adviceId) { - - // 注销监听器 - final AdviceListener listener = advices.remove(adviceId); - - // 触发监听器销毁 + public static void unReg(AdviceListener listener) { if (null != listener) { + // 注销监听器 + advices.remove(listener.id()); + + // 触发监听器销毁 listener.destroy(); } - } + public static AdviceListener listener(long id) { + return advices.get(id); + } /** * 恢复监听 * - * @param adviceId 通知ID * @param listener 通知监听器 */ - public static void resume(int adviceId, AdviceListener listener) { + public static void resume(AdviceListener listener) { // 注册监听器 - advices.put(adviceId, listener); + advices.put(listener.id(), listener); } /** @@ -71,7 +71,7 @@ public class AdviceWeaver { * * @param adviceId 通知ID */ - public static AdviceListener suspend(int adviceId) { + public static AdviceListener suspend(long adviceId) { // 注销监听器 return advices.remove(adviceId); } diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java index 431073b0e..d7cba1ba1 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java @@ -66,9 +66,10 @@ public class Enhancer implements ClassFileTransformer { private final AdviceListener listener; private final boolean isTracing; private final boolean skipJDKTrace; - private final Set> matchingClasses; + private final Matcher classNameMatcher; private final Matcher methodNameMatcher; private final EnhancerAffect affect; + private Set> matchingClasses = null; // 被增强的类的缓存 private final static Map/* Class */, Object> classBytesCache = new WeakHashMap, Object>(); @@ -86,14 +87,15 @@ public class Enhancer implements ClassFileTransformer { * @param methodNameMatcher 方法名匹配 * @param affect 影响统计 */ - Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Set> matchingClasses, - Matcher methodNameMatcher, EnhancerAffect affect) { + public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher, + Matcher methodNameMatcher) { this.listener = listener; this.isTracing = isTracing; this.skipJDKTrace = skipJDKTrace; - this.matchingClasses = matchingClasses; + this.classNameMatcher = classNameMatcher; this.methodNameMatcher = methodNameMatcher; - this.affect = affect; + this.affect = new EnhancerAffect(); + affect.setListenerId(listener.id()); } public static class SpyInterceptor { @@ -196,7 +198,7 @@ public class Enhancer implements ClassFileTransformer { // 这里要再次过滤一次,为啥?因为在transform的过程中,有可能还会再诞生新的类 // 所以需要将之前需要转换的类集合传递下来,再次进行判断 - if (!matchingClasses.contains(classBeingRedefined)) { + if (matchingClasses != null && !matchingClasses.contains(classBeingRedefined)) { return null; } @@ -352,6 +354,9 @@ public class Enhancer implements ClassFileTransformer { 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); } @@ -407,41 +412,33 @@ public class Enhancer implements ClassFileTransformer { * @return 增强影响范围 * @throws UnmodifiableClassException 增强失败 */ - public static synchronized EnhancerAffect enhance(final Instrumentation inst, final AdviceListener listener, - final boolean isTracing, final boolean skipJDKTrace, final Matcher classNameMatcher, - final Matcher methodNameMatcher) throws UnmodifiableClassException { - - final EnhancerAffect affect = new EnhancerAffect(); - + public synchronized EnhancerAffect enhance(final Instrumentation inst) throws UnmodifiableClassException { // 获取需要增强的类集合 - final Set> enhanceClassSet = GlobalOptions.isDisableSubClass + this.matchingClasses = GlobalOptions.isDisableSubClass ? SearchUtils.searchClass(inst, classNameMatcher) : SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher)); // 过滤掉无法被增强的类 - filter(enhanceClassSet); + filter(matchingClasses); - // 构建增强器 - final Enhancer enhancer = new Enhancer(listener, isTracing, skipJDKTrace, enhanceClassSet, methodNameMatcher, - affect); - affect.setTransformer(enhancer); + affect.setTransformer(this); try { - ArthasBootstrap.getInstance().getTransformerManager().addTransformer(enhancer, isTracing); + ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing); //inst.addTransformer(enhancer, true); // 批量增强 if (GlobalOptions.isBatchReTransform) { - final int size = enhanceClassSet.size(); + final int size = matchingClasses.size(); final Class[] classArray = new Class[size]; - arraycopy(enhanceClassSet.toArray(), 0, classArray, 0, 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 : enhanceClassSet) { + for (Class clazz : matchingClasses) { try { inst.retransformClasses(clazz); logger.info("Success to transform class: " + clazz); @@ -506,7 +503,7 @@ public class Enhancer implements ClassFileTransformer { } // 批量增强 - public static void enhance(Instrumentation inst, ClassFileTransformer transformer, Set> classes) + private static void enhance(Instrumentation inst, ClassFileTransformer transformer, Set> classes) throws UnmodifiableClassException { try { inst.addTransformer(transformer, true); diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java index 492cfb085..08b7fe15a 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java @@ -66,6 +66,8 @@ public class TransformerManager { } public void destroy() { + watchTransformers.clear(); + traceTransformers.clear(); instrumentation.removeTransformer(classFileTransformer); } diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/HelpCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/HelpCommand.java index 0b37d70ef..6e1d5f23e 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/basic1000/HelpCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/HelpCommand.java @@ -1,6 +1,9 @@ package com.taobao.arthas.core.command.basic1000; -import com.taobao.arthas.core.command.model.*; +import com.taobao.arthas.core.command.model.ArgumentVO; +import com.taobao.arthas.core.command.model.CommandOptionVO; +import com.taobao.arthas.core.command.model.CommandVO; +import com.taobao.arthas.core.command.model.HelpModel; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; import com.taobao.arthas.core.shell.command.AnnotatedCommand; diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/DumpClassCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/DumpClassCommand.java index f59434808..90c860942 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/DumpClassCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/DumpClassCommand.java @@ -2,7 +2,6 @@ package com.taobao.arthas.core.command.klass100; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; -import com.taobao.arthas.core.advisor.Enhancer; import com.taobao.arthas.core.command.Constants; import com.taobao.arthas.core.command.model.*; import com.taobao.arthas.core.shell.cli.Completion; @@ -10,6 +9,7 @@ import com.taobao.arthas.core.shell.cli.CompletionUtils; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.ClassUtils; +import com.taobao.arthas.core.util.InstrumentationUtils; import com.taobao.arthas.core.util.SearchUtils; import com.taobao.arthas.core.util.affect.RowAffect; import com.taobao.middleware.cli.annotations.Argument; @@ -18,18 +18,15 @@ import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; import com.taobao.middleware.cli.annotations.Option; import com.taobao.middleware.cli.annotations.Summary; -import com.taobao.text.Color; -import com.taobao.text.Decoration; -import com.taobao.text.ui.LabelElement; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; import java.io.File; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; -import static com.taobao.text.ui.Element.label; /** * Dump class byte array @@ -80,7 +77,7 @@ public class DumpClassCommand extends AnnotatedCommand { @Option(shortName = "l", longName = "limit") @Description("The limit of dump classes size, default value is 5") - @DefaultValue("5") + @DefaultValue("50") public void setLimit(int limit) { this.limit = limit; } @@ -161,7 +158,7 @@ public class DumpClassCommand extends AnnotatedCommand { } else { transformer = new ClassDumpTransformer(classes); } - Enhancer.enhance(inst, transformer, classes); + InstrumentationUtils.retransformClasses(inst, transformer, classes); return transformer.getDumpResult(); } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java index db7571ce7..8b7a60a50 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java @@ -3,29 +3,27 @@ package com.taobao.arthas.core.command.klass100; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.command.Constants; -import com.taobao.arthas.core.command.model.*; +import com.taobao.arthas.core.command.model.ClassVO; +import com.taobao.arthas.core.command.model.JadModel; +import com.taobao.arthas.core.command.model.MessageModel; +import com.taobao.arthas.core.command.model.RowAffectModel; +import com.taobao.arthas.core.command.model.StatusModel; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.ClassUtils; import com.taobao.arthas.core.util.Decompiler; +import com.taobao.arthas.core.util.InstrumentationUtils; import com.taobao.arthas.core.util.SearchUtils; -import com.taobao.arthas.core.util.TypeRenderUtils; import com.taobao.arthas.core.util.affect.RowAffect; import com.taobao.middleware.cli.annotations.Argument; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; import com.taobao.middleware.cli.annotations.Option; import com.taobao.middleware.cli.annotations.Summary; -import com.taobao.text.Color; -import com.taobao.text.Decoration; -import com.taobao.text.lang.LangRenderUtil; -import com.taobao.text.ui.LabelElement; -import com.taobao.text.util.RenderUtil; import java.io.File; -import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.HashSet; import java.util.List; @@ -126,27 +124,6 @@ public class JadCommand extends AnnotatedCommand { } } - - public static void retransformClasses(Instrumentation inst, ClassFileTransformer transformer, Set> classes) { - try { - inst.addTransformer(transformer, true); - - for(Class clazz : classes) { - try{ - inst.retransformClasses(clazz); - }catch(Throwable e) { - String errorMsg = "retransformClasses class error, name: " + clazz.getName(); - if(ClassUtils.isLambdaClass(clazz) && e instanceof VerifyError) { - errorMsg += ", Please ignore lambda class VerifyError: https://github.com/alibaba/arthas/issues/675"; - } - logger.error(errorMsg, e); - } - } - } finally { - inst.removeTransformer(transformer); - } - } - private StatusModel processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst, Set> matchedClasses, Set> withInnerClasses) { StatusModel statusModel = new StatusModel(); Class c = matchedClasses.iterator().next(); @@ -155,7 +132,7 @@ public class JadCommand extends AnnotatedCommand { try { ClassDumpTransformer transformer = new ClassDumpTransformer(allClasses); - retransformClasses(inst, transformer, allClasses); + InstrumentationUtils.retransformClasses(inst, transformer, allClasses); Map, File> classFiles = transformer.getDumpResult(); File classFile = classFiles.get(c); diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/SearchMethodCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/SearchMethodCommand.java index fc947fa63..d9add0132 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/SearchMethodCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/SearchMethodCommand.java @@ -6,6 +6,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Set; +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.command.Constants; import com.taobao.arthas.core.command.model.SearchMethodModel; import com.taobao.arthas.core.command.model.MethodVO; @@ -43,6 +45,7 @@ import com.taobao.middleware.cli.annotations.Summary; " sm -Ed org\\\\.apache\\\\.commons\\\\.lang\\.StringUtils .*\n" + Constants.WIKI + Constants.WIKI_HOME + "sm") public class SearchMethodCommand extends AnnotatedCommand { + private static final Logger logger = LoggerFactory.getLogger(SearchMethodCommand.class); private String classPattern; private String methodPattern; @@ -100,23 +103,31 @@ public class SearchMethodCommand extends AnnotatedCommand { return; } for (Class clazz : matchedClasses) { - for (Constructor constructor : clazz.getDeclaredConstructors()) { - if (!methodNameMatcher.matching("")) { - continue; + try { + for (Constructor constructor : clazz.getDeclaredConstructors()) { + if (!methodNameMatcher.matching("")) { + continue; + } + + MethodVO methodInfo = ClassUtils.createMethodInfo(constructor, clazz, isDetail); + process.appendResult(new SearchMethodModel(methodInfo, isDetail)); + affect.rCnt(1); } - MethodVO methodInfo = ClassUtils.createMethodInfo(constructor, clazz, isDetail); - process.appendResult(new SearchMethodModel(methodInfo, isDetail)); - affect.rCnt(1); - } - - for (Method method : clazz.getDeclaredMethods()) { - if (!methodNameMatcher.matching(method.getName())) { - continue; + for (Method method : clazz.getDeclaredMethods()) { + if (!methodNameMatcher.matching(method.getName())) { + continue; + } + MethodVO methodInfo = ClassUtils.createMethodInfo(method, clazz, isDetail); + process.appendResult(new SearchMethodModel(methodInfo, isDetail)); + affect.rCnt(1); } - MethodVO methodInfo = ClassUtils.createMethodInfo(method, clazz, isDetail); - process.appendResult(new SearchMethodModel(methodInfo, isDetail)); - affect.rCnt(1); + } catch (Error e) { + //print failed className + String msg = String.format("process class failed: %s, error: %s", clazz.getName(), e.toString()); + logger.error(msg, e); + process.end(1, msg); + return; } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java index 248487f9f..7bd5e3fc5 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java @@ -8,6 +8,7 @@ import java.util.List; 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.AdviceWeaver; import com.taobao.arthas.core.advisor.Enhancer; import com.taobao.arthas.core.advisor.InvokeTraceable; import com.taobao.arthas.core.command.model.EnhancerAffectModel; @@ -22,6 +23,8 @@ import com.taobao.arthas.core.util.Constants; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.affect.EnhancerAffect; import com.taobao.arthas.core.util.matcher.Matcher; +import com.taobao.middleware.cli.annotations.Description; +import com.taobao.middleware.cli.annotations.Option; /** * @author beiwei30 on 29/11/2016. @@ -36,6 +39,14 @@ public abstract class EnhancerCommand extends AnnotatedCommand { protected Matcher classNameMatcher; protected Matcher methodNameMatcher; + protected long listenerId; + + @Option(longName = "listenerId") + @Description("The special listenerId") + public void setListenerId(long listenerId) { + this.listenerId = listenerId; + } + /** * 类名匹配 * @@ -57,6 +68,15 @@ public abstract class EnhancerCommand extends AnnotatedCommand { */ protected abstract AdviceListener getAdviceListener(CommandProcess process); + AdviceListener getAdviceListenerWithId(CommandProcess process) { + if (listenerId != 0) { + AdviceListener listener = AdviceWeaver.listener(listenerId); + if (listener != null) { + return listener; + } + } + return getAdviceListener(process); + } @Override public void process(final CommandProcess process) { // ctrl-C support @@ -99,7 +119,7 @@ public abstract class EnhancerCommand extends AnnotatedCommand { int lock = session.getLock(); try { Instrumentation inst = session.getInstrumentation(); - AdviceListener listener = getAdviceListener(process); + AdviceListener listener = getAdviceListenerWithId(process); if (listener == null) { logger.error("advice listener is null"); process.end(-1, "cannot operate the current command, pls. check arthas.log"); @@ -110,8 +130,10 @@ public abstract class EnhancerCommand extends AnnotatedCommand { skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace(); } - EnhancerAffect effect = Enhancer.enhance(inst, listener, listener instanceof InvokeTraceable, - skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher()); + Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher()); + // 注册通知监听器 + process.register(listener, enhancer); + EnhancerAffect effect = enhancer.enhance(inst); if (effect.cCnt() == 0 || effect.mCnt() == 0) { // no class effected @@ -129,8 +151,6 @@ public abstract class EnhancerCommand extends AnnotatedCommand { // 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃 if (session.getLock() == lock) { - // 注册通知监听器 - process.register(lock, listener, effect.getTransformer()); if (process.isForeground()) { process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n"); } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java index 910508eb9..be109d791 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java @@ -10,6 +10,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; @@ -17,6 +18,7 @@ import com.taobao.arthas.common.OSUtils; import com.taobao.arthas.core.command.Constants; import com.taobao.arthas.core.command.model.MessageModel; import com.taobao.arthas.core.command.model.ProfilerModel; +import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; @@ -93,6 +95,11 @@ public class ProfilerCommand extends AnnotatedCommand { */ private boolean alluser; + /** + * run profiling for seconds + */ + private Long duration; + private static String libPath; private static AsyncProfiler profiler = null; @@ -182,6 +189,12 @@ public class ProfilerCommand extends AnnotatedCommand { this.alluser = alluser; } + @Option(shortName = "d", longName = "duration") + @Description("run profiling for seconds") + public void setDuration(long duration) { + this.duration = duration; + } + private AsyncProfiler profilerInstance() { if (profiler != null) { return profiler; @@ -258,7 +271,7 @@ public class ProfilerCommand extends AnnotatedCommand { } @Override - public void process(CommandProcess process) { + public void process(final CommandProcess process) { int status = 0; try { ProfilerAction profilerAction = ProfilerAction.valueOf(action); @@ -269,7 +282,7 @@ public class ProfilerCommand extends AnnotatedCommand { return; } - AsyncProfiler asyncProfiler = this.profilerInstance(); + final AsyncProfiler asyncProfiler = this.profilerInstance(); if (ProfilerAction.execute.equals(profilerAction)) { if (actionArg == null) { @@ -283,13 +296,30 @@ public class ProfilerCommand extends AnnotatedCommand { String executeArgs = executeArgs(ProfilerAction.start); String result = execute(asyncProfiler, executeArgs); appendResult(process, executeArgs, result); - } else if (ProfilerAction.stop.equals(profilerAction)) { - if (this.file == null) { - this.file = new File("arthas-output", - new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "." + this.format) - .getAbsolutePath(); + + if (this.duration != null) { + final String outputFile = outputFile(); + final String stopExecuteArgs = executeArgs(ProfilerAction.stop); + process.appendResult(new MessageModel(String.format("profiler will silent stop after %d seconds.", this.duration.longValue()))); + process.appendResult(new MessageModel("profiler output file will be: " + new File(outputFile).getAbsolutePath())); + ArthasBootstrap.getInstance().getScheduledExecutorService().schedule(new Runnable() { + @Override + public void run() { + try { + logger.info("profiler output file: " + new File(outputFile).getAbsolutePath() + "\n"); + String result = execute(asyncProfiler, stopExecuteArgs); + logger.info("profiler stop result: " + result); + } catch (Throwable e) { + logger.error("", e); + } + } + }, this.duration, TimeUnit.SECONDS); } - process.appendResult(new MessageModel("profiler output file: " + new File(this.file).getAbsolutePath())); + + } else if (ProfilerAction.stop.equals(profilerAction)) { + String outputFile = outputFile(); + process.appendResult(new MessageModel("profiler output file: " + new File(outputFile).getAbsolutePath())); + String executeArgs = executeArgs(ProfilerAction.stop); String result = execute(asyncProfiler, executeArgs); @@ -353,6 +383,14 @@ public class ProfilerCommand extends AnnotatedCommand { process.appendResult(new ProfilerModel(action, executeArgs, result)); } + private String outputFile() { + if (this.file == null) { + this.file = new File("arthas-output", + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "." + this.format).getAbsolutePath(); + } + return file; + } + private List events() { List result = new ArrayList(); diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java index 8467db709..74b5f0bb1 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java @@ -53,6 +53,7 @@ import static java.lang.String.format; Constants.WIKI + Constants.WIKI_HOME + "tt") public class TimeTunnelCommand extends EnhancerCommand { // 时间隧道(时间碎片的集合) + // TODO 并非线程安全? private static final Map timeFragmentMap = new LinkedHashMap(); // 时间碎片序列生成器 private static final AtomicInteger sequence = new AtomicInteger(1000); diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchCommand.java index 785894d81..7126f1181 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchCommand.java @@ -42,7 +42,7 @@ public class WatchCommand extends EnhancerCommand { private Integer sizeLimit = 10 * 1024 * 1024; private boolean isRegEx = false; private int numberOfLimit = 100; - + @Argument(index = 0, argName = "class-pattern") @Description("The full qualified class name you want to watch") public void setClassPattern(String classPattern) { diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java index 6e8d105e7..c7ff631f4 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java @@ -32,15 +32,16 @@ public class ResultViewResolver { private void registerResultViews() { try { - registerView(new SessionModel(), new SessionView()); registerView(new StatusModel(), new StatusView()); - registerView(new WatchModel(), new WatchView()); - registerView(new EnhancerAffectModel(), new EnhancerAffectView()); registerView(new VersionModel(), new VersionView()); - registerView(new PropertyModel(), new PropertyView()); registerView(new MessageModel(), new MessageView()); registerView(new HelpModel(), new HelpView()); //registerView(new HistoryModel(), new HistoryView()); + registerView(new EchoModel(), new EchoView()); + registerView(new SessionModel(), new SessionView()); + registerView(new WatchModel(), new WatchView()); + registerView(new EnhancerAffectModel(), new EnhancerAffectView()); + registerView(new PropertyModel(), new PropertyView()); registerView(new SearchClassModel(), new SearchClassView()); registerView(new RowAffectModel(), new RowAffectView()); registerView(new SearchMethodModel(), new SearchMethodView()); @@ -65,7 +66,6 @@ public class ResultViewResolver { registerView(new TraceModel(), new TraceView()); registerView(new OgnlModel(), new OgnlView()); registerView(new PwdModel(), new PwdView()); - registerView(new EchoModel(), new EchoView()); registerView(new CatModel(), new CatView()); } catch (Throwable e) { logger.error("register result view failed", e); diff --git a/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumerHelper.java b/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumerHelper.java index 00be6aa84..534b68cc3 100644 --- a/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumerHelper.java +++ b/core/src/main/java/com/taobao/arthas/core/distribution/ResultConsumerHelper.java @@ -1,13 +1,19 @@ package com.taobao.arthas.core.distribution; import com.alibaba.fastjson.JSON; -import com.taobao.arthas.core.command.model.*; +import com.taobao.arthas.core.command.model.CatModel; +import com.taobao.arthas.core.command.model.ClassSetVO; +import com.taobao.arthas.core.command.model.ResultModel; +import com.taobao.arthas.core.command.model.TraceModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java index f7b4806be..a5ec0afa1 100644 --- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java @@ -1,5 +1,25 @@ package com.taobao.arthas.core.server; +import java.arthas.SpyAPI; +import java.io.File; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.net.URI; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Timer; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + import com.alibaba.arthas.deps.ch.qos.logback.classic.LoggerContext; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; @@ -32,22 +52,6 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.util.concurrent.EventExecutorGroup; -import java.arthas.SpyAPI; -import java.io.File; -import java.io.IOException; -import java.lang.instrument.Instrumentation; -import java.lang.reflect.Method; -import java.net.URI; -import java.security.CodeSource; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - - /** * @author vlinux on 15/5/2. @@ -70,8 +74,8 @@ public class ArthasBootstrap { private Instrumentation instrumentation; private Thread shutdown; private ShellServer shellServer; + private ScheduledExecutorService executorService; private SessionManager sessionManager; - private ExecutorService executorService; private TunnelClient tunnelClient; private File arthasOutputDir; @@ -100,7 +104,7 @@ public class ArthasBootstrap { // 4. start agent server bind(configure); - executorService = Executors.newCachedThreadPool(new ThreadFactory() { + executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { final Thread t = new Thread(r, "as-command-execute-daemon"); @@ -133,7 +137,7 @@ public class ArthasBootstrap { /** *
          * 脚本里传过来的配置项,即命令行参数 > System Env > System Properties > arthas.properties
-         * arthas.properties 指供一个配置项,可以反转优先级。 arthas.config.overrideAll=true
+         * arthas.properties 提供一个配置项,可以反转优先级。 arthas.config.overrideAll=true
          * https://github.com/alibaba/arthas/issues/986
          * 
*/ @@ -238,14 +242,14 @@ public class ArthasBootstrap { tunnelClient.setTunnelServerUrl(configure.getTunnelServer()); // ws://127.0.0.1:8563/ws String host = "127.0.0.1"; - if (configure.getIp() != null) { + if(configure.getIp() != null) { host = configure.getIp(); } URI uri = new URI("ws", null, host, configure.getHttpPort(), "/ws", null, null); tunnelClient.setLocalServerUrl(uri.toString()); ChannelFuture channelFuture = tunnelClient.start(); channelFuture.await(10, TimeUnit.SECONDS); - if (channelFuture.isSuccess()) { + if(channelFuture.isSuccess()) { agentId = tunnelClient.getId(); } } @@ -255,9 +259,9 @@ public class ArthasBootstrap { try { ShellServerOptions options = new ShellServerOptions() - .setInstrumentation(instrumentation) - .setPid(PidUtils.currentLongPid()) - .setSessionTimeout(configure.getSessionTimeout() * 1000); + .setInstrumentation(instrumentation) + .setPid(PidUtils.currentLongPid()) + .setSessionTimeout(configure.getSessionTimeout() * 1000); if (agentId != null) { Map welcomeInfos = new HashMap(); @@ -423,6 +427,10 @@ public class ArthasBootstrap { return this.timer; } + public ScheduledExecutorService getScheduledExecutorService() { + return this.executorService; + } + public Instrumentation getInstrumentation() { return this.instrumentation; } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java index f0ae11652..3cdf16af3 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java @@ -131,10 +131,9 @@ public interface CommandProcess extends Tty { /** * Register listener * - * @param lock the lock for enhance class * @param listener */ - void register(int lock, AdviceListener listener, ClassFileTransformer transformer); + void register(AdviceListener listener, ClassFileTransformer transformer); /** * Unregister listener diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java b/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java index e5198aee1..d891d258f 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/SessionManager.java @@ -1,7 +1,7 @@ package com.taobao.arthas.core.shell.session; +import com.taobao.arthas.core.shell.system.JobController; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; -import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; import java.lang.instrument.Instrumentation; @@ -25,5 +25,5 @@ public interface SessionManager { Instrumentation getInstrumentation(); - JobControllerImpl getJobController(); + JobController getJobController(); } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java index 22ec12e62..fb6cc5bd4 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/session/impl/SessionManagerImpl.java @@ -11,8 +11,8 @@ import com.taobao.arthas.core.shell.ShellServerOptions; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.session.SessionManager; import com.taobao.arthas.core.shell.system.Job; +import com.taobao.arthas.core.shell.system.JobController; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; -import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; import java.lang.instrument.Instrumentation; import java.util.*; @@ -28,7 +28,7 @@ public class SessionManagerImpl implements SessionManager { private final ArthasBootstrap bootstrap; private final InternalCommandManager commandManager; private final Instrumentation instrumentation; - private final JobControllerImpl jobController; + private final JobController jobController; private final long timeoutMillis; private final long reaperInterval; private final Map sessions; @@ -37,7 +37,7 @@ public class SessionManagerImpl implements SessionManager { private ScheduledExecutorService scheduledExecutorService; public SessionManagerImpl(ShellServerOptions options, ArthasBootstrap bootstrap, InternalCommandManager commandManager, - JobControllerImpl jobController) { + JobController jobController) { this.bootstrap = bootstrap; this.commandManager = commandManager; this.jobController = jobController; @@ -187,7 +187,7 @@ public class SessionManagerImpl implements SessionManager { } @Override - public JobControllerImpl getJobController() { + public JobController getJobController() { return jobController; } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java b/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java index fb40ca90c..b3c889171 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/JobListener.java @@ -1,7 +1,5 @@ package com.taobao.arthas.core.shell.system; -import com.taobao.arthas.core.shell.system.impl.JobImpl; - /** * Job listener * @author gongdewei 2020-03-23 diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java index 5256d79bb..31137f3bc 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java @@ -1,5 +1,11 @@ package com.taobao.arthas.core.shell.system.impl; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.GlobalOptions; @@ -12,8 +18,6 @@ import com.taobao.arthas.core.shell.system.Job; import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.term.Term; -import java.util.*; -import java.util.concurrent.TimeUnit; /** * 全局的Job Controller,不应该存在启停的概念,不需要在连接的断开时关闭, @@ -21,7 +25,7 @@ import java.util.concurrent.TimeUnit; * @author gehui 2017年7月31日 上午11:55:41 */ public class GlobalJobControllerImpl extends JobControllerImpl { - private Map jobTimeoutTaskMap = new HashMap(); + private Map jobTimeoutTaskMap = new HashMap(); private static final Logger logger = LoggerFactory.getLogger(GlobalJobControllerImpl.class); @Override @@ -41,7 +45,7 @@ public class GlobalJobControllerImpl extends JobControllerImpl { @Override public boolean removeJob(int id) { - TimerTask jobTimeoutTask = jobTimeoutTaskMap.remove(id); + JobTimeoutTask jobTimeoutTask = jobTimeoutTaskMap.remove(id); if (jobTimeoutTask != null) { jobTimeoutTask.cancel(); } @@ -55,9 +59,10 @@ public class GlobalJobControllerImpl extends JobControllerImpl { /* * 达到超时时间将会停止job */ - TimerTask jobTimeoutTask = new JobTimeoutTask(job); - Date timeoutDate = new Date(System.currentTimeMillis() + (getJobTimeoutInSecond() * 1000)); - ArthasBootstrap.getInstance().getTimer().schedule(jobTimeoutTask, timeoutDate); + JobTimeoutTask jobTimeoutTask = new JobTimeoutTask(job); + long jobTimeoutInSecond = getJobTimeoutInSecond(); + Date timeoutDate = new Date(System.currentTimeMillis() + (jobTimeoutInSecond * 1000)); + ArthasBootstrap.getInstance().getScheduledExecutorService().schedule(jobTimeoutTask, jobTimeoutInSecond, TimeUnit.SECONDS); jobTimeoutTaskMap.put(job.id(), jobTimeoutTask); job.setTimeoutDate(timeoutDate); @@ -98,8 +103,8 @@ public class GlobalJobControllerImpl extends JobControllerImpl { return result; } - private static class JobTimeoutTask extends TimerTask { - Job job; + private static class JobTimeoutTask implements Runnable { + private Job job; public JobTimeoutTask(Job job) { this.job = job; @@ -107,17 +112,23 @@ public class GlobalJobControllerImpl extends JobControllerImpl { @Override public void run() { - if (job != null) { - job.terminate(); + try { + if (job != null) { + Job temp = job; + job = null; + temp.terminate(); + } + } catch (Throwable e) { + try { + logger.error("JobTimeoutTask error, job id: {}, line: {}", job.id(), job.line(), e); + } catch (Throwable t) { + // ignore + } } } - @Override - public boolean cancel() { - // clear job reference from timer - // fix issue: https://github.com/alibaba/arthas/issues/1189 + public void cancel() { job = null; - return super.cancel(); } } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java index fee1b1675..e21b3551d 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobControllerImpl.java @@ -19,11 +19,19 @@ import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.util.Constants; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.TokenUtils; + import io.termd.core.function.Function; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; /** @@ -224,5 +232,4 @@ public class JobControllerImpl implements JobController { public void close() { close(null); } - } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java index f4c60da0a..c960f5d53 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/JobImpl.java @@ -12,10 +12,6 @@ import com.taobao.arthas.core.shell.system.ExecStatus; import com.taobao.arthas.core.shell.system.Job; import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.Process; -import com.taobao.arthas.core.shell.term.Term; -import com.taobao.arthas.core.shell.term.impl.TermImpl; -import com.taobao.arthas.core.util.Constants; -import com.taobao.arthas.core.util.FileUtils; /** * @author Julien Viet diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java index 8cc643ccb..249bcf7ad 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java @@ -397,9 +397,8 @@ public class ProcessImpl implements Process { private final Tty tty; private List args2; private CommandLine commandLine; - private int enhanceLock = -1; private AtomicInteger times = new AtomicInteger(); - private AdviceListener suspendedListener = null; + private AdviceListener listener = null; private ClassFileTransformer transformer; public CommandProcessImpl(Process process, Tty tty) { @@ -547,13 +546,15 @@ public class ProcessImpl implements Process { } @Override - public void register(int enhanceLock, AdviceListener listener, ClassFileTransformer transformer) { - this.enhanceLock = enhanceLock; - + public void register(AdviceListener listener, ClassFileTransformer transformer) { if (listener instanceof ProcessAware) { - ((ProcessAware) listener).setProcess(this.process); + ProcessAware processAware = (ProcessAware) listener; + // listener 有可能是其它 command 创建的 + if(processAware.getProcess() == null) { + processAware.setProcess(this.process); + } } - AdviceWeaver.reg(enhanceLock, listener); + AdviceWeaver.reg(listener); this.transformer = transformer; } @@ -564,22 +565,29 @@ public class ProcessImpl implements Process { ArthasBootstrap.getInstance().getTransformerManager().removeTransformer(transformer); } - AdviceWeaver.unReg(enhanceLock); + if (listener instanceof ProcessAware) { + // listener有可能其它 command 创建的,所以不能unRge + if (this.process.equals(((ProcessAware) listener).getProcess())) { + AdviceWeaver.unReg(listener); + } + } else { + AdviceWeaver.unReg(listener); + } } @Override public void resume() { - if (this.enhanceLock >= 0 && suspendedListener != null) { - AdviceWeaver.resume(enhanceLock, suspendedListener); - suspendedListener = null; - } +// if (suspendedListener != null) { +// AdviceWeaver.resume(suspendedListener); +// suspendedListener = null; +// } } @Override public void suspend() { - if (this.enhanceLock >= 0) { - suspendedListener = AdviceWeaver.suspend(enhanceLock); - } +// if (this.enhanceLock >= 0) { +// suspendedListener = AdviceWeaver.suspend(enhanceLock); +// } } @Override diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java index b614ca580..2b7b1f819 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/api/HttpApiHandler.java @@ -20,9 +20,9 @@ import com.taobao.arthas.core.shell.history.impl.HistoryManagerImpl; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.session.SessionManager; import com.taobao.arthas.core.shell.system.Job; +import com.taobao.arthas.core.shell.system.JobController; import com.taobao.arthas.core.shell.system.JobListener; import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; -import com.taobao.arthas.core.shell.system.impl.JobControllerImpl; import com.taobao.arthas.core.shell.term.SignalHandler; import com.taobao.arthas.core.shell.term.Term; import com.taobao.arthas.core.util.ArthasBanner; @@ -58,7 +58,7 @@ public class HttpApiHandler { private final AtomicInteger requestIdGenerator = new AtomicInteger(0); private static HttpApiHandler instance; private final InternalCommandManager commandManager; - private final JobControllerImpl jobController; + private final JobController jobController; private final HistoryManager historyManager; private int jsonBufferSize = 1024 * 256; diff --git a/core/src/main/java/com/taobao/arthas/core/util/InstrumentationUtils.java b/core/src/main/java/com/taobao/arthas/core/util/InstrumentationUtils.java new file mode 100644 index 000000000..93a3fbeba --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/util/InstrumentationUtils.java @@ -0,0 +1,38 @@ +package com.taobao.arthas.core.util; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.Set; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; + +/** + * + * @author hengyunabc 2020-05-25 + * + */ +public class InstrumentationUtils { + private static final Logger logger = LoggerFactory.getLogger(InstrumentationUtils.class); + + public static void retransformClasses(Instrumentation inst, ClassFileTransformer transformer, + Set> classes) { + try { + inst.addTransformer(transformer, true); + + for (Class clazz : classes) { + try { + inst.retransformClasses(clazz); + } catch (Throwable e) { + String errorMsg = "retransformClasses class error, name: " + clazz.getName(); + if (ClassUtils.isLambdaClass(clazz) && e instanceof VerifyError) { + errorMsg += ", Please ignore lambda class VerifyError: https://github.com/alibaba/arthas/issues/675"; + } + logger.error(errorMsg, e); + } + } + } finally { + inst.removeTransformer(transformer); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java b/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java index a1bfb9e36..4189e708e 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java +++ b/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java @@ -22,6 +22,7 @@ public final class EnhancerAffect extends Affect { private final AtomicInteger cCnt = new AtomicInteger(); private final AtomicInteger mCnt = new AtomicInteger(); private ClassFileTransformer transformer; + private long listenerId; /** * dumpClass的文件存放集合 */ @@ -92,6 +93,14 @@ public final class EnhancerAffect extends Affect { this.transformer = transformer; } + public long getListenerId() { + return listenerId; + } + + public void setListenerId(long listenerId) { + this.listenerId = listenerId; + } + @Override public String toString() { final StringBuilder infoSB = new StringBuilder(); @@ -108,10 +117,11 @@ public final class EnhancerAffect extends Affect { infoSB.append("[Affect method: ").append(method).append("]\n"); } } - infoSB.append(format("Affect(class count:%d , method count:%d) cost in %s ms.", + infoSB.append(format("Affect(class count: %d , method count: %d) cost in %s ms, listenerId: %d", cCnt(), mCnt(), - cost())); + cost(), + listenerId)); return infoSB.toString(); } diff --git a/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java b/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java index 1468356c7..66384e4f3 100644 --- a/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java +++ b/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java @@ -41,14 +41,10 @@ public class EnhancerTest { AdviceListener listener = Mockito.mock(AdviceListener.class); - EnhancerAffect affect = new EnhancerAffect(); + EqualsMatcher methodNameMatcher = new EqualsMatcher("print"); + EqualsMatcher classNameMatcher = new EqualsMatcher(MathGame.class.getName()); - Set> matchingClasses = new HashSet>(); - matchingClasses.add(MathGame.class); - - EqualsMatcher matcher = new EqualsMatcher("print"); - - Enhancer enhancer = new Enhancer(listener, true, false, matchingClasses, matcher, affect); + Enhancer enhancer = new Enhancer(listener, true, false, classNameMatcher, methodNameMatcher); ClassLoader inClassLoader = MathGame.class.getClassLoader(); String className = MathGame.class.getName(); diff --git a/pom.xml b/pom.xml index db7385ad7..283facd7e 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ org.benf cfr - 0.149 + 0.150 com.alibaba.middleware diff --git a/site/src/site/sphinx/_static/dashboard.png b/site/src/site/sphinx/_static/dashboard.png index 1f40b7212..3ed6ec760 100644 Binary files a/site/src/site/sphinx/_static/dashboard.png and b/site/src/site/sphinx/_static/dashboard.png differ diff --git a/site/src/site/sphinx/en/advanced-use.md b/site/src/site/sphinx/en/advanced-use.md index 302d3c3a2..39ee7589a 100644 --- a/site/src/site/sphinx/en/advanced-use.md +++ b/site/src/site/sphinx/en/advanced-use.md @@ -45,7 +45,7 @@ Advanced Usage ## monitor/watch/trace - related -> **Attention**: commands here are taking advantage of byte-code-injection, which means we are injecting some [aspects](https://en.wikipedia.org/wiki/Aspect-oriented_programming) into the current classes for monitoring and statistics purpose. Therefore when use it for online troubleshooting in your production environment, you'd better **explicitly specify** classes/methods/criteria, and remember to remove the injected code by `stop` or `reset`. +> **Attention**: commands here are taking advantage of byte-code-injection, which means we are injecting some [aspects](https://en.wikipedia.org/wiki/Aspect-oriented_programming) into the current classes for monitoring and statistics purpose. Therefore, when using it for online troubleshooting in your production environment, you'd better **explicitly specify** classes/methods/criteria, and remember to remove the injected code by `stop` or `reset`. * [monitor](monitor.md) - monitor method execution statistics * [watch](watch.md) - display the input/output parameter, return object, and thrown exception of specified method invocation diff --git a/site/src/site/sphinx/en/async.md b/site/src/site/sphinx/en/async.md index c95850b08..ec32bf510 100644 --- a/site/src/site/sphinx/en/async.md +++ b/site/src/site/sphinx/en/async.md @@ -40,8 +40,8 @@ You can see that there is currently a background job executing: When the job is executing in the foreground, for example, directly executing the command `trace Test t`, or executing the background job command `trace Test t &`, then putting the job back to the foreground via `fg` command, the console cannot continue to execute other command, but can receive and process the following keyboard events: -* ‘ctrl + z’: Suspend the job, the job status will change to `Stopped`, and the job can be restarted by `bg ` or `fg ` -* ‘ctrl + c’: Stop the job +* ‘ctrl + z’: Suspends the job, the job status will change to `Stopped`, and the job can be restarted by `bg ` or `fg ` +* ‘ctrl + c’: Stops the job * ‘ctrl + d’: According to linux semantics this should lead to exit the terminal, right now Arthas has not implemented this yet, therefore simply ignore this keystroke. ## 4. fg/bg, switch the job from the foreground to the background, and vise verse @@ -60,7 +60,7 @@ $ trace Test t >> test.out & The trace command will be running in the background and the output will be redirect to `~/logs/arthas-cache/test.out`. You can continue to execute other commands in the console, at the same time, you can also examine the execution result from the output file. -When connect to a remote Arthas server, you may not be able to view the output file on the remote machine. In this case, Arthas also supports automatically redirecting the output to the local cache file. Examples are as follows: +When connected to a remote Arthas server, you may not be able to view the output file on the remote machine. In this case, Arthas also supports automatically redirecting the output to the local cache file. Examples are as follows: ```bash $ trace Test t >> & diff --git a/site/src/site/sphinx/en/batch-support.md b/site/src/site/sphinx/en/batch-support.md index c2bcc4e92..0afca2df5 100644 --- a/site/src/site/sphinx/en/batch-support.md +++ b/site/src/site/sphinx/en/batch-support.md @@ -38,7 +38,7 @@ Use `-c` also can specify the commands, like this: ./as.sh -c 'sysprop; thread' 56328 > test.out ``` -#### Step 3: Check the outputs +#### Step 3: Check the output ```bash cat test.out diff --git a/site/src/site/sphinx/en/classloader.md b/site/src/site/sphinx/en/classloader.md index 5cbe19a29..e67ffeae6 100644 --- a/site/src/site/sphinx/en/classloader.md +++ b/site/src/site/sphinx/en/classloader.md @@ -3,7 +3,7 @@ classloader View hierarchy, urls and classes-loading info for the class-loaders. -`classloader` can search and print out the URLs for a specified resource from one particular classloader. It is quite handy when analyze `ResourceNotFoundException`. +`classloader` can search and print out the URLs for a specified resource from one particular classloader. It is quite handy when analyzing `ResourceNotFoundException`. ### Options diff --git a/site/src/site/sphinx/en/commands.md b/site/src/site/sphinx/en/commands.md index 3a3b1060a..f60729ee3 100644 --- a/site/src/site/sphinx/en/commands.md +++ b/site/src/site/sphinx/en/commands.md @@ -42,15 +42,15 @@ All Commands ### Basic Arthas Commands -* help - examine help information -* cls - clear out the screen -* session - examine the current session -* [reset](reset.md) - reset enhanced classes. All enhanced classes will be reset to their original states. When Arthas server closes, all these enhanced classes will be reset too -* version - print out Arthas's version +* help - examines help information +* cls - clears out the screen +* session - examines the current session +* [reset](reset.md) - resets enhanced classes. All enhanced classes will be reset to their original states. When Arthas server closes, all these enhanced classes will be reset too +* version - prints out Arthas's version * history - view command history * quit - exit the current Arthas client without affecting other clients -* stop - terminate the Arthas server, all the Arthas clients connecting to this server will be disconnected -* [keymap](keymap.md) - list all Arthas keyboard shortcuts and shortcut customizations. +* stop - terminates the Arthas server, all the Arthas clients connecting to this server will be disconnected +* [keymap](keymap.md) - lists all Arthas keyboard shortcuts and shortcut customizations. diff --git a/site/src/site/sphinx/en/download.md b/site/src/site/sphinx/en/download.md index 9ee6ae54a..faa46b9b0 100644 --- a/site/src/site/sphinx/en/download.md +++ b/site/src/site/sphinx/en/download.md @@ -3,7 +3,7 @@ Download ## Download full package -### Dwonload from maven central +### Download from maven central Latest Version, Click To Download: [![](https://img.shields.io/maven-central/v/com.taobao.arthas/arthas-packaging.svg?style=flat-square "Arthas")](http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.taobao.arthas&a=arthas-packaging&e=zip&c=bin&v=LATEST) diff --git a/site/src/site/sphinx/en/getstatic.md b/site/src/site/sphinx/en/getstatic.md index be3a430f4..fcf5775e6 100644 --- a/site/src/site/sphinx/en/getstatic.md +++ b/site/src/site/sphinx/en/getstatic.md @@ -1,7 +1,7 @@ getstatic ========= -* It is recommended to use the [OGNL] (ognl.md) command, which will be more flexibility. +* It is recommended to use the [OGNL] (ognl.md) command, which will be more flexible. Check the static fields of classes conveniently, the usage is `getstatic class_name field_name`. diff --git a/site/src/site/sphinx/en/groovy.md b/site/src/site/sphinx/en/groovy.md index 991adff8c..eb1dc16bb 100644 --- a/site/src/site/sphinx/en/groovy.md +++ b/site/src/site/sphinx/en/groovy.md @@ -6,7 +6,7 @@ groovy ### Limitations 1. Prohibit from alternating the original logic. Like `watch` command, The major purpose of scripting is monitoring and observing. -1. Only allow to monitor at the stages of before/success/exception/finish on one method. +2. Only allow to monitor at the stages of before/success/exception/finish on one method. ### Parameters diff --git a/site/src/site/sphinx/en/index.md b/site/src/site/sphinx/en/index.md index 405c805cb..e9c5ea4ae 100644 --- a/site/src/site/sphinx/en/index.md +++ b/site/src/site/sphinx/en/index.md @@ -34,7 +34,7 @@ Arthas is built to solve these issues. A developer can troubleshoot production i * Supports Linux/Mac/Windows -**If you are using Arthas, please let us know. Your use is very important to us: [View](https://github.com/alibaba/arthas/issues/111)** +**If you are using Arthas, please let us know. Your feedback is very important to us: [View](https://github.com/alibaba/arthas/issues/111)** Contents -------- diff --git a/site/src/site/sphinx/en/install-detail.md b/site/src/site/sphinx/en/install-detail.md index 5d34b6b85..10fb6aa92 100644 --- a/site/src/site/sphinx/en/install-detail.md +++ b/site/src/site/sphinx/en/install-detail.md @@ -4,7 +4,7 @@ Install Arthas ## Quick installation -### Use `arthas-boot`(Recommend) +### Use `arthas-boot`(Recommended) Download`arthas-boot.jar`,Start with `java` command: diff --git a/site/src/site/sphinx/en/keymap.md b/site/src/site/sphinx/en/keymap.md index e98456f1c..f02b2462b 100644 --- a/site/src/site/sphinx/en/keymap.md +++ b/site/src/site/sphinx/en/keymap.md @@ -62,8 +62,8 @@ then replace `"\C-h": backward-delete-char` with `"\C-h": backward-char`, then r #### Shortcuts for jobs -* `ctrl + c`: Terminate current command -* `ctrl + z`: Suspend the current command, you can restore this command with `bg`/`fg`, or `kill` it. +* `ctrl + c`: Terminates current command +* `ctrl + z`: Suspends the current command, you can restore this command with `bg`/`fg`, or `kill` it. * `ctrl + a`: Go to the beginning the line * `ctrl + e`: Go to the end of the line diff --git a/site/src/site/sphinx/en/quick-start.md b/site/src/site/sphinx/en/quick-start.md index fa68cbe03..7d47b7162 100644 --- a/site/src/site/sphinx/en/quick-start.md +++ b/site/src/site/sphinx/en/quick-start.md @@ -8,7 +8,7 @@ curl -O https://alibaba.github.io/arthas/arthas-demo.jar java -jar arthas-demo.jar ``` -`arthas-demo` is a simple program that generates a random number every second, then find all prime factors of the number. +`arthas-demo` is a simple program that generates a random number every second, then it finds all prime factors of that number. The source code of `arthas-demo`: [View](https://github.com/alibaba/arthas/blob/master/demo/src/main/java/demo/MathGame.java) diff --git a/static/woqu.png b/static/woqu.png new file mode 100644 index 000000000..0d8289f8f Binary files /dev/null and b/static/woqu.png differ diff --git a/tunnel-client/src/main/java/com/alibaba/arthas/tunnel/client/ForwardClientSocketClientHandler.java b/tunnel-client/src/main/java/com/alibaba/arthas/tunnel/client/ForwardClientSocketClientHandler.java index 9b20c81a9..ffd8300af 100644 --- a/tunnel-client/src/main/java/com/alibaba/arthas/tunnel/client/ForwardClientSocketClientHandler.java +++ b/tunnel-client/src/main/java/com/alibaba/arthas/tunnel/client/ForwardClientSocketClientHandler.java @@ -1,4 +1,3 @@ - package com.alibaba.arthas.tunnel.client; import java.net.URI; @@ -30,18 +29,14 @@ import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.util.concurrent.GenericFutureListener; /** - * * @author hengyunabc 2019-08-28 - * */ public class ForwardClientSocketClientHandler extends SimpleChannelInboundHandler { - private final static Logger logger = LoggerFactory.getLogger(ForwardClientSocketClientHandler.class); - - private ChannelPromise handshakeFuture; - private Channel localChannel; + private static final Logger logger = LoggerFactory.getLogger(ForwardClientSocketClientHandler.class); - private URI localServerURI; + private ChannelPromise handshakeFuture; + private final URI localServerURI; public ForwardClientSocketClientHandler(URI localServerURI) { this.localServerURI = localServerURI; @@ -49,7 +44,6 @@ public class ForwardClientSocketClientHandler extends SimpleChannelInboundHandle @Override public void channelActive(ChannelHandlerContext ctx) { - } @Override @@ -58,26 +52,30 @@ public class ForwardClientSocketClientHandler extends SimpleChannelInboundHandle } @Override - public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) throws Exception { - + public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) { if (evt.equals(ClientHandshakeStateEvent.HANDSHAKE_COMPLETE)) { - - EventLoopGroup group = new NioEventLoopGroup(); - try { + connectLocalServer(ctx); + } catch (Throwable e) { + logger.error("ForwardClientSocketClientHandler connect local arthas server error", e); + } + } else { + ctx.fireUserEventTriggered(evt); + } + } - logger.info("ForwardClientSocketClientHandler star connect local arthas server"); - - WebSocketClientHandshaker newHandshaker = WebSocketClientHandshakerFactory.newHandshaker(localServerURI, - WebSocketVersion.V13, null, true, new DefaultHttpHeaders()); - - final WebSocketClientProtocolHandler websocketClientHandler = new WebSocketClientProtocolHandler( - newHandshaker); - - final LocalFrameHandler localFrameHandler = new LocalFrameHandler(); - - Bootstrap b = new Bootstrap(); - b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { + private void connectLocalServer(final ChannelHandlerContext ctx) throws InterruptedException { + EventLoopGroup group = new NioEventLoopGroup(); + logger.info("ForwardClientSocketClientHandler star connect local arthas server"); + WebSocketClientHandshaker newHandshaker = WebSocketClientHandshakerFactory.newHandshaker(localServerURI, + WebSocketVersion.V13, null, true, new DefaultHttpHeaders()); + final WebSocketClientProtocolHandler websocketClientHandler = new WebSocketClientProtocolHandler( + newHandshaker); + final LocalFrameHandler localFrameHandler = new LocalFrameHandler(); + + Bootstrap b = new Bootstrap(); + b.group(group).channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); @@ -86,42 +84,30 @@ public class ForwardClientSocketClientHandler extends SimpleChannelInboundHandle } }); - localChannel = b.connect(localServerURI.getHost(), localServerURI.getPort()).sync().channel(); - - localFrameHandler.handshakeFuture().addListener(new GenericFutureListener() { + Channel localChannel = b.connect(localServerURI.getHost(), localServerURI.getPort()).sync().channel(); + this.handshakeFuture = localFrameHandler.handshakeFuture(); + handshakeFuture.addListener(new GenericFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { ChannelPipeline pipeline = future.channel().pipeline(); pipeline.remove(localFrameHandler); pipeline.addLast(new RelayHandler(ctx.channel())); - } }); - localFrameHandler.handshakeFuture().sync(); - - ctx.pipeline().remove(ForwardClientSocketClientHandler.this); - - ctx.pipeline().addLast(new RelayHandler(localChannel)); - - logger.info("ForwardClientSocketClientHandler connect local arthas server success"); - } catch (Throwable e) { - logger.error("ForwardClientSocketClientHandler connect local arthas server error", e); - } - - } else { - ctx.fireUserEventTriggered(evt); - } + handshakeFuture.sync(); + ctx.pipeline().remove(ForwardClientSocketClientHandler.this); + ctx.pipeline().addLast(new RelayHandler(localChannel)); + logger.info("ForwardClientSocketClientHandler connect local arthas server success"); } @Override - protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception { - + protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) { } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - cause.printStackTrace(); + logger.error("ForwardClientSocketClient channel: {}" , ctx.channel(), cause); if (!handshakeFuture.isDone()) { handshakeFuture.setFailure(cause); } diff --git a/tunnel-server/README.md b/tunnel-server/README.md index 76016d443..b0cacbbb1 100644 --- a/tunnel-server/README.md +++ b/tunnel-server/README.md @@ -1,5 +1,3 @@ - - ## How it works Tunnel server/client use websocket protocol. @@ -8,24 +6,36 @@ For example: 1. Arthas tunnel server listen at `192.168.1.10:7777` -1. Arthas tunnel client register to the tunnel server +2. Arthas tunnel client register to the tunnel server + + tunnel client connect to tunnel server with URL: `ws://192.168.1.10:7777/ws?method=agentRegister` - tunnel client connect to tunnel server with URL: `ws://192.168.1.10:7777/ws?method=agentRegister` + tunnel server response a text frame message: `response:/?method=agentRegister&id=bvDOe8XbTM2pQWjF4cfw` - tunnel server response a text frame message: `response:/?method=agentRegister&id=bvDOe8XbTM2pQWjF4cfw` + This connection is `control connection`. - This connection is `control connection`. +3. The browser try connect to remote arthas agent -1. The browser try connect to remote arthas agent, start connect to tunnel server with URL: `'ws://192.168.1.10:7777/ws?method=connectArthas&id=bvDOe8XbTM2pQWjF4cfw` + start connect to tunnel server with URL: `'ws://192.168.1.10:7777/ws?method=connectArthas&id=bvDOe8XbTM2pQWjF4cfw` -1. Arthas server find the `control connection` with the id `bvDOe8XbTM2pQWjF4cfw`, then send a text frame to arthas client: `response:/?method=startTunnel&id=bvDOe8XbTM2pQWjF4cfw&clientConnectionId=AMku9EFz2gxeL2gedGOC` +4. Arthas server find the `control connection` with the id `bvDOe8XbTM2pQWjF4cfw` + + then send a text frame to arthas client: `response:/?method=startTunnel&id=bvDOe8XbTM2pQWjF4cfw&clientConnectionId=AMku9EFz2gxeL2gedGOC` -1. Arhtas tunnel client open a new connection to tunnel server, URL: `ws://127.0.0.1:7777/ws/?method=openTunnel&clientConnectionId=AMku9EFz2gxeL2gedGOC&id=bvDOe8XbTM2pQWjF4cfw`. This connection is `tunnel connection`. +5. Arthas tunnel client open a new connection to tunnel server -1. Arhtas tunnel client start connect to local arthas agent, URL: `ws://127.0.0.1:3658/ws`. This connection is `local connection`. + URL: `ws://127.0.0.1:7777/ws/?method=openTunnel&clientConnectionId=AMku9EFz2gxeL2gedGOC&id=bvDOe8XbTM2pQWjF4cfw` + + This connection is `tunnel connection` -1. Forward websocket frame between `tunnel connection` and `local connection`. +6. Arthas tunnel client start connect to local arthas agent, URL: `ws://127.0.0.1:3658/ws` + This connection is `local connection` + +7. Forward websocket frame between `tunnel connection` and `local connection`. + +``` ++---------+ +----------------------+ +----------------------+ +--------------+ +| browser <-----> arthas tunnel server | <-----> arthas tunnel client <--- -> arthas agent | +|---------+ +----------------------+ +----------------------+ +--------------+ ``` -browser <-> arthas tunnel server <-> arthas tunnel client <-> arthas agent -``` \ No newline at end of file