Merge remote-tracking branch 'origin/http-api-base' into http-api

http-api
gongdewei 5 years ago
commit 9a3d59b211

@ -62,7 +62,7 @@ Tip: you can use `--versions` to list all available versions.
## issue
## Issue
欢迎在issue里对arthas做反馈分享使用技巧排查问题的经历。

@ -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.
<a href="https://github.com/alibaba/arthas/graphs/contributors"><img src="https://opencollective.com/arthas/contributors.svg?width=890&button=false" /></a>

@ -499,6 +499,7 @@ OK
![vipkid](static/vipkid.png)
![宇中科技](static/yuzhong.png)
![蘑菇财富](static/mogu.jpg)
![喔趣科技](static/woqu.png)
### 洐生项目

@ -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 <value> Command to execute, multiple commands separated
by ;
-f,--batch-file <value> 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

@ -49,6 +49,7 @@ import com.taobao.middleware.cli.annotations.Summary;
+ " java -jar arthas-boot.jar -f batch.as <pid>\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,6 +115,8 @@ public class Bootstrap {
private String statUrl;
private String select;
static {
ARTHAS_LIB_DIR = new File(
System.getProperty("user.home") + File.separator + ".arthas" + File.separator + "lib");
@ -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;
}
}

@ -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<Long, String> 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<Long, String> entry : processMap.entrySet()) {
if (entry.getValue().contains(select)) {
matchedSelectCount++;
matchedPid = entry.getKey();
}
}
if (matchedSelectCount == 1) {
return matchedPid;
}
}
if (processMap.size() == 1) {
Entry<Long, String> 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;

@ -0,0 +1,222 @@
## ByteKit
## 目标
1. 之前的Arthas里的字节码增强是通过asm来处理的代码逻辑不好修改理解困难
1. 基于ASM提供更高层的字节码处理能力面向诊断/APM领域不是通用的字节码库
1. ByteKit期望能提供一套简洁的API让开发人员可以比较轻松的完成字节码增强
## 对比
| 功能 | 函数Enter/Exit注入点 | 绑定数据 | inline | 防止重复增强 | 避免装箱/拆箱开销 |origin调用替换 | `@ExceptionHandler` |
| ---- | ---- |---- | :----: |:----: | :----: |:----: | :----: |
| ByteKit | `@AtEnter` <br> `@AtExit` <br>`@AtExceptionExit` <br> `@AtFieldAccess` <br> `@AtInvoke`<br>`@AtInvokeException`<br>`@AtLine`<br>`@AtSyncEnter`<br>`@AtSyncExit`<br>`@AtThrow`| this/args/return/throw<br>field<br>locals<br>子调用入参/返回值/子调用异常<br>line number|✓|✓|✓|✓|✓|
| ByteBuddy | `OnMethodEnter`<br>`@OnMethodExit`<br> `@OnMethodExit#onThrowable()`| this/args/return/throw<br>field<br>locals|✓|✗|✓|✓|✓|
| 传统AOP | `Enter`<br>`Exit`<br>`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<ByteKitDemo.Sample> class_ = ByteKitDemo.Sample.class;
ByteKitDemo.Sample sample = this;
System.out.println("atEnter, args[0]: " + arrobject[0]);
}
catch (RuntimeException runtimeException) {
Class<ByteKitDemo.Sample> 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;
}
}
}
```

@ -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);

@ -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<InterceptorProcessor> 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();
}
}

@ -6,6 +6,8 @@ package com.taobao.arthas.core.advisor;
*/
public interface AdviceListener {
long id();
/**
* <br/>
*

@ -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() {

@ -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,15 +51,15 @@ 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() {
try {
if (adviceListenerMap != null) {
for (Entry<ClassLoader, ClassLoaderAdviceListenerManager> entry : adviceListenerMap.entrySet()) {
ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue();
@ -68,7 +70,11 @@ public class AdviceListenerManager {
for (AdviceListener listener : listeners) {
if (listener instanceof ProcessAware) {
ProcessAware processAware = (ProcessAware) listener;
ExecStatus status = processAware.getProcess().status();
Process process = processAware.getProcess();
if (process == null) {
continue;
}
ExecStatus status = process.status();
if (!status.equals(ExecStatus.TERMINATED)) {
newResult.add(listener);
}
@ -83,9 +89,15 @@ public class AdviceListenerManager {
}
}
}
} catch (Throwable e) {
try {
logger.error("clean AdviceListener error", e);
} catch (Throwable t) {
// ignore
}
}, 3000, 3000);
}
}
}, 3, 3, TimeUnit.SECONDS);
}
static private ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager> adviceListenerMap = new ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager>();

@ -19,8 +19,8 @@ public class AdviceWeaver {
private static final Logger logger = LoggerFactory.getLogger(AdviceWeaver.class);
// 通知监听器集合
private final static Map<Integer/*ADVICE_ID*/, AdviceListener> advices
= new ConcurrentHashMap<Integer, AdviceListener>();
private final static Map<Long/*ADVICE_ID*/, AdviceListener> advices
= new ConcurrentHashMap<Long, AdviceListener>();
/**
*
@ -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) {
public static void unReg(AdviceListener listener) {
if (null != listener) {
// 注销监听器
final AdviceListener listener = advices.remove(adviceId);
advices.remove(listener.id());
// 触发监听器销毁
if (null != listener) {
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);
}

@ -66,9 +66,10 @@ public class Enhancer implements ClassFileTransformer {
private final AdviceListener listener;
private final boolean isTracing;
private final boolean skipJDKTrace;
private final Set<Class<?>> matchingClasses;
private final Matcher classNameMatcher;
private final Matcher methodNameMatcher;
private final EnhancerAffect affect;
private Set<Class<?>> matchingClasses = null;
// 被增强的类的缓存
private final static Map<Class<?>/* Class */, Object> classBytesCache = new WeakHashMap<Class<?>, Object>();
@ -86,14 +87,15 @@ public class Enhancer implements ClassFileTransformer {
* @param methodNameMatcher
* @param affect
*/
Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Set<Class<?>> 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<Class<?>> 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<Class<?>> classes)
private static void enhance(Instrumentation inst, ClassFileTransformer transformer, Set<Class<?>> classes)
throws UnmodifiableClassException {
try {
inst.addTransformer(transformer, true);

@ -66,6 +66,8 @@ public class TransformerManager {
}
public void destroy() {
watchTransformers.clear();
traceTransformers.clear();
instrumentation.removeTransformer(classFileTransformer);
}

@ -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;

@ -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();
}
}

@ -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<Class<?>> 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<Class<?>> matchedClasses, Set<Class<?>> 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<Class<?>, File> classFiles = transformer.getDumpResult();
File classFile = classFiles.get(c);

@ -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,6 +103,7 @@ public class SearchMethodCommand extends AnnotatedCommand {
return;
}
for (Class<?> clazz : matchedClasses) {
try {
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (!methodNameMatcher.matching("<init>")) {
continue;
@ -118,6 +122,13 @@ public class SearchMethodCommand extends AnnotatedCommand {
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;
}
}
process.appendResult(new RowAffectModel(affect));

@ -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");
}

@ -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 <duration> 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 <duration> 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<String> events() {
List<String> result = new ArrayList<String>();

@ -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<Integer, TimeFragment> timeFragmentMap = new LinkedHashMap<Integer, TimeFragment>();
// 时间碎片序列生成器
private static final AtomicInteger sequence = new AtomicInteger(1000);

@ -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);

@ -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;
/**

@ -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 {
/**
* <pre>
* > 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
* </pre>
*/
@ -423,6 +427,10 @@ public class ArthasBootstrap {
return this.timer;
}
public ScheduledExecutorService getScheduledExecutorService() {
return this.executorService;
}
public Instrumentation getInstrumentation() {
return this.instrumentation;
}

@ -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

@ -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();
}

@ -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<String, Session> 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;
}
}

@ -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

@ -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 2017731 11:55:41
*/
public class GlobalJobControllerImpl extends JobControllerImpl {
private Map<Integer, TimerTask> jobTimeoutTaskMap = new HashMap<Integer, TimerTask>();
private Map<Integer, JobTimeoutTask> jobTimeoutTaskMap = new HashMap<Integer, JobTimeoutTask>();
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() {
try {
if (job != null) {
job.terminate();
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();
}
}
}

@ -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);
}
}

@ -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 <a href="mailto:julien@julienviet.com">Julien Viet</a>

@ -397,9 +397,8 @@ public class ProcessImpl implements Process {
private final Tty tty;
private List<String> 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

@ -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;

@ -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<Class<?>> 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);
}
}
}

@ -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();
}

@ -41,14 +41,10 @@ public class EnhancerTest {
AdviceListener listener = Mockito.mock(AdviceListener.class);
EnhancerAffect affect = new EnhancerAffect();
EqualsMatcher<String> methodNameMatcher = new EqualsMatcher<String>("print");
EqualsMatcher<String> classNameMatcher = new EqualsMatcher<String>(MathGame.class.getName());
Set<Class<?>> matchingClasses = new HashSet<Class<?>>();
matchingClasses.add(MathGame.class);
EqualsMatcher<String> matcher = new EqualsMatcher<String>("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();

@ -103,7 +103,7 @@
<dependency>
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
<version>0.149</version>
<version>0.150</version>
</dependency>
<dependency>
<groupId>com.alibaba.middleware</groupId>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 976 KiB

@ -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

@ -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 <job-id>` or `fg <job-id>`
* 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 <job-id>` or `fg <job-id>`
* 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 >> &

@ -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

@ -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

@ -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.

@ -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)

@ -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`.

@ -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

@ -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
--------

@ -4,7 +4,7 @@ Install Arthas
## Quick installation
### Use `arthas-boot`(Recommend)
### Use `arthas-boot`(Recommended)
Download`arthas-boot.jar`Start with `java` command:

@ -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

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -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<WebSocketFrame> {
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);
}
}
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<SocketChannel>() {
b.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@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<ChannelFuture>() {
Channel localChannel = b.connect(localServerURI.getHost(), localServerURI.getPort()).sync().channel();
this.handshakeFuture = localFrameHandler.handshakeFuture();
handshakeFuture.addListener(new GenericFutureListener<ChannelFuture>() {
@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();
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);
}
}
@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);
}

@ -1,5 +1,3 @@
## How it works
Tunnel server/client use websocket protocol.
@ -8,7 +6,7 @@ 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`
@ -16,16 +14,28 @@ For example:
This connection is `control connection`.
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`
3. 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`
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`
5. Arthas tunnel client open a new connection to tunnel server
URL: `ws://127.0.0.1:7777/ws/?method=openTunnel&clientConnectionId=AMku9EFz2gxeL2gedGOC&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`
This connection is `tunnel connection`
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`.
6. Arthas tunnel client start connect to local arthas agent, URL: `ws://127.0.0.1:3658/ws`
1. Arhtas tunnel client start connect to local arthas agent, URL: `ws://127.0.0.1:3658/ws`. This connection is `local connection`.
This connection is `local connection`
1. Forward websocket frame between `tunnel connection` and `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 |
|---------+ +----------------------+ +----------------------+ +--------------+
```
Loading…
Cancel
Save