mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
init
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package com.taobao.arthas.core;
|
||||
|
||||
import com.taobao.arthas.core.config.Configure;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
import com.taobao.middleware.cli.CLIs;
|
||||
import com.taobao.middleware.cli.CommandLine;
|
||||
import com.taobao.middleware.cli.Option;
|
||||
import com.taobao.middleware.cli.TypedOption;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Arthas启动器
|
||||
*/
|
||||
public class Arthas {
|
||||
|
||||
private static final String DEFAULT_TELNET_PORT = "3658";
|
||||
private static final String DEFAULT_HTTP_PORT = "8563";
|
||||
|
||||
private Arthas(String[] args) throws Exception {
|
||||
attachAgent(parse(args));
|
||||
}
|
||||
|
||||
private Configure parse(String[] args) {
|
||||
Option pid = new TypedOption<Integer>().setType(Integer.class).setShortName("pid").setRequired(true);
|
||||
Option core = new TypedOption<String>().setType(String.class).setShortName("core").setRequired(true);
|
||||
Option agent = new TypedOption<String>().setType(String.class).setShortName("agent").setRequired(true);
|
||||
Option target = new TypedOption<String>().setType(String.class).setShortName("target-ip");
|
||||
Option telnetPort = new TypedOption<Integer>().setType(Integer.class)
|
||||
.setShortName("telnet-port").setDefaultValue(DEFAULT_TELNET_PORT);
|
||||
Option httpPort = new TypedOption<Integer>().setType(Integer.class)
|
||||
.setShortName("http-port").setDefaultValue(DEFAULT_HTTP_PORT);
|
||||
CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target)
|
||||
.addOption(telnetPort).addOption(httpPort);
|
||||
CommandLine commandLine = cli.parse(Arrays.asList(args));
|
||||
|
||||
Configure configure = new Configure();
|
||||
configure.setJavaPid((Integer) commandLine.getOptionValue("pid"));
|
||||
configure.setArthasAgent((String) commandLine.getOptionValue("agent"));
|
||||
configure.setArthasCore((String) commandLine.getOptionValue("core"));
|
||||
if (commandLine.getOptionValue("target-ip") == null) {
|
||||
throw new IllegalStateException("as.sh is too old to support web console, " +
|
||||
"please run the following command to upgrade to latest version:" +
|
||||
"\ncurl -sLk http://arthas.io/arthas/install.sh | sh");
|
||||
}
|
||||
configure.setIp((String) commandLine.getOptionValue("target-ip"));
|
||||
configure.setTelnetPort((Integer) commandLine.getOptionValue("telnet-port"));
|
||||
configure.setHttpPort((Integer) commandLine.getOptionValue("http-port"));
|
||||
return configure;
|
||||
}
|
||||
|
||||
private void attachAgent(Configure configure) throws Exception {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
Class<?> vmdClass = loader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
|
||||
Class<?> vmClass = loader.loadClass("com.sun.tools.attach.VirtualMachine");
|
||||
|
||||
Object attachVmdObj = null;
|
||||
for (Object obj : (List<?>) vmClass.getMethod("list", (Class<?>[]) null).invoke(null, (Object[]) null)) {
|
||||
Object pid = vmdClass.getMethod("id", (Class<?>[]) null).invoke(obj, (Object[]) null);
|
||||
if (pid.equals(Integer.toString(configure.getJavaPid()))) {
|
||||
attachVmdObj = obj;
|
||||
}
|
||||
}
|
||||
|
||||
Object vmObj = null;
|
||||
try {
|
||||
if (null == attachVmdObj) { // 使用 attach(String pid) 这种方式
|
||||
vmObj = vmClass.getMethod("attach", String.class).invoke(null, "" + configure.getJavaPid());
|
||||
} else {
|
||||
vmObj = vmClass.getMethod("attach", vmdClass).invoke(null, attachVmdObj);
|
||||
}
|
||||
Method loadAgent = vmClass.getMethod("loadAgent", String.class, String.class);
|
||||
loadAgent.invoke(vmObj, configure.getArthasAgent(), configure.getArthasCore() + ";" + configure.toString());
|
||||
} finally {
|
||||
if (null != vmObj) {
|
||||
vmClass.getMethod("detach", (Class<?>[]) null).invoke(vmObj, (Object[]) null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
new Arthas(args);
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Start arthas failed, exception stack trace: ");
|
||||
t.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.taobao.arthas.core;
|
||||
|
||||
/**
|
||||
* 全局开关
|
||||
* Created by vlinux on 15/6/4.
|
||||
*/
|
||||
public class GlobalOptions {
|
||||
|
||||
/**
|
||||
* 是否支持系统类<br/>
|
||||
* 这个开关打开之后将能代理到来自JVM的部分类,由于有非常强的安全风险可能会引起系统崩溃<br/>
|
||||
* 所以这个开关默认是关闭的,除非你非常了解你要做什么,否则请不要打开
|
||||
*/
|
||||
@Option(level = 0,
|
||||
name = "unsafe",
|
||||
summary = "Option to support system-level class",
|
||||
description =
|
||||
"This option enables to proxy functionality of JVM classes."
|
||||
+ " Due to serious security risk a JVM crash is possibly be introduced."
|
||||
+ " Do not activate it unless you are able to manage."
|
||||
)
|
||||
public static volatile boolean isUnsafe = false;
|
||||
|
||||
/**
|
||||
* 是否支持dump被增强的类<br/>
|
||||
* 这个开关打开这后,每次增强类的时候都将会将增强的类dump到文件中,以便于进行反编译分析
|
||||
*/
|
||||
@Option(level = 1,
|
||||
name = "dump",
|
||||
summary = "Option to dump the enhanced classes",
|
||||
description =
|
||||
"This option enables the enhanced classes to be dumped to external file " +
|
||||
"for further de-compilation and analysis."
|
||||
)
|
||||
public static volatile boolean isDump = false;
|
||||
|
||||
/**
|
||||
* 是否支持批量增强<br/>
|
||||
* 这个开关打开后,每次均是批量增强类
|
||||
*/
|
||||
@Option(level = 1,
|
||||
name = "batch-re-transform",
|
||||
summary = "Option to support batch reTransform Class",
|
||||
description = "This options enables to reTransform classes with batch mode."
|
||||
)
|
||||
public static volatile boolean isBatchReTransform = true;
|
||||
|
||||
/**
|
||||
* 是否支持json格式化输出<br/>
|
||||
* 这个开关打开后,使用json格式输出目标对象,配合-x参数使用
|
||||
*/
|
||||
@Option(level = 2,
|
||||
name = "json-format",
|
||||
summary = "Option to support JSON format of object output",
|
||||
description = "This option enables to format object output with JSON when -x option selected."
|
||||
)
|
||||
public static volatile boolean isUsingJson = false;
|
||||
|
||||
/**
|
||||
* 是否关闭子类
|
||||
*/
|
||||
@Option(
|
||||
level = 1,
|
||||
name = "disable-sub-class",
|
||||
summary = "Option to control include sub class when class matching",
|
||||
description = "This option disable to include sub class when matching class."
|
||||
)
|
||||
public static volatile boolean isDisableSubClass = false;
|
||||
|
||||
/**
|
||||
* 是否在asm中输出
|
||||
*/
|
||||
@Option(level = 1,
|
||||
name = "debug-for-asm",
|
||||
summary = "Option to print DEBUG message if ASM is involved",
|
||||
description = "This option enables to print DEBUG message of ASM for each method invocation."
|
||||
)
|
||||
public static volatile boolean isDebugForAsm = false;
|
||||
|
||||
/**
|
||||
* 是否日志中保存命令执行结果
|
||||
*/
|
||||
@Option(level = 1,
|
||||
name = "save-result",
|
||||
summary = "Option to print command's result to log file",
|
||||
description = "This option enables to save each command's result to log file, " +
|
||||
"which path is ${user.home}/logs/arthas/result.log."
|
||||
)
|
||||
public static volatile boolean isSaveResult = false;
|
||||
|
||||
/**
|
||||
* job的超时时间
|
||||
*/
|
||||
@Option(level = 2,
|
||||
name = "job-timeout",
|
||||
summary = "Option to job timeout",
|
||||
description = "This option setting job timeout,The unit can be d, h, m, s for day, hour, minute, second. "
|
||||
+ "1d is one day in default"
|
||||
)
|
||||
public static volatile String jobTimeout = "1d";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.taobao.arthas.core;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Arthas全局选项
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Option {
|
||||
|
||||
/*
|
||||
* 选项级别,数字越小级别越高
|
||||
*/
|
||||
int level();
|
||||
|
||||
/*
|
||||
* 选项名称
|
||||
*/
|
||||
String name();
|
||||
|
||||
/*
|
||||
* 选项摘要说明
|
||||
*/
|
||||
String summary();
|
||||
|
||||
/*
|
||||
* 命令描述
|
||||
*/
|
||||
String description();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
/**
|
||||
* 通知点 Created by vlinux on 15/5/20.
|
||||
*/
|
||||
public class Advice {
|
||||
|
||||
private final ClassLoader loader;
|
||||
private final Class<?> clazz;
|
||||
private final ArthasMethod method;
|
||||
private final Object target;
|
||||
private final Object[] params;
|
||||
private final Object returnObj;
|
||||
private final Throwable throwExp;
|
||||
|
||||
private final static int ACCESS_BEFORE = 1;
|
||||
private final static int ACCESS_AFTER_RETUNING = 1 << 1;
|
||||
private final static int ACCESS_AFTER_THROWING = 1 << 2;
|
||||
|
||||
private final boolean isBefore;
|
||||
private final boolean isThrow;
|
||||
private final boolean isReturn;
|
||||
|
||||
public boolean isBefore() {
|
||||
return isBefore;
|
||||
}
|
||||
|
||||
public boolean isAfterReturning() {
|
||||
return isReturn;
|
||||
}
|
||||
|
||||
public boolean isAfterThrowing() {
|
||||
return isThrow;
|
||||
}
|
||||
|
||||
public ClassLoader getLoader() {
|
||||
return loader;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public Object[] getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public Object getReturnObj() {
|
||||
return returnObj;
|
||||
}
|
||||
|
||||
public Throwable getThrowExp() {
|
||||
return throwExp;
|
||||
}
|
||||
|
||||
public Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public ArthasMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* for finish
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param clazz 类
|
||||
* @param method 方法
|
||||
* @param target 目标类
|
||||
* @param params 调用参数
|
||||
* @param returnObj 返回值
|
||||
* @param throwExp 抛出异常
|
||||
* @param access 进入场景
|
||||
*/
|
||||
private Advice(
|
||||
ClassLoader loader,
|
||||
Class<?> clazz,
|
||||
ArthasMethod method,
|
||||
Object target,
|
||||
Object[] params,
|
||||
Object returnObj,
|
||||
Throwable throwExp,
|
||||
int access) {
|
||||
this.loader = loader;
|
||||
this.clazz = clazz;
|
||||
this.method = method;
|
||||
this.target = target;
|
||||
this.params = params;
|
||||
this.returnObj = returnObj;
|
||||
this.throwExp = throwExp;
|
||||
isBefore = (access & ACCESS_BEFORE) == ACCESS_BEFORE;
|
||||
isThrow = (access & ACCESS_AFTER_THROWING) == ACCESS_AFTER_THROWING;
|
||||
isReturn = (access & ACCESS_AFTER_RETUNING) == ACCESS_AFTER_RETUNING;
|
||||
}
|
||||
|
||||
public static Advice newForBefore(ClassLoader loader,
|
||||
Class<?> clazz,
|
||||
ArthasMethod method,
|
||||
Object target,
|
||||
Object[] params) {
|
||||
return new Advice(
|
||||
loader,
|
||||
clazz,
|
||||
method,
|
||||
target,
|
||||
params,
|
||||
null, //returnObj
|
||||
null, //throwExp
|
||||
ACCESS_BEFORE
|
||||
);
|
||||
}
|
||||
|
||||
public static Advice newForAfterRetuning(ClassLoader loader,
|
||||
Class<?> clazz,
|
||||
ArthasMethod method,
|
||||
Object target,
|
||||
Object[] params,
|
||||
Object returnObj) {
|
||||
return new Advice(
|
||||
loader,
|
||||
clazz,
|
||||
method,
|
||||
target,
|
||||
params,
|
||||
returnObj,
|
||||
null, //throwExp
|
||||
ACCESS_AFTER_RETUNING
|
||||
);
|
||||
}
|
||||
|
||||
public static Advice newForAfterThrowing(ClassLoader loader,
|
||||
Class<?> clazz,
|
||||
ArthasMethod method,
|
||||
Object target,
|
||||
Object[] params,
|
||||
Throwable throwExp) {
|
||||
return new Advice(
|
||||
loader,
|
||||
clazz,
|
||||
method,
|
||||
target,
|
||||
params,
|
||||
null, //returnObj
|
||||
throwExp,
|
||||
ACCESS_AFTER_THROWING
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
/**
|
||||
* 通知监听器<br/>
|
||||
* Created by vlinux on 15/5/17.
|
||||
*/
|
||||
public interface AdviceListener {
|
||||
|
||||
/**
|
||||
* 监听器创建<br/>
|
||||
* 监听器被注册时触发
|
||||
*/
|
||||
void create();
|
||||
|
||||
/**
|
||||
* 监听器销毁<br/>
|
||||
* 监听器被销毁时触发
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* 前置通知
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param methodDesc 方法描述
|
||||
* @param target 目标类实例
|
||||
* 若目标为静态方法,则为null
|
||||
* @param args 参数列表
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
void before(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args) throws Throwable;
|
||||
|
||||
/**
|
||||
* 返回通知
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param methodDesc 方法描述
|
||||
* @param target 目标类实例
|
||||
* 若目标为静态方法,则为null
|
||||
* @param args 参数列表
|
||||
* @param returnObject 返回结果
|
||||
* 若为无返回值方法(void),则为null
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
void afterReturning(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args,
|
||||
Object returnObject) throws Throwable;
|
||||
|
||||
/**
|
||||
* 异常通知
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param methodDesc 方法描述
|
||||
* @param target 目标类实例
|
||||
* 若目标为静态方法,则为null
|
||||
* @param args 参数列表
|
||||
* @param throwable 目标异常
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
void afterThrowing(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args,
|
||||
Throwable throwable) throws Throwable;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
/**
|
||||
* 通知监听适配器
|
||||
*/
|
||||
public class AdviceListenerAdapter implements AdviceListener {
|
||||
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args) throws Throwable {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args,
|
||||
Throwable throwable) throws Throwable {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,984 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.*;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
import com.taobao.arthas.core.util.collection.GaStack;
|
||||
import com.taobao.arthas.core.util.collection.ThreadUnsafeFixGaStack;
|
||||
import com.taobao.arthas.core.util.collection.ThreadUnsafeGaStack;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
import org.objectweb.asm.*;
|
||||
import org.objectweb.asm.commons.AdviceAdapter;
|
||||
import org.objectweb.asm.commons.JSRInlinerAdapter;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 通知编织者<br/>
|
||||
* <p/>
|
||||
* <h2>线程帧栈与执行帧栈</h2>
|
||||
* 编织者在执行通知的时候有两个重要的栈:线程帧栈(threadFrameStack),执行帧栈(frameStack)
|
||||
* <p/>
|
||||
* Created by vlinux on 15/5/17.
|
||||
*/
|
||||
public class AdviceWeaver extends ClassVisitor implements Opcodes {
|
||||
|
||||
private final static Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
|
||||
|
||||
// 线程帧栈堆栈大小
|
||||
private final static int FRAME_STACK_SIZE = 7;
|
||||
// 通知监听器集合
|
||||
private final static Map<Integer/*ADVICE_ID*/, AdviceListener> advices
|
||||
= new ConcurrentHashMap<Integer, AdviceListener>();
|
||||
// 线程帧封装
|
||||
private static final ThreadLocal<GaStack<GaStack<Object>>> threadBoundContext
|
||||
= new ThreadLocal<GaStack<GaStack<Object>>>();
|
||||
// 防止自己递归调用
|
||||
private static final ThreadLocal<Boolean> isSelfCallRef = new ThreadLocal<Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean initialValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 方法开始<br/>
|
||||
* 用于编织通知器,外部不会直接调用
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param adviceId 通知ID
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param methodDesc 方法描述
|
||||
* @param target 返回结果
|
||||
* 若为无返回值方法(void),则为null
|
||||
* @param args 参数列表
|
||||
*/
|
||||
public static void methodOnBegin(
|
||||
int adviceId,
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args) {
|
||||
|
||||
if (isSelfCallRef.get()) {
|
||||
return;
|
||||
} else {
|
||||
isSelfCallRef.set(true);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建执行帧栈,保护当前的执行现场
|
||||
final GaStack<Object> frameStack = new ThreadUnsafeFixGaStack<Object>(FRAME_STACK_SIZE);
|
||||
frameStack.push(loader);
|
||||
frameStack.push(className);
|
||||
frameStack.push(methodName);
|
||||
frameStack.push(methodDesc);
|
||||
frameStack.push(target);
|
||||
frameStack.push(args);
|
||||
|
||||
final AdviceListener listener = getListener(adviceId);
|
||||
frameStack.push(listener);
|
||||
|
||||
// 获取通知器并做前置通知
|
||||
before(listener, loader, className, methodName, methodDesc, target, args);
|
||||
|
||||
// 保护当前执行帧栈,压入线程帧栈
|
||||
threadFrameStackPush(frameStack);
|
||||
} finally {
|
||||
isSelfCallRef.set(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 方法以返回结束<br/>
|
||||
* 用于编织通知器,外部不会直接调用
|
||||
*
|
||||
* @param returnObject 返回对象
|
||||
* 若目标为静态方法,则为null
|
||||
*/
|
||||
public static void methodOnReturnEnd(Object returnObject) {
|
||||
methodOnEnd(false, returnObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法以抛异常结束<br/>
|
||||
* 用于编织通知器,外部不会直接调用
|
||||
*
|
||||
* @param throwable 抛出异常
|
||||
*/
|
||||
public static void methodOnThrowingEnd(Throwable throwable) {
|
||||
methodOnEnd(true, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有的返回都统一处理
|
||||
*
|
||||
* @param isThrowing 标记正常返回结束还是抛出异常结束
|
||||
* @param returnOrThrowable 正常返回或者抛出异常对象
|
||||
*/
|
||||
private static void methodOnEnd(boolean isThrowing, Object returnOrThrowable) {
|
||||
|
||||
if (isSelfCallRef.get()) {
|
||||
return;
|
||||
} else {
|
||||
isSelfCallRef.set(true);
|
||||
}
|
||||
|
||||
try {
|
||||
// 弹射线程帧栈,恢复Begin所保护的执行帧栈
|
||||
final GaStack<Object> frameStack = threadFrameStackPop();
|
||||
|
||||
// 弹射执行帧栈,恢复Begin所保护的现场
|
||||
final AdviceListener listener = (AdviceListener) frameStack.pop();
|
||||
final Object[] args = (Object[]) frameStack.pop();
|
||||
final Object target = frameStack.pop();
|
||||
final String methodDesc = (String) frameStack.pop();
|
||||
final String methodName = (String) frameStack.pop();
|
||||
final String className = (String) frameStack.pop();
|
||||
final ClassLoader loader = (ClassLoader) frameStack.pop();
|
||||
|
||||
// 异常通知
|
||||
if (isThrowing) {
|
||||
afterThrowing(listener, loader, className, methodName, methodDesc, target, args, (Throwable) returnOrThrowable);
|
||||
}
|
||||
|
||||
// 返回通知
|
||||
else {
|
||||
afterReturning(listener, loader, className, methodName, methodDesc, target, args, returnOrThrowable);
|
||||
}
|
||||
} finally {
|
||||
isSelfCallRef.set(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法内部调用开始
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
* @param owner 调用类名
|
||||
* @param name 调用方法名
|
||||
* @param desc 调用方法描述
|
||||
*/
|
||||
public static void methodOnInvokeBeforeTracing(int adviceId, String owner, String name, String desc) {
|
||||
final InvokeTraceable listener = (InvokeTraceable) getListener(adviceId);
|
||||
if (null != listener) {
|
||||
try {
|
||||
listener.invokeBeforeTracing(owner, name, desc);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("advice before tracing failed.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法内部调用结束(正常返回)
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
* @param owner 调用类名
|
||||
* @param name 调用方法名
|
||||
* @param desc 调用方法描述
|
||||
*/
|
||||
public static void methodOnInvokeAfterTracing(int adviceId, String owner, String name, String desc) {
|
||||
final InvokeTraceable listener = (InvokeTraceable) getListener(adviceId);
|
||||
if (null != listener) {
|
||||
try {
|
||||
listener.invokeAfterTracing(owner, name, desc);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("advice after tracing failed.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法内部调用结束(异常返回)
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
* @param owner 调用类名
|
||||
* @param name 调用方法名
|
||||
* @param desc 调用方法描述
|
||||
*/
|
||||
public static void methodOnInvokeThrowTracing(int adviceId, String owner, String name, String desc) {
|
||||
final InvokeTraceable listener = (InvokeTraceable) getListener(adviceId);
|
||||
if (null != listener) {
|
||||
try {
|
||||
listener.invokeThrowTracing(owner, name, desc);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("advice throw tracing failed.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 线程帧栈压栈<br/>
|
||||
* 将当前执行帧栈压入线程栈
|
||||
*/
|
||||
private static void threadFrameStackPush(GaStack<Object> frameStack) {
|
||||
GaStack<GaStack<Object>> threadFrameStack = threadBoundContext.get();
|
||||
if (null == threadFrameStack) {
|
||||
threadBoundContext.set(threadFrameStack = new ThreadUnsafeGaStack<GaStack<Object>>());
|
||||
}
|
||||
|
||||
threadFrameStack.push(frameStack);
|
||||
}
|
||||
|
||||
private static GaStack<Object> threadFrameStackPop() {
|
||||
return threadBoundContext.get().pop();
|
||||
}
|
||||
|
||||
private static AdviceListener getListener(int adviceId) {
|
||||
return advices.get(adviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册监听器
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
* @param listener 通知监听器
|
||||
*/
|
||||
public static void reg(int adviceId, AdviceListener listener) {
|
||||
|
||||
// 触发监听器创建
|
||||
listener.create();
|
||||
|
||||
// 注册监听器
|
||||
advices.put(adviceId, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销监听器
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
*/
|
||||
public static void unReg(int adviceId) {
|
||||
|
||||
// 注销监听器
|
||||
final AdviceListener listener = advices.remove(adviceId);
|
||||
|
||||
// 触发监听器销毁
|
||||
if (null != listener) {
|
||||
listener.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 恢复监听
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
* @param listener 通知监听器
|
||||
*/
|
||||
public static void resume(int adviceId, AdviceListener listener) {
|
||||
// 注册监听器
|
||||
advices.put(adviceId, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停监听
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
*/
|
||||
public static AdviceListener suspend(int adviceId) {
|
||||
// 注销监听器
|
||||
return advices.remove(adviceId);
|
||||
}
|
||||
|
||||
private static void before(AdviceListener listener,
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args) {
|
||||
|
||||
if (null != listener) {
|
||||
try {
|
||||
listener.before(loader, className, methodName, methodDesc, target, args);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("advice before failed.", t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void afterReturning(AdviceListener listener,
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args, Object returnObject) {
|
||||
if (null != listener) {
|
||||
try {
|
||||
listener.afterReturning(loader, className, methodName, methodDesc, target, args, returnObject);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("advice returning failed.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void afterThrowing(AdviceListener listener,
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args, Throwable throwable) {
|
||||
if (null != listener) {
|
||||
try {
|
||||
listener.afterThrowing(loader, className, methodName, methodDesc, target, args, throwable);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("advice throwing failed.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final int adviceId;
|
||||
private final boolean isTracing;
|
||||
private final boolean skipJDKTrace;
|
||||
private final String className;
|
||||
private String superName;
|
||||
private final Matcher matcher;
|
||||
private final EnhancerAffect affect;
|
||||
|
||||
|
||||
/**
|
||||
* 构建通知编织器
|
||||
*
|
||||
* @param adviceId 通知ID
|
||||
* @param isTracing 可跟踪方法调用
|
||||
* @param className 类名称
|
||||
* @param matcher 方法匹配
|
||||
* 只有匹配上的方法才会被织入通知器
|
||||
* @param affect 影响计数
|
||||
* @param cv ClassVisitor for ASM
|
||||
*/
|
||||
public AdviceWeaver(int adviceId, boolean isTracing, boolean skipJDKTrace, String className, Matcher matcher, EnhancerAffect affect, ClassVisitor cv) {
|
||||
super(ASM5, cv);
|
||||
this.adviceId = adviceId;
|
||||
this.isTracing = isTracing;
|
||||
this.skipJDKTrace = skipJDKTrace;
|
||||
this.className = className;
|
||||
this.matcher = matcher;
|
||||
this.affect = affect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
this.superName = superName;
|
||||
}
|
||||
|
||||
protected boolean isSuperOrSiblingConstructorCall(int opcode, String owner, String name) {
|
||||
return (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")
|
||||
&& (superName.equals(owner) || className.equals(owner)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否抽象属性
|
||||
*/
|
||||
private boolean isAbstract(int access) {
|
||||
return (ACC_ABSTRACT & access) == ACC_ABSTRACT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否需要忽略
|
||||
*/
|
||||
private boolean isIgnore(MethodVisitor mv, int access, String methodName) {
|
||||
return null == mv
|
||||
|| isAbstract(access)
|
||||
|| !matcher.matching(methodName)
|
||||
|| ArthasCheckUtils.isEquals(methodName, "<clinit>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(
|
||||
final int access,
|
||||
final String name,
|
||||
final String desc,
|
||||
final String signature,
|
||||
final String[] exceptions) {
|
||||
|
||||
final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
|
||||
if (isIgnore(mv, access, name)) {
|
||||
return mv;
|
||||
}
|
||||
|
||||
// 编织方法计数
|
||||
affect.mCnt(1);
|
||||
|
||||
return new AdviceAdapter(ASM5, new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions), access, name, desc) {
|
||||
|
||||
// -- Label for try...catch block
|
||||
private final Label beginLabel = new Label();
|
||||
private final Label endLabel = new Label();
|
||||
|
||||
// -- KEY of advice --
|
||||
private final int KEY_ARTHAS_ADVICE_BEFORE_METHOD = 0;
|
||||
private final int KEY_ARTHAS_ADVICE_RETURN_METHOD = 1;
|
||||
private final int KEY_ARTHAS_ADVICE_THROWS_METHOD = 2;
|
||||
private final int KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD = 3;
|
||||
private final int KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD = 4;
|
||||
private final int KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD = 5;
|
||||
|
||||
|
||||
// -- KEY of ASM_TYPE or ASM_METHOD --
|
||||
private final Type ASM_TYPE_SPY = Type.getType("Ljava/arthas/Spy;");
|
||||
private final Type ASM_TYPE_OBJECT = Type.getType(Object.class);
|
||||
private final Type ASM_TYPE_OBJECT_ARRAY = Type.getType(Object[].class);
|
||||
private final Type ASM_TYPE_CLASS = Type.getType(Class.class);
|
||||
private final Type ASM_TYPE_INTEGER = Type.getType(Integer.class);
|
||||
private final Type ASM_TYPE_CLASS_LOADER = Type.getType(ClassLoader.class);
|
||||
private final Type ASM_TYPE_STRING = Type.getType(String.class);
|
||||
private final Type ASM_TYPE_THROWABLE = Type.getType(Throwable.class);
|
||||
private final Type ASM_TYPE_INT = Type.getType(int.class);
|
||||
private final Type ASM_TYPE_METHOD = Type.getType(java.lang.reflect.Method.class);
|
||||
private final Method ASM_METHOD_METHOD_INVOKE = Method.getMethod("Object invoke(Object,Object[])");
|
||||
|
||||
// 代码锁
|
||||
private final CodeLock codeLockForTracing = new TracingAsmCodeLock(this);
|
||||
|
||||
|
||||
private void _debug(final StringBuilder append, final String msg) {
|
||||
|
||||
if (!GlobalOptions.isDebugForAsm) {
|
||||
return;
|
||||
}
|
||||
|
||||
// println msg
|
||||
visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
if (StringUtils.isBlank(append.toString())) {
|
||||
visitLdcInsn(append.append(msg).toString());
|
||||
} else {
|
||||
visitLdcInsn(append.append(" >> ").append(msg).toString());
|
||||
}
|
||||
|
||||
visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
}
|
||||
|
||||
// private void _debug_dup(final String msg) {
|
||||
//
|
||||
// if (!isDebugForAsm) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // print prefix
|
||||
// visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
// visitLdcInsn(msg);
|
||||
// visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
|
||||
//
|
||||
// // println msg
|
||||
// dup();
|
||||
// visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
// swap();
|
||||
// visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
|
||||
// visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 加载通知方法
|
||||
* @param keyOfMethod 通知方法KEY
|
||||
*/
|
||||
private void loadAdviceMethod(int keyOfMethod) {
|
||||
|
||||
switch (keyOfMethod) {
|
||||
|
||||
case KEY_ARTHAS_ADVICE_BEFORE_METHOD: {
|
||||
getStatic(ASM_TYPE_SPY, "ON_BEFORE_METHOD", ASM_TYPE_METHOD);
|
||||
break;
|
||||
}
|
||||
|
||||
case KEY_ARTHAS_ADVICE_RETURN_METHOD: {
|
||||
getStatic(ASM_TYPE_SPY, "ON_RETURN_METHOD", ASM_TYPE_METHOD);
|
||||
break;
|
||||
}
|
||||
|
||||
case KEY_ARTHAS_ADVICE_THROWS_METHOD: {
|
||||
getStatic(ASM_TYPE_SPY, "ON_THROWS_METHOD", ASM_TYPE_METHOD);
|
||||
break;
|
||||
}
|
||||
|
||||
case KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD: {
|
||||
getStatic(ASM_TYPE_SPY, "BEFORE_INVOKING_METHOD", ASM_TYPE_METHOD);
|
||||
break;
|
||||
}
|
||||
|
||||
case KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD: {
|
||||
getStatic(ASM_TYPE_SPY, "AFTER_INVOKING_METHOD", ASM_TYPE_METHOD);
|
||||
break;
|
||||
}
|
||||
|
||||
case KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD: {
|
||||
getStatic(ASM_TYPE_SPY, "THROW_INVOKING_METHOD", ASM_TYPE_METHOD);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new IllegalArgumentException("illegal keyOfMethod=" + keyOfMethod);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载ClassLoader<br/>
|
||||
* 这里分开静态方法中ClassLoader的获取以及普通方法中ClassLoader的获取
|
||||
* 主要是性能上的考虑
|
||||
*/
|
||||
private void loadClassLoader() {
|
||||
|
||||
if (this.isStaticMethod()) {
|
||||
visitLdcInsn(StringUtils.normalizeClassName(className));
|
||||
invokeStatic(ASM_TYPE_CLASS, Method.getMethod("Class forName(String)"));
|
||||
invokeVirtual(ASM_TYPE_CLASS, Method.getMethod("ClassLoader getClassLoader()"));
|
||||
|
||||
} else {
|
||||
loadThis();
|
||||
invokeVirtual(ASM_TYPE_OBJECT, Method.getMethod("Class getClass()"));
|
||||
invokeVirtual(ASM_TYPE_CLASS, Method.getMethod("ClassLoader getClassLoader()"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载before通知参数数组
|
||||
*/
|
||||
private void loadArrayForBefore() {
|
||||
push(7);
|
||||
newArray(ASM_TYPE_OBJECT);
|
||||
|
||||
dup();
|
||||
push(0);
|
||||
push(adviceId);
|
||||
box(ASM_TYPE_INT);
|
||||
arrayStore(ASM_TYPE_INTEGER);
|
||||
|
||||
dup();
|
||||
push(1);
|
||||
loadClassLoader();
|
||||
arrayStore(ASM_TYPE_CLASS_LOADER);
|
||||
|
||||
dup();
|
||||
push(2);
|
||||
push(className);
|
||||
arrayStore(ASM_TYPE_STRING);
|
||||
|
||||
dup();
|
||||
push(3);
|
||||
push(name);
|
||||
arrayStore(ASM_TYPE_STRING);
|
||||
|
||||
dup();
|
||||
push(4);
|
||||
push(desc);
|
||||
arrayStore(ASM_TYPE_STRING);
|
||||
|
||||
dup();
|
||||
push(5);
|
||||
loadThisOrPushNullIfIsStatic();
|
||||
arrayStore(ASM_TYPE_OBJECT);
|
||||
|
||||
dup();
|
||||
push(6);
|
||||
loadArgArray();
|
||||
arrayStore(ASM_TYPE_OBJECT_ARRAY);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onMethodEnter() {
|
||||
|
||||
codeLockForTracing.lock(new CodeLock.Block() {
|
||||
@Override
|
||||
public void code() {
|
||||
|
||||
final StringBuilder append = new StringBuilder();
|
||||
_debug(append, "debug:onMethodEnter()");
|
||||
|
||||
// 加载before方法
|
||||
loadAdviceMethod(KEY_ARTHAS_ADVICE_BEFORE_METHOD);
|
||||
|
||||
_debug(append, "debug:onMethodEnter() > loadAdviceMethod()");
|
||||
|
||||
// 推入Method.invoke()的第一个参数
|
||||
pushNull();
|
||||
|
||||
// 方法参数
|
||||
loadArrayForBefore();
|
||||
|
||||
_debug(append, "debug:onMethodEnter() > loadAdviceMethod() > loadArrayForBefore()");
|
||||
|
||||
// 调用方法
|
||||
invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE);
|
||||
pop();
|
||||
|
||||
_debug(append, "debug:onMethodEnter() > loadAdviceMethod() > loadArrayForBefore() > invokeVirtual()");
|
||||
}
|
||||
});
|
||||
|
||||
mark(beginLabel);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 加载return通知参数数组
|
||||
*/
|
||||
private void loadReturnArgs() {
|
||||
dup2X1();
|
||||
pop2();
|
||||
push(1);
|
||||
newArray(ASM_TYPE_OBJECT);
|
||||
dup();
|
||||
dup2X1();
|
||||
pop2();
|
||||
push(0);
|
||||
swap();
|
||||
arrayStore(ASM_TYPE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMethodExit(final int opcode) {
|
||||
|
||||
if (!isThrow(opcode)) {
|
||||
codeLockForTracing.lock(new CodeLock.Block() {
|
||||
@Override
|
||||
public void code() {
|
||||
|
||||
final StringBuilder append = new StringBuilder();
|
||||
_debug(append, "debug:onMethodExit()");
|
||||
|
||||
// 加载返回对象
|
||||
loadReturn(opcode);
|
||||
_debug(append, "debug:onMethodExit() > loadReturn()");
|
||||
|
||||
|
||||
// 加载returning方法
|
||||
loadAdviceMethod(KEY_ARTHAS_ADVICE_RETURN_METHOD);
|
||||
_debug(append, "debug:onMethodExit() > loadReturn() > loadAdviceMethod()");
|
||||
|
||||
// 推入Method.invoke()的第一个参数
|
||||
pushNull();
|
||||
|
||||
// 加载return通知参数数组
|
||||
loadReturnArgs();
|
||||
_debug(append, "debug:onMethodExit() > loadReturn() > loadAdviceMethod() > loadReturnArgs()");
|
||||
|
||||
invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE);
|
||||
pop();
|
||||
|
||||
_debug(append, "debug:onMethodExit() > loadReturn() > loadAdviceMethod() > loadReturnArgs() > invokeVirtual()");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 创建throwing通知参数本地变量
|
||||
*/
|
||||
private void loadThrowArgs() {
|
||||
dup2X1();
|
||||
pop2();
|
||||
push(1);
|
||||
newArray(ASM_TYPE_OBJECT);
|
||||
dup();
|
||||
dup2X1();
|
||||
pop2();
|
||||
push(0);
|
||||
swap();
|
||||
arrayStore(ASM_TYPE_THROWABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
|
||||
mark(endLabel);
|
||||
// catchException(beginLabel, endLabel, ASM_TYPE_THROWABLE);
|
||||
visitTryCatchBlock(beginLabel, endLabel, mark(),
|
||||
ASM_TYPE_THROWABLE.getInternalName());
|
||||
|
||||
codeLockForTracing.lock(new CodeLock.Block() {
|
||||
@Override
|
||||
public void code() {
|
||||
|
||||
final StringBuilder append = new StringBuilder();
|
||||
_debug(append, "debug:catchException()");
|
||||
|
||||
// 加载异常
|
||||
loadThrow();
|
||||
_debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod()");
|
||||
|
||||
// 加载throwing方法
|
||||
loadAdviceMethod(KEY_ARTHAS_ADVICE_THROWS_METHOD);
|
||||
_debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod()");
|
||||
|
||||
|
||||
// 推入Method.invoke()的第一个参数
|
||||
pushNull();
|
||||
|
||||
// 加载throw通知参数数组
|
||||
loadThrowArgs();
|
||||
_debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod() > loadThrowArgs()");
|
||||
|
||||
// 调用方法
|
||||
invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE);
|
||||
pop();
|
||||
_debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod() > loadThrowArgs() > invokeVirtual()");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
throwException();
|
||||
|
||||
super.visitMaxs(maxStack, maxLocals);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否静态方法
|
||||
* @return true:静态方法 / false:非静态方法
|
||||
*/
|
||||
private boolean isStaticMethod() {
|
||||
return (methodAccess & ACC_STATIC) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否抛出异常返回(通过字节码判断)
|
||||
* @param opcode 操作码
|
||||
* @return true:以抛异常形式返回 / false:非抛异常形式返回(return)
|
||||
*/
|
||||
private boolean isThrow(int opcode) {
|
||||
return opcode == ATHROW;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将NULL推入堆栈
|
||||
*/
|
||||
private void pushNull() {
|
||||
push((Type) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载this/null
|
||||
*/
|
||||
private void loadThisOrPushNullIfIsStatic() {
|
||||
if (isStaticMethod()) {
|
||||
pushNull();
|
||||
} else {
|
||||
loadThis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载返回值
|
||||
* @param opcode 操作吗
|
||||
*/
|
||||
private void loadReturn(int opcode) {
|
||||
switch (opcode) {
|
||||
|
||||
case RETURN: {
|
||||
pushNull();
|
||||
break;
|
||||
}
|
||||
|
||||
case ARETURN: {
|
||||
dup();
|
||||
break;
|
||||
}
|
||||
|
||||
case LRETURN:
|
||||
case DRETURN: {
|
||||
dup2();
|
||||
box(Type.getReturnType(methodDesc));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
dup();
|
||||
box(Type.getReturnType(methodDesc));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载异常
|
||||
*/
|
||||
private void loadThrow() {
|
||||
dup();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载方法调用跟踪通知所需参数数组
|
||||
*/
|
||||
private void loadArrayForInvokeTracing(String owner, String name, String desc) {
|
||||
push(4);
|
||||
newArray(ASM_TYPE_OBJECT);
|
||||
|
||||
dup();
|
||||
push(0);
|
||||
push(adviceId);
|
||||
box(ASM_TYPE_INT);
|
||||
arrayStore(ASM_TYPE_INTEGER);
|
||||
|
||||
dup();
|
||||
push(1);
|
||||
push(owner);
|
||||
arrayStore(ASM_TYPE_STRING);
|
||||
|
||||
dup();
|
||||
push(2);
|
||||
push(name);
|
||||
arrayStore(ASM_TYPE_STRING);
|
||||
|
||||
dup();
|
||||
push(3);
|
||||
push(desc);
|
||||
arrayStore(ASM_TYPE_STRING);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
super.visitInsn(opcode);
|
||||
codeLockForTracing.code(opcode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
tcbs.add(new AsmTryCatchBlock(start, end, handler, type));
|
||||
}
|
||||
|
||||
List<AsmTryCatchBlock> tcbs = new ArrayList<AsmTryCatchBlock>();
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
for (AsmTryCatchBlock tcb : tcbs) {
|
||||
super.visitTryCatchBlock(tcb.start, tcb.end, tcb.handler, tcb.type);
|
||||
}
|
||||
|
||||
super.visitEnd();
|
||||
}
|
||||
|
||||
/*
|
||||
* 跟踪代码
|
||||
*/
|
||||
private void tracing(final int tracingType, final String owner, final String name, final String desc) {
|
||||
|
||||
final String label;
|
||||
switch (tracingType) {
|
||||
case KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD: {
|
||||
label = "beforeInvoking";
|
||||
break;
|
||||
}
|
||||
case KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD: {
|
||||
label = "afterInvoking";
|
||||
break;
|
||||
}
|
||||
case KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD: {
|
||||
label = "throwInvoking";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("illegal tracing type: " + tracingType);
|
||||
}
|
||||
}
|
||||
|
||||
codeLockForTracing.lock(new CodeLock.Block() {
|
||||
@Override
|
||||
public void code() {
|
||||
|
||||
final StringBuilder append = new StringBuilder();
|
||||
_debug(append, "debug:" + label + "()");
|
||||
|
||||
loadAdviceMethod(tracingType);
|
||||
_debug(append, "loadAdviceMethod()");
|
||||
|
||||
pushNull();
|
||||
loadArrayForInvokeTracing(owner, name, desc);
|
||||
_debug(append, "loadArrayForInvokeTracing()");
|
||||
|
||||
invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE);
|
||||
pop();
|
||||
_debug(append, "invokeVirtual()");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, final String owner, final String name, final String desc, boolean itf) {
|
||||
if (isSuperOrSiblingConstructorCall(opcode, owner, name)) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTracing || codeLockForTracing.isLock()) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
return;
|
||||
}
|
||||
|
||||
//是否要对JDK内部的方法调用进行trace
|
||||
if (skipJDKTrace && owner.startsWith("java/")) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
return;
|
||||
}
|
||||
|
||||
// 方法调用前通知
|
||||
tracing(KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD, owner, name, desc);
|
||||
|
||||
final Label beginLabel = new Label();
|
||||
final Label endLabel = new Label();
|
||||
final Label finallyLabel = new Label();
|
||||
|
||||
// try
|
||||
// {
|
||||
|
||||
mark(beginLabel);
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
mark(endLabel);
|
||||
|
||||
// 方法调用后通知
|
||||
tracing(KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD, owner, name, desc);
|
||||
goTo(finallyLabel);
|
||||
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
|
||||
catchException(beginLabel, endLabel, ASM_TYPE_THROWABLE);
|
||||
tracing(KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD, owner, name, desc);
|
||||
|
||||
throwException();
|
||||
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
mark(finallyLabel);
|
||||
// }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static class AsmTryCatchBlock {
|
||||
Label start;
|
||||
Label end;
|
||||
Label handler;
|
||||
String type;
|
||||
|
||||
AsmTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.handler = handler;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Arthas封装的方法<br/>
|
||||
* 主要用来封装构造函数cinit/init/method
|
||||
* Created by vlinux on 15/5/24.
|
||||
*/
|
||||
public class ArthasMethod {
|
||||
|
||||
private final int type;
|
||||
private final Constructor<?> constructor;
|
||||
private final Method method;
|
||||
|
||||
/*
|
||||
* 构造方法
|
||||
*/
|
||||
private static final int TYPE_INIT = 1 << 1;
|
||||
|
||||
/*
|
||||
* 普通方法
|
||||
*/
|
||||
private static final int TYPE_METHOD = 1 << 2;
|
||||
|
||||
/**
|
||||
* 是否构造方法
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public boolean isInit() {
|
||||
return (TYPE_INIT & type) == TYPE_INIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否普通方法
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public boolean isMethod() {
|
||||
return (TYPE_METHOD & type) == TYPE_METHOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法名称
|
||||
*
|
||||
* @return 返回方法名称
|
||||
*/
|
||||
public String getName() {
|
||||
return isInit()
|
||||
? "<init>"
|
||||
: method.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return isInit()
|
||||
? constructor.toString()
|
||||
: method.toString();
|
||||
}
|
||||
|
||||
public boolean isAccessible() {
|
||||
return isInit()
|
||||
? constructor.isAccessible()
|
||||
: method.isAccessible();
|
||||
}
|
||||
|
||||
public void setAccessible(boolean accessFlag) {
|
||||
if (isInit()) {
|
||||
constructor.setAccessible(accessFlag);
|
||||
} else {
|
||||
method.setAccessible(accessFlag);
|
||||
}
|
||||
}
|
||||
|
||||
public Object invoke(Object target, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
return isInit()
|
||||
? constructor.newInstance(args)
|
||||
: method.invoke(target, args);
|
||||
}
|
||||
|
||||
private ArthasMethod(int type, Constructor<?> constructor, Method method) {
|
||||
this.type = type;
|
||||
this.constructor = constructor;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public static ArthasMethod newInit(Constructor<?> constructor) {
|
||||
return new ArthasMethod(TYPE_INIT, constructor, null);
|
||||
}
|
||||
|
||||
public static ArthasMethod newMethod(Method method) {
|
||||
return new ArthasMethod(TYPE_METHOD, null, method);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.AdviceAdapter;
|
||||
|
||||
/**
|
||||
* ASM代码锁<br/>
|
||||
* Created by vlinux on 15/5/28.
|
||||
*/
|
||||
public class AsmCodeLock implements CodeLock, Opcodes {
|
||||
|
||||
private final AdviceAdapter aa;
|
||||
|
||||
// 锁标记
|
||||
private boolean isLook;
|
||||
|
||||
// 代码块开始特征数组
|
||||
private final int[] beginCodeArray;
|
||||
|
||||
// 代码块结束特征数组
|
||||
private final int[] endCodeArray;
|
||||
|
||||
// 代码匹配索引
|
||||
private int index = 0;
|
||||
|
||||
|
||||
/**
|
||||
* 用ASM构建代码锁
|
||||
*
|
||||
* @param aa ASM
|
||||
* @param beginCodeArray 代码块开始特征数组
|
||||
* 字节码流要求不能破坏执行堆栈
|
||||
* @param endCodeArray 代码块结束特征数组
|
||||
* 字节码流要求不能破坏执行堆栈
|
||||
*/
|
||||
public AsmCodeLock(AdviceAdapter aa, int[] beginCodeArray, int[] endCodeArray) {
|
||||
if (null == beginCodeArray
|
||||
|| null == endCodeArray
|
||||
|| beginCodeArray.length != endCodeArray.length) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
this.aa = aa;
|
||||
this.beginCodeArray = beginCodeArray;
|
||||
this.endCodeArray = endCodeArray;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void code(int code) {
|
||||
|
||||
final int[] codes = isLock() ? endCodeArray : beginCodeArray;
|
||||
|
||||
if (index >= codes.length) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (codes[index] != code) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (++index == codes.length) {
|
||||
// 翻转锁状态
|
||||
isLook = !isLook;
|
||||
reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* 重置索引<br/>
|
||||
* 一般在代码序列判断失败时,则会对索引进行重置,冲头开始匹配特征序列
|
||||
*/
|
||||
private void reset() {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
|
||||
private void asm(int opcode) {
|
||||
aa.visitInsn(opcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定序列
|
||||
*/
|
||||
private void lock() {
|
||||
for (int op : beginCodeArray) {
|
||||
asm(op);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 解锁序列
|
||||
*/
|
||||
private void unLock() {
|
||||
for (int op : endCodeArray) {
|
||||
asm(op);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLock() {
|
||||
return isLook;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(Block block) {
|
||||
lock();
|
||||
try {
|
||||
block.code();
|
||||
} finally {
|
||||
unLock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
/**
|
||||
* 代码锁<br/>
|
||||
* 什么叫代码锁?代码锁的出现是由于在字节码中,我们无法用简单的if语句来判定这段代码是生成的还是原有的。
|
||||
* 这会导致一些监控逻辑的混乱,比如trace命令如果不使用代码锁保护,将能看到Arthas所植入的代码并进行跟踪
|
||||
* Created by vlinux on 15/5/28.
|
||||
*/
|
||||
public interface CodeLock {
|
||||
|
||||
/**
|
||||
* 根据字节码流锁或解锁代码<br/>
|
||||
* 通过对字节码流的判断,决定当前代码是锁定和解锁
|
||||
*
|
||||
* @param opcode 字节码
|
||||
*/
|
||||
void code(int opcode);
|
||||
|
||||
/**
|
||||
* 判断当前代码是否还在锁定中
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
boolean isLock();
|
||||
|
||||
/**
|
||||
* 将一个代码块纳入代码锁保护范围
|
||||
*
|
||||
* @param block 代码块
|
||||
*/
|
||||
void lock(Block block);
|
||||
|
||||
/**
|
||||
* 代码块
|
||||
*/
|
||||
interface Block {
|
||||
/**
|
||||
* 代码
|
||||
*/
|
||||
void code();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
import com.taobao.arthas.core.util.FileUtils;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
|
||||
import com.taobao.arthas.core.util.reflect.FieldUtils;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.*;
|
||||
|
||||
import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;
|
||||
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
||||
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
|
||||
|
||||
/**
|
||||
* 对类进行通知增强
|
||||
* Created by vlinux on 15/5/17.
|
||||
*/
|
||||
public class Enhancer implements ClassFileTransformer {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private final int adviceId;
|
||||
private final boolean isTracing;
|
||||
private final boolean skipJDKTrace;
|
||||
private final Set<Class<?>> matchingClasses;
|
||||
private final Matcher methodNameMatcher;
|
||||
private final EnhancerAffect affect;
|
||||
|
||||
// 类-字节码缓存
|
||||
private final static Map<Class<?>/*Class*/, byte[]/*bytes of Class*/> classBytesCache
|
||||
= new WeakHashMap<Class<?>, byte[]>();
|
||||
|
||||
/**
|
||||
* @param adviceId 通知编号
|
||||
* @param isTracing 可跟踪方法调用
|
||||
* @param matchingClasses 匹配中的类
|
||||
* @param methodNameMatcher 方法名匹配
|
||||
* @param affect 影响统计
|
||||
*/
|
||||
private Enhancer(int adviceId,
|
||||
boolean isTracing,
|
||||
boolean skipJDKTrace,
|
||||
Set<Class<?>> matchingClasses,
|
||||
Matcher methodNameMatcher,
|
||||
EnhancerAffect affect) {
|
||||
this.adviceId = adviceId;
|
||||
this.isTracing = isTracing;
|
||||
this.skipJDKTrace = skipJDKTrace;
|
||||
this.matchingClasses = matchingClasses;
|
||||
this.methodNameMatcher = methodNameMatcher;
|
||||
this.affect = affect;
|
||||
}
|
||||
|
||||
private void spy(final ClassLoader targetClassLoader) throws Exception {
|
||||
if (targetClassLoader == null) {
|
||||
// 增强JDK自带的类,targetClassLoader为null
|
||||
return;
|
||||
}
|
||||
// 因为 Spy 是被bootstrap classloader加载的,所以一定可以被找到,如果找不到的话,说明应用方的classloader实现有问题
|
||||
Class<?> spyClass = targetClassLoader.loadClass(Constants.SPY_CLASSNAME);
|
||||
|
||||
final ClassLoader arthasClassLoader = Enhancer.class.getClassLoader();
|
||||
|
||||
// 初始化间谍, AgentLauncher会把各种hook设置到ArthasClassLoader当中
|
||||
// 这里我们需要把这些hook取出来设置到目标classloader当中
|
||||
Method initMethod = spyClass.getMethod("init", ClassLoader.class, Method.class,
|
||||
Method.class, Method.class, Method.class, Method.class, Method.class);
|
||||
initMethod.invoke(null, arthasClassLoader,
|
||||
FieldUtils.getField(spyClass, "ON_BEFORE_METHOD").get(null),
|
||||
FieldUtils.getField(spyClass, "ON_RETURN_METHOD").get(null),
|
||||
FieldUtils.getField(spyClass, "ON_THROWS_METHOD").get(null),
|
||||
FieldUtils.getField(spyClass, "BEFORE_INVOKING_METHOD").get(null),
|
||||
FieldUtils.getField(spyClass, "AFTER_INVOKING_METHOD").get(null),
|
||||
FieldUtils.getField(spyClass, "THROW_INVOKING_METHOD").get(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(
|
||||
final ClassLoader inClassLoader,
|
||||
String className,
|
||||
Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain,
|
||||
byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
|
||||
|
||||
// 这里要再次过滤一次,为啥?因为在transform的过程中,有可能还会再诞生新的类
|
||||
// 所以需要将之前需要转换的类集合传递下来,再次进行判断
|
||||
if (!matchingClasses.contains(classBeingRedefined)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ClassReader cr;
|
||||
|
||||
// 首先先检查是否在缓存中存在Class字节码
|
||||
// 因为要支持多人协作,存在多人同时增强的情况
|
||||
final byte[] byteOfClassInCache = classBytesCache.get(classBeingRedefined);
|
||||
if (null != byteOfClassInCache) {
|
||||
cr = new ClassReader(byteOfClassInCache);
|
||||
}
|
||||
|
||||
// 如果没有命中缓存,则从原始字节码开始增强
|
||||
else {
|
||||
cr = new ClassReader(classfileBuffer);
|
||||
}
|
||||
|
||||
// 字节码增强
|
||||
final ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS) {
|
||||
|
||||
|
||||
/*
|
||||
* 注意,为了自动计算帧的大小,有时必须计算两个类共同的父类。
|
||||
* 缺省情况下,ClassWriter将会在getCommonSuperClass方法中计算这些,通过在加载这两个类进入虚拟机时,使用反射API来计算。
|
||||
* 但是,如果你将要生成的几个类相互之间引用,这将会带来问题,因为引用的类可能还不存在。
|
||||
* 在这种情况下,你可以重写getCommonSuperClass方法来解决这个问题。
|
||||
*
|
||||
* 通过重写 getCommonSuperClass() 方法,更正获取ClassLoader的方式,改成使用指定ClassLoader的方式进行。
|
||||
* 规避了原有代码采用Object.class.getClassLoader()的方式
|
||||
*/
|
||||
@Override
|
||||
protected String getCommonSuperClass(String type1, String type2) {
|
||||
Class<?> c, d;
|
||||
final ClassLoader classLoader = inClassLoader;
|
||||
try {
|
||||
c = Class.forName(type1.replace('/', '.'), false, classLoader);
|
||||
d = Class.forName(type2.replace('/', '.'), false, classLoader);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (c.isAssignableFrom(d)) {
|
||||
return type1;
|
||||
}
|
||||
if (d.isAssignableFrom(c)) {
|
||||
return type2;
|
||||
}
|
||||
if (c.isInterface() || d.isInterface()) {
|
||||
return "java/lang/Object";
|
||||
} else {
|
||||
do {
|
||||
c = c.getSuperclass();
|
||||
} while (!c.isAssignableFrom(d));
|
||||
return c.getName().replace('.', '/');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
// 生成增强字节码
|
||||
cr.accept(new AdviceWeaver(adviceId, isTracing, skipJDKTrace, cr.getClassName(), methodNameMatcher, affect, cw), EXPAND_FRAMES);
|
||||
final byte[] enhanceClassByteArray = cw.toByteArray();
|
||||
|
||||
// 生成成功,推入缓存
|
||||
classBytesCache.put(classBeingRedefined, enhanceClassByteArray);
|
||||
|
||||
// dump the class
|
||||
dumpClassIfNecessary(className, enhanceClassByteArray, affect);
|
||||
|
||||
// 成功计数
|
||||
affect.cCnt(1);
|
||||
|
||||
// 排遣间谍
|
||||
try {
|
||||
spy(inClassLoader);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("print spy failed. classname={};loader={};", className, inClassLoader, t);
|
||||
throw t;
|
||||
}
|
||||
|
||||
return enhanceClassByteArray;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* dump class to file
|
||||
*/
|
||||
private static void dumpClassIfNecessary(String className, byte[] data, EnhancerAffect affect) {
|
||||
if (!GlobalOptions.isDump) {
|
||||
return;
|
||||
}
|
||||
final File dumpClassFile = new File("./arthas-class-dump/" + className + ".class");
|
||||
final File classPath = new File(dumpClassFile.getParent());
|
||||
|
||||
// 创建类所在的包路径
|
||||
if (!classPath.mkdirs()
|
||||
&& !classPath.exists()) {
|
||||
logger.warn("create dump classpath:{} failed.", classPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 将类字节码写入文件
|
||||
try {
|
||||
FileUtils.writeByteArrayToFile(dumpClassFile, data);
|
||||
affect.getClassDumpFiles().add(dumpClassFile);
|
||||
} catch (IOException e) {
|
||||
logger.warn("dump class:{} to file {} failed.", className, dumpClassFile, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否需要过滤的类
|
||||
*
|
||||
* @param classes 类集合
|
||||
*/
|
||||
private static void filter(Set<Class<?>> classes) {
|
||||
final Iterator<Class<?>> it = classes.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Class<?> clazz = it.next();
|
||||
if (null == clazz
|
||||
|| isSelf(clazz)
|
||||
|| isUnsafeClass(clazz)
|
||||
|| isUnsupportedClass(clazz)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤Arthas加载的类
|
||||
*/
|
||||
private static boolean isSelf(Class<?> clazz) {
|
||||
return null != clazz
|
||||
&& isEquals(clazz.getClassLoader(), Enhancer.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤unsafe类
|
||||
*/
|
||||
private static boolean isUnsafeClass(Class<?> clazz) {
|
||||
return !GlobalOptions.isUnsafe
|
||||
&& clazz.getClassLoader() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤目前暂不支持的类
|
||||
*/
|
||||
private static boolean isUnsupportedClass(Class<?> clazz) {
|
||||
|
||||
return clazz.isArray()
|
||||
|| clazz.isInterface()
|
||||
|| clazz.isEnum()
|
||||
|| clazz.equals(Class.class) || clazz.equals(Integer.class) || clazz.equals(Method.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象增强
|
||||
*
|
||||
* @param inst inst
|
||||
* @param adviceId 通知ID
|
||||
* @param isTracing 可跟踪方法调用
|
||||
* @param classNameMatcher 类名匹配
|
||||
* @param methodNameMatcher 方法名匹配
|
||||
* @return 增强影响范围
|
||||
* @throws UnmodifiableClassException 增强失败
|
||||
*/
|
||||
public static synchronized EnhancerAffect enhance(
|
||||
final Instrumentation inst,
|
||||
final int adviceId,
|
||||
final boolean isTracing,
|
||||
final boolean skipJDKTrace,
|
||||
final Matcher classNameMatcher,
|
||||
final Matcher methodNameMatcher) throws UnmodifiableClassException {
|
||||
|
||||
final EnhancerAffect affect = new EnhancerAffect();
|
||||
|
||||
// 获取需要增强的类集合
|
||||
final Set<Class<?>> enhanceClassSet = GlobalOptions.isDisableSubClass
|
||||
? SearchUtils.searchClass(inst, classNameMatcher)
|
||||
: SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher));
|
||||
|
||||
// 过滤掉无法被增强的类
|
||||
filter(enhanceClassSet);
|
||||
|
||||
// 构建增强器
|
||||
final Enhancer enhancer = new Enhancer(adviceId, isTracing, skipJDKTrace, enhanceClassSet, methodNameMatcher, affect);
|
||||
try {
|
||||
inst.addTransformer(enhancer, true);
|
||||
|
||||
// 批量增强
|
||||
if (GlobalOptions.isBatchReTransform) {
|
||||
final int size = enhanceClassSet.size();
|
||||
final Class<?>[] classArray = new Class<?>[size];
|
||||
arraycopy(enhanceClassSet.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) {
|
||||
try {
|
||||
inst.retransformClasses(clazz);
|
||||
logger.info("Success to transform class: " + clazz);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("retransform {} failed.", clazz, t);
|
||||
if (t instanceof UnmodifiableClassException) {
|
||||
throw (UnmodifiableClassException) t;
|
||||
} else if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
} else {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
inst.removeTransformer(enhancer);
|
||||
}
|
||||
|
||||
return affect;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重置指定的Class
|
||||
*
|
||||
* @param inst inst
|
||||
* @param classNameMatcher 类名匹配
|
||||
* @return 增强影响范围
|
||||
* @throws UnmodifiableClassException
|
||||
*/
|
||||
public static synchronized EnhancerAffect reset(
|
||||
final Instrumentation inst,
|
||||
final Matcher classNameMatcher) throws UnmodifiableClassException {
|
||||
|
||||
final EnhancerAffect affect = new EnhancerAffect();
|
||||
final Set<Class<?>> enhanceClassSet = new HashSet<Class<?>>();
|
||||
|
||||
for (Class<?> classInCache : classBytesCache.keySet()) {
|
||||
if (classNameMatcher.matching(classInCache.getName())) {
|
||||
enhanceClassSet.add(classInCache);
|
||||
}
|
||||
}
|
||||
|
||||
final ClassFileTransformer resetClassFileTransformer = new ClassFileTransformer() {
|
||||
@Override
|
||||
public byte[] transform(
|
||||
ClassLoader loader,
|
||||
String className,
|
||||
Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain,
|
||||
byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
enhance(inst, resetClassFileTransformer, enhanceClassSet);
|
||||
logger.info("Success to reset classes: " + enhanceClassSet);
|
||||
} finally {
|
||||
for (Class<?> resetClass : enhanceClassSet) {
|
||||
classBytesCache.remove(resetClass);
|
||||
affect.cCnt(1);
|
||||
}
|
||||
}
|
||||
|
||||
return affect;
|
||||
}
|
||||
|
||||
// 批量增强
|
||||
public static void enhance(Instrumentation inst, ClassFileTransformer transformer, Set<Class<?>> classes)
|
||||
throws UnmodifiableClassException {
|
||||
try {
|
||||
inst.addTransformer(transformer, true);
|
||||
int size = classes.size();
|
||||
Class<?>[] classArray = new Class<?>[size];
|
||||
arraycopy(classes.toArray(), 0, classArray, 0, size);
|
||||
if (classArray.length > 0) {
|
||||
inst.retransformClasses(classArray);
|
||||
}
|
||||
} finally {
|
||||
inst.removeTransformer(transformer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
/**
|
||||
* 方法调用跟踪<br/>
|
||||
* 当一个方法内部调用另外一个方法时,会出发此跟踪方法
|
||||
* Created by vlinux on 15/5/27.
|
||||
*/
|
||||
public interface InvokeTraceable {
|
||||
|
||||
/**
|
||||
* 调用之前跟踪
|
||||
*
|
||||
* @param tracingClassName 调用类名
|
||||
* @param tracingMethodName 调用方法名
|
||||
* @param tracingMethodDesc 调用方法描述
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
void invokeBeforeTracing(
|
||||
String tracingClassName,
|
||||
String tracingMethodName,
|
||||
String tracingMethodDesc) throws Throwable;
|
||||
|
||||
/**
|
||||
* 抛异常后跟踪
|
||||
*
|
||||
* @param tracingClassName 调用类名
|
||||
* @param tracingMethodName 调用方法名
|
||||
* @param tracingMethodDesc 调用方法描述
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
void invokeThrowTracing(
|
||||
String tracingClassName,
|
||||
String tracingMethodName,
|
||||
String tracingMethodDesc) throws Throwable;
|
||||
|
||||
|
||||
/**
|
||||
* 调用之后跟踪
|
||||
*
|
||||
* @param tracingClassName 调用类名
|
||||
* @param tracingMethodName 调用方法名
|
||||
* @param tracingMethodDesc 调用方法描述
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
void invokeAfterTracing(
|
||||
String tracingClassName,
|
||||
String tracingMethodName,
|
||||
String tracingMethodDesc) throws Throwable;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.command.express.ExpressFactory;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.ArthasCheckUtils;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 反射通知适配器<br/>
|
||||
* 通过反射拿到对应的Class/Method类,而不是原始的ClassName/MethodName
|
||||
* 当然性能开销要比普通监听器高许多
|
||||
*/
|
||||
public abstract class ReflectAdviceListenerAdapter implements AdviceListener {
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
// default no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// default no-op
|
||||
}
|
||||
|
||||
private ClassLoader toClassLoader(ClassLoader loader) {
|
||||
return null != loader
|
||||
? loader
|
||||
: AdviceListener.class.getClassLoader();
|
||||
}
|
||||
|
||||
private Class<?> toClass(ClassLoader loader, String className) throws ClassNotFoundException {
|
||||
return Class.forName(StringUtils.normalizeClassName(className), true, toClassLoader(loader));
|
||||
}
|
||||
|
||||
private ArthasMethod toMethod(ClassLoader loader, Class<?> clazz, String methodName, String methodDesc)
|
||||
throws ClassNotFoundException, NoSuchMethodException {
|
||||
final org.objectweb.asm.Type asmType = org.objectweb.asm.Type.getMethodType(methodDesc);
|
||||
|
||||
// to arg types
|
||||
final Class<?>[] argsClasses = new Class<?>[asmType.getArgumentTypes().length];
|
||||
for (int index = 0; index < argsClasses.length; index++) {
|
||||
// asm class descriptor to jvm class
|
||||
final Class<?> argumentClass;
|
||||
final Type argumentAsmType = asmType.getArgumentTypes()[index];
|
||||
switch (argumentAsmType.getSort()) {
|
||||
case Type.BOOLEAN: {
|
||||
argumentClass = boolean.class;
|
||||
break;
|
||||
}
|
||||
case Type.CHAR: {
|
||||
argumentClass = char.class;
|
||||
break;
|
||||
}
|
||||
case Type.BYTE: {
|
||||
argumentClass = byte.class;
|
||||
break;
|
||||
}
|
||||
case Type.SHORT: {
|
||||
argumentClass = short.class;
|
||||
break;
|
||||
}
|
||||
case Type.INT: {
|
||||
argumentClass = int.class;
|
||||
break;
|
||||
}
|
||||
case Type.FLOAT: {
|
||||
argumentClass = float.class;
|
||||
break;
|
||||
}
|
||||
case Type.LONG: {
|
||||
argumentClass = long.class;
|
||||
break;
|
||||
}
|
||||
case Type.DOUBLE: {
|
||||
argumentClass = double.class;
|
||||
break;
|
||||
}
|
||||
case Type.ARRAY: {
|
||||
argumentClass = toClass(loader, argumentAsmType.getInternalName());
|
||||
break;
|
||||
}
|
||||
case Type.VOID: {
|
||||
argumentClass = void.class;
|
||||
break;
|
||||
}
|
||||
case Type.OBJECT:
|
||||
case Type.METHOD:
|
||||
default: {
|
||||
argumentClass = toClass(loader, argumentAsmType.getClassName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
argsClasses[index] = argumentClass;
|
||||
}
|
||||
|
||||
// to method or constructor
|
||||
if (ArthasCheckUtils.isEquals(methodName, "<init>")) {
|
||||
return ArthasMethod.newInit(toConstructor(clazz, argsClasses));
|
||||
} else {
|
||||
return ArthasMethod.newMethod(toMethod(clazz, methodName, argsClasses));
|
||||
}
|
||||
}
|
||||
|
||||
private Method toMethod(Class<?> clazz, String methodName, Class<?>[] argClasses) throws NoSuchMethodException {
|
||||
return clazz.getDeclaredMethod(methodName, argClasses);
|
||||
}
|
||||
|
||||
private Constructor<?> toConstructor(Class<?> clazz, Class<?>[] argClasses) throws NoSuchMethodException {
|
||||
return clazz.getDeclaredConstructor(argClasses);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
final public void before(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args) throws Throwable {
|
||||
final Class<?> clazz = toClass(loader, className);
|
||||
before(loader, clazz, toMethod(loader, clazz, methodName, methodDesc), target, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void afterReturning(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args, Object returnObject) throws Throwable {
|
||||
final Class<?> clazz = toClass(loader, className);
|
||||
afterReturning(loader, clazz, toMethod(loader, clazz, methodName, methodDesc), target, args, returnObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void afterThrowing(
|
||||
ClassLoader loader, String className, String methodName, String methodDesc,
|
||||
Object target, Object[] args, Throwable throwable) throws Throwable {
|
||||
final Class<?> clazz = toClass(loader, className);
|
||||
afterThrowing(loader, clazz, toMethod(loader, clazz, methodName, methodDesc), target, args, throwable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 前置通知
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param clazz 类
|
||||
* @param method 方法
|
||||
* @param target 目标类实例
|
||||
* 若目标为静态方法,则为null
|
||||
* @param args 参数列表
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
public abstract void before(
|
||||
ClassLoader loader, Class<?> clazz, ArthasMethod method,
|
||||
Object target, Object[] args) throws Throwable;
|
||||
|
||||
/**
|
||||
* 返回通知
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param clazz 类
|
||||
* @param method 方法
|
||||
* @param target 目标类实例
|
||||
* 若目标为静态方法,则为null
|
||||
* @param args 参数列表
|
||||
* @param returnObject 返回结果
|
||||
* 若为无返回值方法(void),则为null
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
public abstract void afterReturning(
|
||||
ClassLoader loader, Class<?> clazz, ArthasMethod method,
|
||||
Object target, Object[] args,
|
||||
Object returnObject) throws Throwable;
|
||||
|
||||
/**
|
||||
* 异常通知
|
||||
*
|
||||
* @param loader 类加载器
|
||||
* @param clazz 类
|
||||
* @param method 方法
|
||||
* @param target 目标类实例
|
||||
* 若目标为静态方法,则为null
|
||||
* @param args 参数列表
|
||||
* @param throwable 目标异常
|
||||
* @throws Throwable 通知过程出错
|
||||
*/
|
||||
public abstract void afterThrowing(
|
||||
ClassLoader loader, Class<?> clazz, ArthasMethod method,
|
||||
Object target, Object[] args,
|
||||
Throwable throwable) throws Throwable;
|
||||
|
||||
|
||||
/**
|
||||
* 判断条件是否满足,满足的情况下需要输出结果
|
||||
* @param conditionExpress 条件表达式
|
||||
* @param advice 当前的advice对象
|
||||
* @param cost 本次执行的耗时
|
||||
* @return true 如果条件表达式满足
|
||||
*/
|
||||
protected boolean isConditionMet(String conditionExpress, Advice advice, double cost) throws ExpressException {
|
||||
return StringUtils.isEmpty(conditionExpress) ||
|
||||
ExpressFactory.newExpress(advice).bind(Constants.COST_VARIABLE, cost).is(conditionExpress);
|
||||
}
|
||||
|
||||
protected Object getExpressionResult(String express, Advice advice, double cost) throws ExpressException {
|
||||
return ExpressFactory.newExpress(advice)
|
||||
.bind(Constants.COST_VARIABLE, cost).get(express);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否超过了上限,超过之后,停止输出
|
||||
* @param limit 命令执行上限
|
||||
* @param currentTimes 当前执行次数
|
||||
* @return true 如果超过或者达到了上限
|
||||
*/
|
||||
protected boolean isLimitExceeded(int limit, int currentTimes) {
|
||||
return currentTimes >= limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 超过次数上限,则不在输出,命令终止
|
||||
* @param process the process to be aborted
|
||||
* @param limit the limit to be printed
|
||||
*/
|
||||
protected void abortProcess(CommandProcess process, int limit) {
|
||||
process.write("Command execution times exceed limit: " + limit + ", so command will exit. You can set it with -n option.\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.taobao.arthas.core.advisor;
|
||||
|
||||
import org.objectweb.asm.commons.AdviceAdapter;
|
||||
|
||||
/**
|
||||
* 用于Tracing的代码锁
|
||||
* @author ralf0131 2016-12-28 16:46.
|
||||
*/
|
||||
public class TracingAsmCodeLock extends AsmCodeLock {
|
||||
|
||||
public TracingAsmCodeLock(AdviceAdapter aa) {
|
||||
super(
|
||||
aa,
|
||||
new int[]{
|
||||
ACONST_NULL, ICONST_0, ICONST_1, SWAP, SWAP, POP2, POP
|
||||
},
|
||||
new int[]{
|
||||
ICONST_1, ACONST_NULL, ICONST_0, SWAP, SWAP, POP, POP2
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.taobao.arthas.core.command;
|
||||
|
||||
import com.taobao.arthas.core.command.basic1000.ClsCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.HelpCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.KeymapCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.ResetCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.SessionCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.ShutdownCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.SystemPropertyCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.VersionCommand;
|
||||
import com.taobao.arthas.core.command.hidden.JulyCommand;
|
||||
import com.taobao.arthas.core.command.hidden.OptionsCommand;
|
||||
import com.taobao.arthas.core.command.hidden.ThanksCommand;
|
||||
import com.taobao.arthas.core.command.klass100.ClassLoaderCommand;
|
||||
import com.taobao.arthas.core.command.klass100.DumpClassCommand;
|
||||
import com.taobao.arthas.core.command.klass100.GetStaticCommand;
|
||||
import com.taobao.arthas.core.command.klass100.JadCommand;
|
||||
import com.taobao.arthas.core.command.klass100.RedefineCommand;
|
||||
import com.taobao.arthas.core.command.klass100.SearchClassCommand;
|
||||
import com.taobao.arthas.core.command.klass100.SearchMethodCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.DashboardCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.GroovyScriptCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.JvmCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.MonitorCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.StackCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.ThreadCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.TimeTunnelCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.TraceCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.WatchCommand;
|
||||
import com.taobao.arthas.core.shell.command.Command;
|
||||
import com.taobao.arthas.core.shell.command.CommandResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO automatically discover the built-in commands.
|
||||
* @author beiwei30 on 17/11/2016.
|
||||
*/
|
||||
public class BuiltinCommandPack implements CommandResolver {
|
||||
|
||||
private static List<Command> commands = new ArrayList<Command>();
|
||||
|
||||
static {
|
||||
initCommands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Command> commands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
private static void initCommands() {
|
||||
commands.add(Command.create(HelpCommand.class));
|
||||
commands.add(Command.create(KeymapCommand.class));
|
||||
commands.add(Command.create(SearchClassCommand.class));
|
||||
commands.add(Command.create(SearchMethodCommand.class));
|
||||
commands.add(Command.create(ClassLoaderCommand.class));
|
||||
commands.add(Command.create(JadCommand.class));
|
||||
commands.add(Command.create(GetStaticCommand.class));
|
||||
commands.add(Command.create(MonitorCommand.class));
|
||||
commands.add(Command.create(StackCommand.class));
|
||||
commands.add(Command.create(ThreadCommand.class));
|
||||
commands.add(Command.create(TraceCommand.class));
|
||||
commands.add(Command.create(WatchCommand.class));
|
||||
commands.add(Command.create(TimeTunnelCommand.class));
|
||||
commands.add(Command.create(JvmCommand.class));
|
||||
// commands.add(Command.create(GroovyScriptCommand.class));
|
||||
commands.add(Command.create(DashboardCommand.class));
|
||||
commands.add(Command.create(DumpClassCommand.class));
|
||||
commands.add(Command.create(JulyCommand.class));
|
||||
commands.add(Command.create(ThanksCommand.class));
|
||||
commands.add(Command.create(OptionsCommand.class));
|
||||
commands.add(Command.create(ClsCommand.class));
|
||||
commands.add(Command.create(ResetCommand.class));
|
||||
commands.add(Command.create(VersionCommand.class));
|
||||
commands.add(Command.create(ShutdownCommand.class));
|
||||
commands.add(Command.create(SessionCommand.class));
|
||||
commands.add(Command.create(SystemPropertyCommand.class));
|
||||
commands.add(Command.create(RedefineCommand.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.core.command;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2016-12-14 17:21.
|
||||
*/
|
||||
public interface Constants {
|
||||
|
||||
/**
|
||||
* TODO improve the description
|
||||
*/
|
||||
String EXPRESS_DESCRIPTION = " The express may be one of the following expression (evaluated dynamically):\n" +
|
||||
" target : the object\n" +
|
||||
" clazz : the object's class\n" +
|
||||
" method : the constructor or method\n" +
|
||||
" params[0..n] : the parameters of method\n" +
|
||||
" returnObj : the returned object of method\n" +
|
||||
" throwExp : the throw exception of method\n" +
|
||||
" isReturn : the method ended by return\n" +
|
||||
" isThrow : the method ended by throwing exception\n" +
|
||||
" #cost : the execution time in ms of method invocation";
|
||||
|
||||
String EXAMPLE = "\nEXAMPLES:\n";
|
||||
|
||||
String WIKI = "\nWIKI:\n";
|
||||
|
||||
String WIKI_HOME = " middleware-container/arthas/wikis/";
|
||||
|
||||
String EXPRESS_EXAMPLES = "Examples:\n" +
|
||||
" params[0]\n" +
|
||||
" 'params[0]+params[1]'\n" +
|
||||
" returnObj\n" +
|
||||
" throwExp\n" +
|
||||
" target\n" +
|
||||
" clazz\n" +
|
||||
" method\n";
|
||||
|
||||
String CONDITION_EXPRESS = "Conditional expression in ognl style, for example:\n" +
|
||||
" TRUE : 1==1\n" +
|
||||
" TRUE : true\n" +
|
||||
" FALSE : false\n" +
|
||||
" TRUE : 'params.length>=0'\n" +
|
||||
" FALSE : 1==2\n";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.taobao.arthas.core.command;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
|
||||
/**
|
||||
* 脚本支持命令
|
||||
* Created by vlinux on 15/6/1.
|
||||
*/
|
||||
public interface ScriptSupportCommand {
|
||||
|
||||
/**
|
||||
* 增强脚本监听器
|
||||
*/
|
||||
interface ScriptListener {
|
||||
|
||||
/**
|
||||
* 脚本创建
|
||||
*
|
||||
* @param output 输出器
|
||||
*/
|
||||
void create(Output output);
|
||||
|
||||
/**
|
||||
* 脚本销毁
|
||||
*
|
||||
* @param output 输出器
|
||||
*/
|
||||
void destroy(Output output);
|
||||
|
||||
/**
|
||||
* 方法执行前
|
||||
*
|
||||
* @param output 输出器
|
||||
* @param advice 通知点
|
||||
*/
|
||||
void before(Output output, Advice advice);
|
||||
|
||||
/**
|
||||
* 方法正常返回
|
||||
*
|
||||
* @param output 输出器
|
||||
* @param advice 通知点
|
||||
*/
|
||||
void afterReturning(Output output, Advice advice);
|
||||
|
||||
/**
|
||||
* 方法异常返回
|
||||
*
|
||||
* @param output 输出器
|
||||
* @param advice 通知点
|
||||
*/
|
||||
void afterThrowing(Output output, Advice advice);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 脚本监听器适配器
|
||||
*/
|
||||
class ScriptListenerAdapter implements ScriptListener {
|
||||
|
||||
@Override
|
||||
public void create(Output output) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(Output output) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(Output output, Advice advice) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(Output output, Advice advice) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(Output output, Advice advice) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 输出器
|
||||
*/
|
||||
interface Output {
|
||||
|
||||
/**
|
||||
* 输出字符串(不换行)
|
||||
*
|
||||
* @param string 待输出字符串
|
||||
* @return this
|
||||
*/
|
||||
Output print(String string);
|
||||
|
||||
/**
|
||||
* 输出字符串(换行)
|
||||
*
|
||||
* @param string 待输出字符串
|
||||
* @return this
|
||||
*/
|
||||
Output println(String string);
|
||||
|
||||
/**
|
||||
* 结束当前脚本
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
Output finish();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
@Name("cls")
|
||||
@Summary("Clear the screen")
|
||||
public class ClsCommand extends AnnotatedCommand {
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
process.write(RenderUtil.cls()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.Command;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.command.CommandResolver;
|
||||
import com.taobao.arthas.core.util.usage.StyledUsageFormatter;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
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.Summary;
|
||||
import com.taobao.text.Color;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.Style;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.LabelElement;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
import static com.taobao.text.ui.Element.row;
|
||||
|
||||
/**
|
||||
* @author vlinux on 14/10/26.
|
||||
*/
|
||||
@Name("help")
|
||||
@Summary("Display Arthas Help")
|
||||
@Description("Examples:\n" + " help\n" + " help sc\n" + " help sm\n" + " help watch")
|
||||
public class HelpCommand extends AnnotatedCommand {
|
||||
|
||||
private String cmd;
|
||||
|
||||
@Argument(index = 0, argName = "cmd", required = false)
|
||||
@Description("command name")
|
||||
public void setCmd(String cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
List<CommandResolver> commandResolvers = process.session().getCommandResolvers();
|
||||
List<Command> commands = new ArrayList<Command>();
|
||||
for (CommandResolver commandResolver : commandResolvers) {
|
||||
commands.addAll(commandResolver.commands());
|
||||
}
|
||||
|
||||
Command targetCmd = findCommand(commands);
|
||||
String message;
|
||||
if (targetCmd == null) {
|
||||
message = RenderUtil.render(mainHelp(commands), process.width());
|
||||
} else {
|
||||
message = commandHelp(targetCmd, process.width());
|
||||
}
|
||||
process.write(message);
|
||||
process.end();
|
||||
}
|
||||
|
||||
private static String commandHelp(Command command, int width) {
|
||||
return StyledUsageFormatter.styledUsage(command.cli(), width);
|
||||
}
|
||||
|
||||
private static Element mainHelp(List<Command> commands) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(new LabelElement("NAME").style(Style.style(Decoration.bold)), new LabelElement("DESCRIPTION"));
|
||||
for (Command command : commands) {
|
||||
CLI cli = command.cli();
|
||||
// com.taobao.arthas.core.shell.impl.BuiltinCommandResolver doesn't have CLI instance
|
||||
if (cli == null || cli.isHidden()) {
|
||||
continue;
|
||||
}
|
||||
table.add(row().add(label(cli.getName()).style(Style.style(Color.green))).add(label(cli.getSummary())));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private Command findCommand(List<Command> commands) {
|
||||
for (Command command : commands) {
|
||||
if (command.name().equals(cmd)) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.term.impl.Helper;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* A command to display all the keymap for the specified connection.
|
||||
* @author ralf0131 2016-12-15 17:27.
|
||||
*/
|
||||
@Name("keymap")
|
||||
@Summary("Display all the available keymap for the specified connection.")
|
||||
public class KeymapCommand extends AnnotatedCommand {
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
InputStream inputrc = Helper.loadInputRcFile();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(inputrc));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
try {
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith("#") || "".equals(line.trim())) {
|
||||
continue;
|
||||
}
|
||||
sb.append(line + "\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append(e.getMessage());
|
||||
}
|
||||
process.write(sb.toString());
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Enhancer;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
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 java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
|
||||
/**
|
||||
* 恢复所有增强类<br/>
|
||||
*
|
||||
* @author vlinux on 15/5/29.
|
||||
*/
|
||||
@Name("reset")
|
||||
@Summary("Reset all the enhanced classes")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" reset\n" +
|
||||
" reset *List\n" +
|
||||
" reset -E .*List\n")
|
||||
public class ResetCommand extends AnnotatedCommand {
|
||||
private String classPattern;
|
||||
private boolean isRegEx = false;
|
||||
|
||||
@Argument(index = 0, argName = "class-pattern", required = false)
|
||||
@Description("Path and classname of Pattern Matching")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
Matcher matcher = SearchUtils.classNameMatcher(classPattern, isRegEx);
|
||||
EnhancerAffect enhancerAffect = null;
|
||||
try {
|
||||
enhancerAffect = Enhancer.reset(inst, matcher);
|
||||
process.write(enhancerAffect.toString()).write("\n");
|
||||
} catch (UnmodifiableClassException e) {
|
||||
// ignore
|
||||
} finally {
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* 查看会话状态命令
|
||||
*
|
||||
* @author vlinux on 15/5/3.
|
||||
*/
|
||||
@Name("session")
|
||||
@Summary("Display current session information")
|
||||
public class SessionCommand extends AnnotatedCommand {
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
process.write(RenderUtil.render(sessionTable(process.session()), process.width())).end();
|
||||
}
|
||||
|
||||
/*
|
||||
* 会话详情
|
||||
*/
|
||||
private Element sessionTable(Session session) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(true, label("Name").style(Decoration.bold.bold()), label("Value").style(Decoration.bold.bold()));
|
||||
table.row("JAVA_PID", "" + session.getPid()).row("SESSION_ID", "" + session.getSessionId());
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Enhancer;
|
||||
import com.taobao.arthas.core.shell.ShellServer;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
import com.taobao.arthas.core.util.matcher.WildcardMatcher;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
|
||||
/**
|
||||
* 关闭命令
|
||||
*
|
||||
* @author vlinux on 14/10/23.
|
||||
*/
|
||||
@Name("shutdown")
|
||||
@Summary("Shut down Arthas server and exit the console")
|
||||
public class ShutdownCommand extends AnnotatedCommand {
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
try {
|
||||
// 退出之前需要重置所有的增强类
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
EnhancerAffect enhancerAffect = Enhancer.reset(inst, new WildcardMatcher("*"));
|
||||
process.write(enhancerAffect.toString()).write("\n");
|
||||
process.write("Arthas Server is going to shut down...\n");
|
||||
} catch (UnmodifiableClassException e) {
|
||||
// ignore
|
||||
} finally {
|
||||
process.end();
|
||||
ShellServer server = process.session().getServer();
|
||||
server.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
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.StringUtils;
|
||||
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.Summary;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* A command to display all the keymap for the specified connection.
|
||||
* @author ralf0131 2017-01-09 14:03.
|
||||
*/
|
||||
@Name("sysprop")
|
||||
@Summary("Display, and change the system properties.")
|
||||
@Description(Constants.EXAMPLE + "sysprop\n"+ "sysprop file.encoding\n" + "sysprop production.mode true\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/sysprop")
|
||||
public class SystemPropertyCommand extends AnnotatedCommand {
|
||||
|
||||
private String propertyName;
|
||||
private String propertyValue;
|
||||
|
||||
@Argument(index = 0, argName = "property-name", required = false)
|
||||
@Description("property name")
|
||||
public void setOptionName(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
@Argument(index = 1, argName = "property-value", required = false)
|
||||
@Description("property value")
|
||||
public void setOptionValue(String propertyValue) {
|
||||
this.propertyValue = propertyValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
try {
|
||||
if (StringUtils.isBlank(propertyName) && StringUtils.isBlank(propertyValue)) {
|
||||
// show all system properties
|
||||
process.write(renderSystemProperties(System.getProperties(), process.width()));
|
||||
} else if (StringUtils.isBlank(propertyValue)) {
|
||||
// view the specified system property
|
||||
String value = System.getProperty(propertyName);
|
||||
if (value == null) {
|
||||
process.write("In order to change the system properties, you must specify the property value.\n");
|
||||
} else {
|
||||
process.write(propertyName + "=" + value + "\n");
|
||||
}
|
||||
} else {
|
||||
// change system property
|
||||
System.setProperty(propertyName, propertyValue);
|
||||
process.write("Successfully changed the system property.\n");
|
||||
process.write(propertyName + "=" + System.getProperty(propertyName) + "\n");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
process.write("Error during setting system property: " + t.getMessage() + "\n");
|
||||
} finally {
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* First, try to complete with the sysprop command scope.
|
||||
* If completion is failed, delegates to super class.
|
||||
* @param completion the completion object
|
||||
*/
|
||||
@Override
|
||||
public void complete(Completion completion) {
|
||||
CompletionUtils.complete(completion, System.getProperties().stringPropertyNames());
|
||||
}
|
||||
|
||||
private String renderSystemProperties(Properties properties, int width) {
|
||||
TableElement table = new TableElement(1, 4).leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(true, label("KEY").style(Decoration.bold.bold()),
|
||||
label("VALUE").style(Decoration.bold.bold()));
|
||||
|
||||
for (String name: properties.stringPropertyNames()) {
|
||||
table.row(name, properties.getProperty(name));
|
||||
}
|
||||
|
||||
return RenderUtil.render(table, width);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.ArthasBanner;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
/**
|
||||
* 输出版本
|
||||
*
|
||||
* @author vlinux
|
||||
*/
|
||||
@Name("version")
|
||||
@Summary("Display Arthas version")
|
||||
public class VersionCommand extends AnnotatedCommand {
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
process.write(ArthasBanner.version()).write("\n").end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import ognl.ClassResolver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author diecui1202 on 2017/9/29.
|
||||
*/
|
||||
public class CustomClassResolver implements ClassResolver {
|
||||
|
||||
public static final CustomClassResolver customClassResolver = new CustomClassResolver();
|
||||
|
||||
private static final ThreadLocal<ClassLoader> classLoader = new ThreadLocal<ClassLoader>();
|
||||
|
||||
private Map classes = new HashMap(101);
|
||||
|
||||
private CustomClassResolver() {
|
||||
|
||||
}
|
||||
|
||||
public Class classForName(String className, Map context) throws ClassNotFoundException {
|
||||
Class result = null;
|
||||
|
||||
if ((result = (Class) classes.get(className)) == null) {
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader != null) {
|
||||
result = classLoader.loadClass(className);
|
||||
} else {
|
||||
result = Class.forName(className);
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
if (className.indexOf('.') == -1) {
|
||||
result = Class.forName("java.lang." + className);
|
||||
classes.put("java.lang." + className, result);
|
||||
}
|
||||
}
|
||||
classes.put(className, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
/**
|
||||
* 表达式
|
||||
* Created by vlinux on 15/5/20.
|
||||
*/
|
||||
public interface Express {
|
||||
|
||||
/**
|
||||
* 根据表达式获取值
|
||||
*
|
||||
* @param express 表达式
|
||||
* @return 表达式运算后的值
|
||||
* @throws ExpressException 表达式运算出错
|
||||
*/
|
||||
Object get(String express) throws ExpressException;
|
||||
|
||||
/**
|
||||
* 根据表达式判断是与否
|
||||
*
|
||||
* @param express 表达式
|
||||
* @return 表达式运算后的布尔值
|
||||
* @throws ExpressException 表达式运算出错
|
||||
*/
|
||||
boolean is(String express) throws ExpressException;
|
||||
|
||||
/**
|
||||
* 绑定对象
|
||||
*
|
||||
* @param object 待绑定对象
|
||||
* @return this
|
||||
*/
|
||||
Express bind(Object object);
|
||||
|
||||
/**
|
||||
* 绑定变量
|
||||
*
|
||||
* @param name 变量名
|
||||
* @param value 变量值
|
||||
* @return this
|
||||
*/
|
||||
Express bind(String name, Object value);
|
||||
|
||||
/**
|
||||
* 重置整个表达式
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
Express reset();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
/**
|
||||
* 表达式异常
|
||||
* Created by vlinux on 15/5/20.
|
||||
*/
|
||||
public class ExpressException extends Exception {
|
||||
|
||||
private final String express;
|
||||
|
||||
/**
|
||||
* 表达式异常
|
||||
*
|
||||
* @param express 原始表达式
|
||||
* @param cause 异常原因
|
||||
*/
|
||||
public ExpressException(String express, Throwable cause) {
|
||||
super(cause);
|
||||
this.express = express;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表达式
|
||||
*
|
||||
* @return 返回出问题的表达式
|
||||
*/
|
||||
public String getExpress() {
|
||||
return express;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
/**
|
||||
* 表达式工厂类
|
||||
* @author ralf0131 2017-01-04 14:40.
|
||||
*/
|
||||
public class ExpressFactory {
|
||||
|
||||
private static final ThreadLocal<Express> expressRef = new ThreadLocal<Express>() {
|
||||
@Override
|
||||
protected Express initialValue() {
|
||||
return new OgnlExpress();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 构造表达式执行类
|
||||
*
|
||||
* @param object 执行对象
|
||||
* @return 返回表达式实现
|
||||
*/
|
||||
public static Express newExpress(Object object) {
|
||||
return expressRef.get().reset().bind(object);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
import ognl.DefaultMemberAccess;
|
||||
import ognl.Ognl;
|
||||
import ognl.OgnlContext;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-01-04 14:41.
|
||||
*/
|
||||
public class OgnlExpress implements Express {
|
||||
|
||||
Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private Object bindObject;
|
||||
private final OgnlContext context;
|
||||
|
||||
public OgnlExpress() {
|
||||
context = new OgnlContext();
|
||||
context.setClassResolver(CustomClassResolver.customClassResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String express) throws ExpressException {
|
||||
try {
|
||||
context.setMemberAccess(new DefaultMemberAccess(true));
|
||||
return Ognl.getValue(express, context, bindObject);
|
||||
} catch (Exception e) {
|
||||
logger.error(null, "Error during evaluating the expression:", e);
|
||||
throw new ExpressException(express, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean is(String express) throws ExpressException {
|
||||
final Object ret = get(express);
|
||||
return null != ret && ret instanceof Boolean && (Boolean) ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Express bind(Object object) {
|
||||
this.bindObject = object;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Express bind(String name, Object value) {
|
||||
context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Express reset() {
|
||||
context.clear();
|
||||
context.setClassResolver(CustomClassResolver.customClassResolver);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.taobao.arthas.core.command.hidden;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.middleware.cli.annotations.Hidden;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
/**
|
||||
* @author vlinux on 02/11/2016.
|
||||
*/
|
||||
@Name("july")
|
||||
@Summary("don't ask why")
|
||||
@Hidden
|
||||
public class JulyCommand extends AnnotatedCommand {
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
process.write(new String($$())).write("\n").end();
|
||||
}
|
||||
|
||||
private static byte[] $$() {
|
||||
return new byte[]{
|
||||
0x49, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65,
|
||||
0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x69, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74,
|
||||
0x20, 0x79, 0x6f, 0x75, 0x20, 0x64, 0x69, 0x64, 0x0a, 0x49, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74,
|
||||
0x20, 0x6c, 0x65, 0x74, 0x20, 0x6d, 0x79, 0x73, 0x65, 0x6c, 0x66, 0x0a, 0x43, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6d,
|
||||
0x79, 0x20, 0x68, 0x65, 0x61, 0x72, 0x74, 0x20, 0x73, 0x6f, 0x20, 0x6d, 0x75, 0x63, 0x68, 0x20, 0x6d, 0x69, 0x73,
|
||||
0x65, 0x72, 0x79, 0x0a, 0x49, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x72, 0x65, 0x61,
|
||||
0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x61, 0x79, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x64, 0x69, 0x64, 0x0a, 0x59,
|
||||
0x6f, 0x75, 0x20, 0x66, 0x65, 0x6c, 0x6c, 0x20, 0x73, 0x6f, 0x20, 0x68, 0x61, 0x72, 0x64, 0x0a, 0x0a, 0x49, 0x20,
|
||||
0x76, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x72, 0x64,
|
||||
0x20, 0x77, 0x61, 0x79, 0x0a, 0x54, 0x6f, 0x20, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x69,
|
||||
0x74, 0x20, 0x67, 0x65, 0x74, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x66, 0x61, 0x72, 0x0a, 0x0a, 0x42, 0x65, 0x63,
|
||||
0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x6e, 0x65, 0x76, 0x65, 0x72,
|
||||
0x20, 0x73, 0x74, 0x72, 0x61, 0x79, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x66, 0x61, 0x72, 0x20, 0x66, 0x72, 0x6f, 0x6d,
|
||||
0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x64, 0x65, 0x77, 0x61, 0x6c, 0x6b, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75,
|
||||
0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x6c, 0x65, 0x61, 0x72, 0x6e, 0x65, 0x64,
|
||||
0x20, 0x74, 0x6f, 0x20, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x66,
|
||||
0x65, 0x20, 0x73, 0x69, 0x64, 0x65, 0x20, 0x73, 0x6f, 0x20, 0x49, 0x20, 0x64, 0x6f, 0x6e, 0x20, 0x74, 0x20, 0x67,
|
||||
0x65, 0x74, 0x20, 0x68, 0x75, 0x72, 0x74, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20,
|
||||
0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x68, 0x61, 0x72, 0x64, 0x20,
|
||||
0x74, 0x6f, 0x20, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6d,
|
||||
0x65, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x6f, 0x6e, 0x65, 0x20, 0x61, 0x72, 0x6f,
|
||||
0x75, 0x6e, 0x64, 0x20, 0x6d, 0x65, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79,
|
||||
0x6f, 0x75, 0x0a, 0x49, 0x20, 0x61, 0x6d, 0x20, 0x61, 0x66, 0x72, 0x61, 0x69, 0x64, 0x0a, 0x0a, 0x49, 0x20, 0x6c,
|
||||
0x6f, 0x73, 0x65, 0x20, 0x6d, 0x79, 0x20, 0x77, 0x61, 0x79, 0x0a, 0x41, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x73,
|
||||
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72,
|
||||
0x65, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x74, 0x20, 0x6f, 0x75, 0x74, 0x0a,
|
||||
0x49, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x72, 0x79, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73,
|
||||
0x65, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6b, 0x6e, 0x6f, 0x77, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x73, 0x20, 0x77,
|
||||
0x65, 0x61, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x65, 0x79, 0x65,
|
||||
0x73, 0x0a, 0x49, 0x20, 0x6d, 0x20, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6b,
|
||||
0x65, 0x0a, 0x41, 0x20, 0x73, 0x6d, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x61, 0x20, 0x6c, 0x61, 0x75, 0x67, 0x68, 0x20,
|
||||
0x65, 0x76, 0x65, 0x72, 0x79, 0x64, 0x61, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x79, 0x20, 0x6c, 0x69, 0x66, 0x65,
|
||||
0x0a, 0x4d, 0x79, 0x20, 0x68, 0x65, 0x61, 0x72, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x27, 0x74, 0x20, 0x70, 0x6f, 0x73,
|
||||
0x73, 0x69, 0x62, 0x6c, 0x79, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x0a, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x74,
|
||||
0x20, 0x77, 0x61, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x77, 0x68, 0x6f, 0x6c, 0x65, 0x20,
|
||||
0x74, 0x6f, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x0a, 0x0a, 0x42, 0x65, 0x63, 0x61,
|
||||
0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20,
|
||||
0x73, 0x74, 0x72, 0x61, 0x79, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x66, 0x61, 0x72, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
|
||||
0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x64, 0x65, 0x77, 0x61, 0x6c, 0x6b, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73,
|
||||
0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x6c, 0x65, 0x61, 0x72, 0x6e, 0x65, 0x64, 0x20,
|
||||
0x74, 0x6f, 0x20, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x66, 0x65,
|
||||
0x20, 0x73, 0x69, 0x64, 0x65, 0x20, 0x73, 0x6f, 0x20, 0x49, 0x20, 0x64, 0x6f, 0x6e, 0x20, 0x74, 0x20, 0x67, 0x65,
|
||||
0x74, 0x20, 0x68, 0x75, 0x72, 0x74, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79,
|
||||
0x6f, 0x75, 0x0a, 0x49, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x68, 0x61, 0x72, 0x64, 0x20, 0x74,
|
||||
0x6f, 0x20, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6d, 0x65,
|
||||
0x2c, 0x20, 0x62, 0x75, 0x74, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x6f, 0x6e, 0x65, 0x20, 0x61, 0x72, 0x6f, 0x75,
|
||||
0x6e, 0x64, 0x20, 0x6d, 0x65, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f,
|
||||
0x75, 0x0a, 0x49, 0x20, 0x61, 0x6d, 0x20, 0x61, 0x66, 0x72, 0x61, 0x69, 0x64, 0x0a, 0x0a, 0x49, 0x20, 0x77, 0x61,
|
||||
0x74, 0x63, 0x68, 0x65, 0x64, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x64, 0x69, 0x65, 0x0a, 0x49, 0x20, 0x68, 0x65, 0x61,
|
||||
0x72, 0x64, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x72, 0x79, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6e, 0x69,
|
||||
0x67, 0x68, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x0a, 0x49,
|
||||
0x20, 0x77, 0x61, 0x73, 0x20, 0x73, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x6e, 0x67, 0x0a, 0x59, 0x6f, 0x75, 0x20, 0x73,
|
||||
0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x62, 0x65,
|
||||
0x74, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x61, 0x6e, 0x20, 0x6f,
|
||||
0x6e, 0x20, 0x6d, 0x65, 0x0a, 0x59, 0x6f, 0x75, 0x20, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x6f, 0x75,
|
||||
0x67, 0x68, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x79, 0x6f, 0x6e, 0x65, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a,
|
||||
0x59, 0x6f, 0x75, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x73, 0x61, 0x77, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x70,
|
||||
0x61, 0x69, 0x6e, 0x0a, 0x41, 0x6e, 0x64, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x49, 0x20, 0x63, 0x72, 0x79, 0x20, 0x69,
|
||||
0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65,
|
||||
0x20, 0x6e, 0x69, 0x67, 0x68, 0x74, 0x0a, 0x46, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65,
|
||||
0x20, 0x64, 0x61, 0x6d, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73,
|
||||
0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20, 0x73, 0x74,
|
||||
0x72, 0x61, 0x79, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x66, 0x61, 0x72, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68,
|
||||
0x65, 0x20, 0x73, 0x69, 0x64, 0x65, 0x77, 0x61, 0x6c, 0x6b, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20,
|
||||
0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x6c, 0x65, 0x61, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f,
|
||||
0x20, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x66, 0x65, 0x20, 0x73,
|
||||
0x69, 0x64, 0x65, 0x20, 0x73, 0x6f, 0x20, 0x49, 0x20, 0x64, 0x6f, 0x6e, 0x20, 0x74, 0x20, 0x67, 0x65, 0x74, 0x20,
|
||||
0x68, 0x75, 0x72, 0x74, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75,
|
||||
0x0a, 0x49, 0x20, 0x74, 0x72, 0x79, 0x20, 0x6d, 0x79, 0x20, 0x68, 0x61, 0x72, 0x64, 0x65, 0x73, 0x74, 0x20, 0x6a,
|
||||
0x75, 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79,
|
||||
0x74, 0x68, 0x69, 0x6e, 0x67, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f,
|
||||
0x75, 0x0a, 0x49, 0x20, 0x64, 0x6f, 0x6e, 0x20, 0x74, 0x20, 0x6b, 0x6e, 0x6f, 0x77, 0x20, 0x68, 0x6f, 0x77, 0x20,
|
||||
0x74, 0x6f, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x79, 0x6f, 0x6e, 0x65, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20,
|
||||
0x69, 0x6e, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49,
|
||||
0x20, 0x6d, 0x20, 0x61, 0x73, 0x68, 0x61, 0x6d, 0x65, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x79, 0x20, 0x6c, 0x69,
|
||||
0x66, 0x65, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x69, 0x74, 0x20, 0x73, 0x20, 0x65, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x49,
|
||||
0x20, 0x61, 0x6d, 0x20, 0x61, 0x66, 0x72, 0x61, 0x69, 0x64, 0x0a, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65,
|
||||
0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x0a, 0x42, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20,
|
||||
0x79, 0x6f, 0x75, 0x0a, 0x2e, 0x2e, 0x2e, 0x0a, /*0x0a, 0x66, 0x6f, 0x72, 0x20, 0x6a, 0x75, 0x6c, 0x79, 0x0a, 0x0a,*/
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package com.taobao.arthas.core.command.hidden;
|
||||
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.Option;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.matcher.EqualsMatcher;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.util.matcher.RegexMatcher;
|
||||
import com.taobao.arthas.core.util.reflect.FieldUtils;
|
||||
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.Summary;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.taobao.arthas.core.util.ArthasCheckUtils.isIn;
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* 选项开关命令
|
||||
*
|
||||
* @author vlinux on 15/6/6.
|
||||
*/
|
||||
@Name("options")
|
||||
@Summary("View and change various Arthas options")
|
||||
@Description(Constants.EXAMPLE + "options dump true\n"+ "options unsafe true\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/options")
|
||||
public class OptionsCommand extends AnnotatedCommand {
|
||||
private String optionName;
|
||||
private String optionValue;
|
||||
|
||||
@Argument(index = 0, argName = "options-name", required = false)
|
||||
@Description("Option name")
|
||||
public void setOptionName(String optionName) {
|
||||
this.optionName = optionName;
|
||||
}
|
||||
|
||||
@Argument(index = 1, argName = "options-value", required = false)
|
||||
@Description("Option value")
|
||||
public void setOptionValue(String optionValue) {
|
||||
this.optionValue = optionValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
try {
|
||||
if (isShow()) {
|
||||
processShow(process);
|
||||
} else if (isShowName()) {
|
||||
processShowName(process);
|
||||
} else {
|
||||
processChangeNameValue(process);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
} finally {
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void processShow(CommandProcess process) throws IllegalAccessException {
|
||||
Collection<Field> fields = findOptions(new RegexMatcher(".*"));
|
||||
process.write(RenderUtil.render(drawShowTable(fields), process.width()));
|
||||
}
|
||||
|
||||
private void processShowName(CommandProcess process) throws IllegalAccessException {
|
||||
Collection<Field> fields = findOptions(new EqualsMatcher<String>(optionName));
|
||||
process.write(RenderUtil.render(drawShowTable(fields), process.width()));
|
||||
}
|
||||
|
||||
private void processChangeNameValue(CommandProcess process) throws IllegalAccessException {
|
||||
Collection<Field> fields = findOptions(new EqualsMatcher<String>(optionName));
|
||||
|
||||
// name not exists
|
||||
if (fields.isEmpty()) {
|
||||
process.write(format("options[%s] not found.\n", optionName));
|
||||
return;
|
||||
}
|
||||
|
||||
Field field = fields.iterator().next();
|
||||
Option optionAnnotation = field.getAnnotation(Option.class);
|
||||
Class<?> type = field.getType();
|
||||
Object beforeValue = FieldUtils.readStaticField(field);
|
||||
Object afterValue;
|
||||
|
||||
try {
|
||||
// try to case string to type
|
||||
if (isIn(type, int.class, Integer.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Integer.valueOf(optionValue));
|
||||
} else if (isIn(type, long.class, Long.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Long.valueOf(optionValue));
|
||||
} else if (isIn(type, boolean.class, Boolean.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Boolean.valueOf(optionValue));
|
||||
} else if (isIn(type, double.class, Double.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Double.valueOf(optionValue));
|
||||
} else if (isIn(type, float.class, Float.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Float.valueOf(optionValue));
|
||||
} else if (isIn(type, byte.class, Byte.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Byte.valueOf(optionValue));
|
||||
} else if (isIn(type, short.class, Short.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = Short.valueOf(optionValue));
|
||||
} else if (isIn(type, short.class, String.class)) {
|
||||
FieldUtils.writeStaticField(field, afterValue = optionValue);
|
||||
} else {
|
||||
process.write(format("Options[%s] type[%s] desupported.\n", optionName, type.getSimpleName()));
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
process.write(format("Cannot cast option value[%s] to type[%s].\n", optionValue, type.getSimpleName()));
|
||||
return;
|
||||
}
|
||||
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(true, label("NAME").style(Decoration.bold.bold()),
|
||||
label("BEFORE-VALUE").style(Decoration.bold.bold()),
|
||||
label("AFTER-VALUE").style(Decoration.bold.bold()));
|
||||
table.row(optionAnnotation.name(), StringUtils.objectToString(beforeValue),
|
||||
StringUtils.objectToString(afterValue));
|
||||
process.write(RenderUtil.render(table, process.width()));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 判断当前动作是否需要展示整个options
|
||||
*/
|
||||
private boolean isShow() {
|
||||
return StringUtils.isBlank(optionName) && StringUtils.isBlank(optionValue);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 判断当前动作是否需要展示某个Name的值
|
||||
*/
|
||||
private boolean isShowName() {
|
||||
return !StringUtils.isBlank(optionName) && StringUtils.isBlank(optionValue);
|
||||
}
|
||||
|
||||
private Collection<Field> findOptions(Matcher optionNameMatcher) {
|
||||
final Collection<Field> matchFields = new ArrayList<Field>();
|
||||
for (final Field optionField : FieldUtils.getAllFields(GlobalOptions.class)) {
|
||||
if (!optionField.isAnnotationPresent(Option.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Option optionAnnotation = optionField.getAnnotation(Option.class);
|
||||
if (optionAnnotation != null
|
||||
&& !optionNameMatcher.matching(optionAnnotation.name())) {
|
||||
continue;
|
||||
}
|
||||
matchFields.add(optionField);
|
||||
}
|
||||
return matchFields;
|
||||
}
|
||||
|
||||
private Element drawShowTable(Collection<Field> optionFields) throws IllegalAccessException {
|
||||
TableElement table = new TableElement(1, 1, 2, 1, 3, 6)
|
||||
.leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(true, label("LEVEL").style(Decoration.bold.bold()),
|
||||
label("TYPE").style(Decoration.bold.bold()),
|
||||
label("NAME").style(Decoration.bold.bold()),
|
||||
label("VALUE").style(Decoration.bold.bold()),
|
||||
label("SUMMARY").style(Decoration.bold.bold()),
|
||||
label("DESCRIPTION").style(Decoration.bold.bold()));
|
||||
|
||||
for (final Field optionField : optionFields) {
|
||||
final Option optionAnnotation = optionField.getAnnotation(Option.class);
|
||||
table.row("" + optionAnnotation.level(),
|
||||
optionField.getType().getSimpleName(),
|
||||
optionAnnotation.name(),
|
||||
"" + optionField.get(null),
|
||||
optionAnnotation.summary(),
|
||||
optionAnnotation.description());
|
||||
}
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.taobao.arthas.core.command.hidden;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.ArthasBanner;
|
||||
import com.taobao.middleware.cli.annotations.Hidden;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
/**
|
||||
* 工具介绍<br/>
|
||||
* 感谢
|
||||
*
|
||||
* @author vlinux on 15/9/1.
|
||||
*/
|
||||
@Name("thanks")
|
||||
@Summary("Credits to all personnel and organization who either contribute or help to this product. Thanks you all!")
|
||||
@Hidden
|
||||
public class ThanksCommand extends AnnotatedCommand {
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
process.write(ArthasBanner.credit()).write("\n").end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.util.FileUtils;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 25/11/2016.
|
||||
*/
|
||||
class ClassDumpTransformer implements ClassFileTransformer {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private Set<Class<?>> classesToEnhance;
|
||||
private Map<Class<?>, File> dumpResult;
|
||||
private File arthasLogHome;
|
||||
|
||||
public ClassDumpTransformer(Set<Class<?>> classesToEnhance) {
|
||||
this.classesToEnhance = classesToEnhance;
|
||||
this.dumpResult = new HashMap<Class<?>, File>();
|
||||
this.arthasLogHome = new File(LogUtil.LOGGER_FILE).getParentFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] classfileBuffer)
|
||||
throws IllegalClassFormatException {
|
||||
if (classesToEnhance.contains(classBeingRedefined)) {
|
||||
dumpClassIfNecessary(classBeingRedefined, classfileBuffer);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<Class<?>, File> getDumpResult() {
|
||||
return dumpResult;
|
||||
}
|
||||
|
||||
private void dumpClassIfNecessary(Class<?> clazz, byte[] data) {
|
||||
String className = clazz.getName();
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
String classDumpDir = "classdump";
|
||||
|
||||
// 创建类所在的包路径
|
||||
File dumpDir = new File(arthasLogHome, classDumpDir);
|
||||
if (!dumpDir.mkdirs() && !dumpDir.exists()) {
|
||||
logger.warn("create dump directory:{} failed.", dumpDir.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName;
|
||||
if (classLoader != null) {
|
||||
fileName = classLoader.getClass().getName() + "-" + Integer.toHexString(classLoader.hashCode()) +
|
||||
File.separator + className.replace(".", File.separator) + ".class";
|
||||
} else {
|
||||
fileName = className.replace(".", File.separator) + ".class";
|
||||
}
|
||||
|
||||
File dumpClassFile = new File(dumpDir, fileName);
|
||||
|
||||
// 将类字节码写入文件
|
||||
try {
|
||||
FileUtils.writeByteArrayToFile(dumpClassFile, data);
|
||||
dumpResult.put(clazz, dumpClassFile);
|
||||
} catch (IOException e) {
|
||||
logger.warn("dump class:{} to file {} failed.", className, dumpClassFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.affect.RowAffect;
|
||||
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.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.LabelElement;
|
||||
import com.taobao.text.ui.RowElement;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.ui.TreeElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
@Name("classloader")
|
||||
@Summary("Show classloader info")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" classloader\n" +
|
||||
" classloader -t\n" +
|
||||
" classloader -c 327a647b\n" +
|
||||
" classloader -c 327a647b -r META-INF/MANIFEST.MF\n" +
|
||||
" classloader -a\n" +
|
||||
" classloader -a -c 327a647b\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/classloader")
|
||||
public class ClassLoaderCommand extends AnnotatedCommand {
|
||||
private boolean isTree = false;
|
||||
private String hashCode;
|
||||
private boolean all = false;
|
||||
private String resource;
|
||||
private boolean includeReflectionClassLoader = true;
|
||||
private boolean listClassLoader = false;
|
||||
|
||||
@Option(shortName = "t", longName = "tree", flag = true)
|
||||
@Description("Display ClassLoader tree")
|
||||
public void setTree(boolean tree) {
|
||||
isTree = tree;
|
||||
}
|
||||
|
||||
@Option(shortName = "c", longName = "classloader")
|
||||
@Description("Display ClassLoader urls")
|
||||
public void setHashCode(String hashCode) {
|
||||
this.hashCode = hashCode;
|
||||
}
|
||||
|
||||
@Option(shortName = "a", longName = "all", flag = true)
|
||||
@Description("Display all classes loaded by ClassLoader")
|
||||
public void setAll(boolean all) {
|
||||
this.all = all;
|
||||
}
|
||||
|
||||
@Option(shortName = "r", longName = "resource")
|
||||
@Description("Use ClassLoader to find resources, won't work without -c specified")
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Option(shortName = "i", longName = "include-reflection-classloader", flag = true)
|
||||
@Description("Include sun.reflect.DelegatingClassLoader")
|
||||
public void setIncludeReflectionClassLoader(boolean includeReflectionClassLoader) {
|
||||
this.includeReflectionClassLoader = includeReflectionClassLoader;
|
||||
}
|
||||
|
||||
@Option(shortName = "l", longName = "list-classloader", flag = true)
|
||||
@Description("Display statistics info by classloader instance")
|
||||
public void setListClassLoader(boolean listClassLoader) {
|
||||
this.listClassLoader = listClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
if (all) {
|
||||
processAllClasses(process, inst);
|
||||
} else if (hashCode != null && resource != null) {
|
||||
processResources(process, inst);
|
||||
} else if (hashCode != null) {
|
||||
processClassloader(process, inst);
|
||||
} else if (listClassLoader || isTree){
|
||||
processClassloaders(process, inst);
|
||||
} else {
|
||||
processClassLoaderStats(process, inst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate classloader statistics.
|
||||
* e.g. In JVM, there are 100 GrooyClassLoader instances, which loaded 200 classes in total
|
||||
* @param process
|
||||
* @param inst
|
||||
*/
|
||||
private void processClassLoaderStats(CommandProcess process, Instrumentation inst) {
|
||||
RowAffect affect = new RowAffect();
|
||||
List<ClassLoaderInfo> classLoaderInfos = getAllClassLoaderInfo(inst);
|
||||
Map<String, ClassLoaderStat> classLoaderStats = new HashMap<String, ClassLoaderStat>();
|
||||
for (ClassLoaderInfo info: classLoaderInfos) {
|
||||
String name = info.classLoader == null ? "BootstrapClassLoader" : info.classLoader.getClass().getName();
|
||||
ClassLoaderStat stat = classLoaderStats.get(name);
|
||||
if (null == stat) {
|
||||
stat = new ClassLoaderStat();
|
||||
classLoaderStats.put(name, stat);
|
||||
}
|
||||
stat.addLoadedCount(info.loadedClassCount);
|
||||
stat.addNumberOfInstance(1);
|
||||
}
|
||||
|
||||
// sort the map by value
|
||||
TreeMap<String, ClassLoaderStat> sorted =
|
||||
new TreeMap<String, ClassLoaderStat>(new ValueComparator(classLoaderStats));
|
||||
sorted.putAll(classLoaderStats);
|
||||
|
||||
Element element = renderStat(sorted);
|
||||
process.write(RenderUtil.render(element, process.width()))
|
||||
.write(com.taobao.arthas.core.util.Constants.EMPTY_STRING);
|
||||
affect.rCnt(sorted.keySet().size());
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private void processClassloaders(CommandProcess process, Instrumentation inst) {
|
||||
RowAffect affect = new RowAffect();
|
||||
List<ClassLoaderInfo> classLoaderInfos = includeReflectionClassLoader ? getAllClassLoaderInfo(inst) :
|
||||
getAllClassLoaderInfo(inst, new SunReflectionClassLoaderFilter());
|
||||
Element element = isTree ? renderTree(classLoaderInfos) : renderTable(classLoaderInfos);
|
||||
process.write(RenderUtil.render(element, process.width()))
|
||||
.write(com.taobao.arthas.core.util.Constants.EMPTY_STRING);
|
||||
affect.rCnt(classLoaderInfos.size());
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
// 据 hashCode 来打印URLClassLoader的urls
|
||||
private void processClassloader(CommandProcess process, Instrumentation inst) {
|
||||
RowAffect affect = new RowAffect();
|
||||
|
||||
Set<ClassLoader> allClassLoader = getAllClassLoader(inst);
|
||||
for (ClassLoader cl : allClassLoader) {
|
||||
if (Integer.toHexString(cl.hashCode()).equals(hashCode)) {
|
||||
process.write(RenderUtil.render(renderClassLoaderUrls(cl), process.width()));
|
||||
}
|
||||
}
|
||||
process.write(com.taobao.arthas.core.util.Constants.EMPTY_STRING);
|
||||
affect.rCnt(allClassLoader.size());
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
// 使用ClassLoader去getResources
|
||||
private void processResources(CommandProcess process, Instrumentation inst) {
|
||||
RowAffect affect = new RowAffect();
|
||||
int rowCount = 0;
|
||||
Set<ClassLoader> allClassLoader = includeReflectionClassLoader ? getAllClassLoader(inst) :
|
||||
getAllClassLoader(inst, new SunReflectionClassLoaderFilter());
|
||||
for (ClassLoader cl : allClassLoader) {
|
||||
if (Integer.toHexString(cl.hashCode()).equals(hashCode)) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
try {
|
||||
Enumeration<URL> urls = cl.getResources(resource);
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
table.row(url.toString());
|
||||
rowCount++;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
process.write(RenderUtil.render(table, process.width()) + "\n");
|
||||
}
|
||||
}
|
||||
process.write(com.taobao.arthas.core.util.Constants.EMPTY_STRING);
|
||||
process.write(affect.rCnt(rowCount) + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private void processAllClasses(CommandProcess process, Instrumentation inst) {
|
||||
RowAffect affect = new RowAffect();
|
||||
process.write(RenderUtil.render(renderClasses(hashCode, inst), process.width()));
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到所有的class, 还有它们的classloader,按classloader归类好,统一输出每个classloader里有哪些class
|
||||
* <p>
|
||||
* 当hashCode是null,则把所有的classloader的都打印
|
||||
*
|
||||
* @param hashCode
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Element renderClasses(String hashCode, Instrumentation inst) {
|
||||
int hashCodeInt = -1;
|
||||
if (hashCode != null) {
|
||||
hashCodeInt = Integer.valueOf(hashCode, 16);
|
||||
}
|
||||
|
||||
SortedSet<Class> bootstrapClassSet = new TreeSet<Class>(new Comparator<Class>() {
|
||||
@Override
|
||||
public int compare(Class o1, Class o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
Class[] allLoadedClasses = inst.getAllLoadedClasses();
|
||||
Map<ClassLoader, SortedSet<Class>> classLoaderClassMap = new HashMap<ClassLoader, SortedSet<Class>>();
|
||||
for (Class clazz : allLoadedClasses) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
// Class loaded by BootstrapClassLoader
|
||||
if (classLoader == null) {
|
||||
if (hashCode == null) {
|
||||
bootstrapClassSet.add(clazz);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hashCode != null && classLoader.hashCode() != hashCodeInt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SortedSet<Class> classSet = classLoaderClassMap.get(classLoader);
|
||||
if (classSet == null) {
|
||||
classSet = new TreeSet<Class>(new Comparator<Class>() {
|
||||
@Override
|
||||
public int compare(Class o1, Class o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
classLoaderClassMap.put(classLoader, classSet);
|
||||
}
|
||||
classSet.add(clazz);
|
||||
}
|
||||
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
|
||||
if (!bootstrapClassSet.isEmpty()) {
|
||||
table.row(new LabelElement("hash:null, BootstrapClassLoader").style(Decoration.bold.bold()));
|
||||
for (Class clazz : bootstrapClassSet) {
|
||||
table.row(new LabelElement(clazz.getName()));
|
||||
}
|
||||
table.row(new LabelElement(" "));
|
||||
}
|
||||
|
||||
for (Entry<ClassLoader, SortedSet<Class>> entry : classLoaderClassMap.entrySet()) {
|
||||
ClassLoader classLoader = entry.getKey();
|
||||
SortedSet<Class> classSet = entry.getValue();
|
||||
|
||||
table.row(new LabelElement("hash:" + classLoader.hashCode() + ", " + classLoader.toString())
|
||||
.style(Decoration.bold.bold()));
|
||||
for (Class clazz : classSet) {
|
||||
table.row(new LabelElement(clazz.getName()));
|
||||
}
|
||||
|
||||
table.row(new LabelElement(" "));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private static Element renderClassLoaderUrls(ClassLoader classLoader) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
URLClassLoader cl = (URLClassLoader) classLoader;
|
||||
URL[] urls = cl.getURLs();
|
||||
if (urls != null) {
|
||||
for (URL url : urls) {
|
||||
sb.append(url.toString() + "\n");
|
||||
}
|
||||
return new LabelElement(sb.toString());
|
||||
} else {
|
||||
return new LabelElement("urls is empty.");
|
||||
}
|
||||
} else {
|
||||
return new LabelElement("not a URLClassLoader.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// 以树状列出ClassLoader的继承结构
|
||||
private static Element renderTree(List<ClassLoaderInfo> classLoaderInfos) {
|
||||
TreeElement root = new TreeElement();
|
||||
|
||||
List<ClassLoaderInfo> parentNullClassLoaders = new ArrayList<ClassLoaderInfo>();
|
||||
List<ClassLoaderInfo> parentNotNullClassLoaders = new ArrayList<ClassLoaderInfo>();
|
||||
for (ClassLoaderInfo info : classLoaderInfos) {
|
||||
if (info.parent() == null) {
|
||||
parentNullClassLoaders.add(info);
|
||||
} else {
|
||||
parentNotNullClassLoaders.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
for (ClassLoaderInfo info : parentNullClassLoaders) {
|
||||
if (info.parent() == null) {
|
||||
TreeElement parent = new TreeElement(info.getName());
|
||||
renderParent(parent, info, parentNotNullClassLoaders);
|
||||
root.addChild(parent);
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// 统计所有的ClassLoader的信息
|
||||
private static TableElement renderTable(List<ClassLoaderInfo> classLoaderInfos) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("name", "loadedCount", "hash", "parent"));
|
||||
for (ClassLoaderInfo info : classLoaderInfos) {
|
||||
table.row(info.getName(), "" + info.loadedClassCount(), info.hashCodeStr(), info.parentStr());
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private static TableElement renderStat(Map<String, ClassLoaderStat> classLoaderStats) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("name", "numberOfInstances", "loadedCountTotal"));
|
||||
for (Map.Entry<String, ClassLoaderStat> entry : classLoaderStats.entrySet()) {
|
||||
table.row(entry.getKey(), "" + entry.getValue().getNumberOfInstance(), "" + entry.getValue().getLoadedCount());
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private static void renderParent(TreeElement node, ClassLoaderInfo parent, List<ClassLoaderInfo> classLoaderInfos) {
|
||||
for (ClassLoaderInfo info : classLoaderInfos) {
|
||||
if (info.parent() == parent.classLoader) {
|
||||
TreeElement child = new TreeElement(info.getName());
|
||||
node.addChild(child);
|
||||
renderParent(child, info, classLoaderInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<ClassLoader> getAllClassLoader(Instrumentation inst, Filter... filters) {
|
||||
Set<ClassLoader> classLoaderSet = new HashSet<ClassLoader>();
|
||||
|
||||
for (Class<?> clazz : inst.getAllLoadedClasses()) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
if (shouldInclude(classLoader, filters)) {
|
||||
classLoaderSet.add(classLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classLoaderSet;
|
||||
}
|
||||
|
||||
private static List<ClassLoaderInfo> getAllClassLoaderInfo(Instrumentation inst, Filter... filters) {
|
||||
// 这里认为class.getClassLoader()返回是null的是由BootstrapClassLoader加载的,特殊处理
|
||||
ClassLoaderInfo bootstrapInfo = new ClassLoaderInfo(null);
|
||||
|
||||
Map<ClassLoader, ClassLoaderInfo> loaderInfos = new HashMap<ClassLoader, ClassLoaderInfo>();
|
||||
|
||||
for (Class<?> clazz : inst.getAllLoadedClasses()) {
|
||||
ClassLoader classLoader = clazz.getClassLoader();
|
||||
if (classLoader == null) {
|
||||
bootstrapInfo.increase();
|
||||
} else {
|
||||
if (shouldInclude(classLoader, filters)) {
|
||||
ClassLoaderInfo loaderInfo = loaderInfos.get(classLoader);
|
||||
if (loaderInfo == null) {
|
||||
loaderInfo = new ClassLoaderInfo(classLoader);
|
||||
loaderInfos.put(classLoader, loaderInfo);
|
||||
ClassLoader parent = classLoader.getParent();
|
||||
while (parent != null) {
|
||||
ClassLoaderInfo parentLoaderInfo = loaderInfos.get(parent);
|
||||
if (parentLoaderInfo == null) {
|
||||
parentLoaderInfo = new ClassLoaderInfo(parent);
|
||||
loaderInfos.put(parent, parentLoaderInfo);
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
loaderInfo.increase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 排序时,把用户自己定的ClassLoader排在最前面,以sun.
|
||||
// 开头的放后面,因为sun.reflect.DelegatingClassLoader的实例太多
|
||||
List<ClassLoaderInfo> sunClassLoaderList = new ArrayList<ClassLoaderInfo>();
|
||||
|
||||
List<ClassLoaderInfo> otherClassLoaderList = new ArrayList<ClassLoaderInfo>();
|
||||
|
||||
for (Entry<ClassLoader, ClassLoaderInfo> entry : loaderInfos.entrySet()) {
|
||||
ClassLoader classLoader = entry.getKey();
|
||||
if (classLoader.getClass().getName().startsWith("sun.")) {
|
||||
sunClassLoaderList.add(entry.getValue());
|
||||
} else {
|
||||
otherClassLoaderList.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(sunClassLoaderList);
|
||||
Collections.sort(otherClassLoaderList);
|
||||
|
||||
List<ClassLoaderInfo> result = new ArrayList<ClassLoaderInfo>();
|
||||
result.add(bootstrapInfo);
|
||||
result.addAll(otherClassLoaderList);
|
||||
result.addAll(sunClassLoaderList);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean shouldInclude(ClassLoader classLoader, Filter... filters) {
|
||||
if (filters == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Filter filter : filters) {
|
||||
if (!filter.accept(classLoader)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class ClassLoaderInfo implements Comparable<ClassLoaderInfo> {
|
||||
private ClassLoader classLoader;
|
||||
private int loadedClassCount = 0;
|
||||
|
||||
ClassLoaderInfo(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public ClassLoader getClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (classLoader != null) {
|
||||
return classLoader.toString();
|
||||
}
|
||||
return "BootstrapClassLoader";
|
||||
}
|
||||
|
||||
String hashCodeStr() {
|
||||
if (classLoader != null) {
|
||||
return "" + Integer.toHexString(classLoader.hashCode());
|
||||
}
|
||||
return "null";
|
||||
}
|
||||
|
||||
void increase() {
|
||||
loadedClassCount++;
|
||||
}
|
||||
|
||||
int loadedClassCount() {
|
||||
return loadedClassCount;
|
||||
}
|
||||
|
||||
ClassLoader parent() {
|
||||
return classLoader == null ? null : classLoader.getParent();
|
||||
}
|
||||
|
||||
String parentStr() {
|
||||
if (classLoader == null) {
|
||||
return "null";
|
||||
}
|
||||
ClassLoader parent = classLoader.getParent();
|
||||
if (parent == null) {
|
||||
return "null";
|
||||
}
|
||||
return parent.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ClassLoaderInfo other) {
|
||||
if (other == null) {
|
||||
return -1;
|
||||
}
|
||||
if (other.classLoader == null) {
|
||||
return -1;
|
||||
}
|
||||
if (this.classLoader == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.classLoader.getClass().getName().compareTo(other.classLoader.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface Filter {
|
||||
boolean accept(ClassLoader classLoader);
|
||||
}
|
||||
|
||||
private static class SunReflectionClassLoaderFilter implements Filter {
|
||||
private static final String REFLECTION_CLASSLOADER = "sun.reflect.DelegatingClassLoader";
|
||||
|
||||
@Override
|
||||
public boolean accept(ClassLoader classLoader) {
|
||||
return !REFLECTION_CLASSLOADER.equals(classLoader.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassLoaderStat {
|
||||
private int loadedCount;
|
||||
private int numberOfInstance;
|
||||
|
||||
void addLoadedCount(int count) {
|
||||
this.loadedCount += count;
|
||||
}
|
||||
|
||||
void addNumberOfInstance(int count) {
|
||||
this.numberOfInstance += count;
|
||||
}
|
||||
|
||||
int getLoadedCount() {
|
||||
return loadedCount;
|
||||
}
|
||||
|
||||
int getNumberOfInstance() {
|
||||
return numberOfInstance;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ValueComparator implements Comparator<String> {
|
||||
|
||||
private Map<String, ClassLoaderStat> unsortedStats;
|
||||
|
||||
ValueComparator(Map<String, ClassLoaderStat> stats) {
|
||||
this.unsortedStats = stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
if (null == unsortedStats) {
|
||||
return -1;
|
||||
}
|
||||
if (!unsortedStats.containsKey(o1)) {
|
||||
return 1;
|
||||
}
|
||||
if (!unsortedStats.containsKey(o2)) {
|
||||
return -1;
|
||||
}
|
||||
return unsortedStats.get(o2).getLoadedCount() - unsortedStats.get(o1).getLoadedCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Enhancer;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
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.middleware.logger.Logger;
|
||||
import com.taobao.text.Color;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
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.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* Dump class byte array
|
||||
*/
|
||||
@Name("dump")
|
||||
@Summary("Dump class byte array from JVM")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" dump -E org\\\\.apache\\\\.commons\\\\.lang\\\\.StringUtils\n" +
|
||||
" dump org.apache.commons.lang.StringUtils\n" +
|
||||
" dump org/apache/commons/lang/StringUtils\n" +
|
||||
" dump *StringUtils\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/dump")
|
||||
public class DumpClassCommand extends AnnotatedCommand {
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private String classPattern;
|
||||
private String code = null;
|
||||
private boolean isRegEx = false;
|
||||
|
||||
@Argument(index = 0, argName = "class-pattern")
|
||||
@Description("Class name pattern, use either '.' or '/' as separator")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Option(shortName = "c", longName = "code")
|
||||
@Description("The hash code of the special class's classLoader")
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex")
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
RowAffect effect = new RowAffect();
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
|
||||
Set<Class<?>> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx, code);
|
||||
try {
|
||||
if (matchedClasses == null || matchedClasses.isEmpty()) {
|
||||
processNoMatch(process);
|
||||
} else if (matchedClasses.size() > 5) {
|
||||
processMatches(process, matchedClasses);
|
||||
} else {
|
||||
processMatch(process, effect, inst, matchedClasses);
|
||||
}
|
||||
} finally {
|
||||
process.write(effect + "\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processMatch(CommandProcess process, RowAffect effect, Instrumentation inst, Set<Class<?>> matchedClasses) {
|
||||
try {
|
||||
Map<Class<?>, File> classFiles = dump(inst, matchedClasses);
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(new LabelElement("HASHCODE").style(Decoration.bold.bold()),
|
||||
new LabelElement("CLASSLOADER").style(Decoration.bold.bold()),
|
||||
new LabelElement("LOCATION").style(Decoration.bold.bold()));
|
||||
|
||||
for (Map.Entry<Class<?>, File> entry : classFiles.entrySet()) {
|
||||
Class<?> clazz = entry.getKey();
|
||||
File file = entry.getValue();
|
||||
table.row(label(StringUtils.classLoaderHash(clazz)).style(Decoration.bold.fg(Color.red)),
|
||||
TypeRenderUtils.drawClassLoader(clazz),
|
||||
label(file.getCanonicalPath()).style(Decoration.bold.fg(Color.red)));
|
||||
}
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()))
|
||||
.write(com.taobao.arthas.core.util.Constants.EMPTY_STRING);
|
||||
effect.rCnt(classFiles.keySet().size());
|
||||
} catch (Throwable t) {
|
||||
logger.error(null, "dump: fail to dump classes: " + matchedClasses, t);
|
||||
}
|
||||
}
|
||||
|
||||
private void processMatches(CommandProcess process, Set<Class<?>> matchedClasses) {
|
||||
Element usage = new LabelElement("dump -c hashcode " + classPattern).style(Decoration.bold.fg(Color.blue));
|
||||
process.write("Found more than 5 class for: " + classPattern + ", Please use ");
|
||||
process.write(RenderUtil.render(usage, process.width()));
|
||||
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(new LabelElement("NAME").style(Decoration.bold.bold()),
|
||||
new LabelElement("HASHCODE").style(Decoration.bold.bold()),
|
||||
new LabelElement("CLASSLOADER").style(Decoration.bold.bold()));
|
||||
|
||||
for (Class<?> c : matchedClasses) {
|
||||
table.row(label(c.getName()), label(StringUtils.classLoaderHash(c)).style(Decoration.bold.fg(Color.red)),
|
||||
TypeRenderUtils.drawClassLoader(c));
|
||||
}
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()) + "\n");
|
||||
}
|
||||
|
||||
private void processNoMatch(CommandProcess process) {
|
||||
process.write("No class found for: " + classPattern + "\n");
|
||||
}
|
||||
|
||||
private Map<Class<?>, File> dump(Instrumentation inst, Set<Class<?>> classes) throws UnmodifiableClassException {
|
||||
ClassDumpTransformer transformer = new ClassDumpTransformer(classes);
|
||||
Enhancer.enhance(inst, transformer, classes);
|
||||
return transformer.getDumpResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.command.express.ExpressFactory;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.util.TypeRenderUtils;
|
||||
import com.taobao.arthas.core.util.affect.RowAffect;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.matcher.RegexMatcher;
|
||||
import com.taobao.arthas.core.util.matcher.WildcardMatcher;
|
||||
import com.taobao.arthas.core.view.ObjectView;
|
||||
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.middleware.logger.Logger;
|
||||
import com.taobao.text.Color;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.LabelElement;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* @author diecui1202 on 2017/9/27.
|
||||
*/
|
||||
|
||||
@Name("getstatic")
|
||||
@Summary("Show the static field of a class")
|
||||
@Description(Constants.EXAMPLE + " getstatic -c 39eb305e org.apache.log4j.LogManager DEFAULT_CONFIGURATION_FILE\n"
|
||||
+ Constants.WIKI + Constants.WIKI_HOME + "cmds/getstatic")
|
||||
public class GetStaticCommand extends AnnotatedCommand {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private String classPattern;
|
||||
private String fieldPattern;
|
||||
private String express;
|
||||
private String code = null;
|
||||
private boolean isRegEx = false;
|
||||
private int expand = 1;
|
||||
|
||||
@Argument(argName = "class-pattern", index = 0)
|
||||
@Description("Class name pattern, use either '.' or '/' as separator")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "field-pattern", index = 1)
|
||||
@Description("Field name pattern")
|
||||
public void setFieldPattern(String fieldPattern) {
|
||||
this.fieldPattern = fieldPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "express", index = 2, required = false)
|
||||
@Description("the content you want to watch, written by ognl")
|
||||
public void setExpress(String express) {
|
||||
this.express = express;
|
||||
}
|
||||
|
||||
@Option(shortName = "c", longName = "code")
|
||||
@Description("The hash code of the special class's classLoader")
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "x", longName = "expand")
|
||||
@Description("Expand level of object (1 by default)")
|
||||
public void setExpand(Integer expand) {
|
||||
this.expand = expand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
Set<Class<?>> matchedClasses = SearchUtils.searchClassOnly(inst, classPattern, isRegEx, code);
|
||||
|
||||
try {
|
||||
if (matchedClasses == null || matchedClasses.isEmpty()) {
|
||||
process.write("No class found for: " + classPattern + "\n");
|
||||
} else if (matchedClasses.size() > 1) {
|
||||
processMatches(process, matchedClasses);
|
||||
} else {
|
||||
processExactMatch(process, affect, inst, matchedClasses);
|
||||
}
|
||||
} finally {
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst,
|
||||
Set<Class<?>> matchedClasses) {
|
||||
Matcher<String> fieldNameMatcher = fieldNameMatcher();
|
||||
|
||||
Class<?> clazz = matchedClasses.iterator().next();
|
||||
|
||||
boolean found = false;
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (!Modifier.isStatic(field.getModifiers()) || !fieldNameMatcher.matching(field.getName())) {
|
||||
continue;
|
||||
}
|
||||
if (!field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
try {
|
||||
Object value = field.get(null);
|
||||
|
||||
if (!StringUtils.isEmpty(express)) {
|
||||
value = ExpressFactory.newExpress(value).get(express);
|
||||
}
|
||||
|
||||
String result = StringUtils.objectToString(expand >= 0 ? new ObjectView(value, expand).draw() : value);
|
||||
process.write("field: " + field.getName() + "\n" + result + "\n");
|
||||
|
||||
affect.rCnt(1);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.warn("getstatic: failed to get static value, class: " + clazz + ", field: " + field.getName(),
|
||||
e);
|
||||
process.write("Failed to get static, exception message: " + e.getMessage()
|
||||
+ ", please check $HOME/logs/arthas/arthas.log for more details. \n");
|
||||
} catch (ExpressException e) {
|
||||
logger.warn("getstatic: failed to get express value, class: " + clazz + ", field: " + field.getName()
|
||||
+ ", express: " + express, e);
|
||||
process.write("Failed to get static, exception message: " + e.getMessage()
|
||||
+ ", please check $HOME/logs/arthas/arthas.log for more details. \n");
|
||||
} finally {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
process.write("getstatic: no matched static field was found\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void processMatches(CommandProcess process, Set<Class<?>> matchedClasses) {
|
||||
Element usage = new LabelElement("getstatic -c <hashcode> " + classPattern + " " + fieldPattern).style(
|
||||
Decoration.bold.fg(Color.blue));
|
||||
process.write("\n Found more than one class for: " + classPattern + ", Please use " + RenderUtil.render(usage,
|
||||
process.width()));
|
||||
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(new LabelElement("HASHCODE").style(Decoration.bold.bold()),
|
||||
new LabelElement("CLASSLOADER").style(Decoration.bold.bold()));
|
||||
|
||||
for (Class<?> c : matchedClasses) {
|
||||
ClassLoader classLoader = c.getClassLoader();
|
||||
table.row(label(Integer.toHexString(classLoader.hashCode())).style(Decoration.bold.fg(Color.red)),
|
||||
TypeRenderUtils.drawClassLoader(c));
|
||||
}
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()) + "\n");
|
||||
}
|
||||
|
||||
private Matcher<String> fieldNameMatcher() {
|
||||
return isRegEx ? new RegexMatcher(fieldPattern) : new WildcardMatcher(fieldPattern);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Enhancer;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.FileUtils;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
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.middleware.logger.Logger;
|
||||
import com.taobao.text.Color;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.lang.LangRenderUtil;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.LabelElement;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
import org.benf.cfr.reader.Main;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* @author diecui1202 on 15/11/24.
|
||||
*/
|
||||
@Name("jad")
|
||||
@Summary("Decompile class")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" jad -c 39eb305e org.apache.log4j.Logger\n" +
|
||||
" jad -c 39eb305e org/apache/log4j/Logger\n" +
|
||||
" jad -c 39eb305e -E org\\\\.apache\\\\.*\\\\.StringUtils\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/jad")
|
||||
public class JadCommand extends AnnotatedCommand {
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
private static Pattern pattern = Pattern.compile("(?m)^/\\*\\s*\\*/\\s*$" + System.getProperty("line.separator"));
|
||||
private static final String OUTPUTOPTION = "--outputdir";
|
||||
private static final String COMMENTS = "--comments";
|
||||
private static final String DecompilePath = new File(LogUtil.LOGGER_FILE).getParent() + File.separator + "decompile";
|
||||
|
||||
private String classPattern;
|
||||
private String methodName;
|
||||
private String code = null;
|
||||
private boolean isRegEx = false;
|
||||
|
||||
@Argument(argName = "class-pattern", index = 0)
|
||||
@Description("Class name pattern, use either '.' or '/' as separator")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "method-name", index = 1, required = false)
|
||||
@Description("method name pattern, decompile a specific method instead of the whole class")
|
||||
public void setMethodName(String methodName) {
|
||||
this.methodName = methodName;
|
||||
}
|
||||
|
||||
|
||||
@Option(shortName = "c", longName = "code")
|
||||
@Description("The hash code of the special class's classLoader")
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
Set<Class<?>> matchedClasses = SearchUtils.searchClassOnly(inst, classPattern, isRegEx, code);
|
||||
|
||||
try {
|
||||
if (matchedClasses == null || matchedClasses.isEmpty()) {
|
||||
processNoMatch(process);
|
||||
} else if (matchedClasses.size() > 1) {
|
||||
processMatches(process, matchedClasses);
|
||||
} else {
|
||||
Set<Class<?>> withInnerClasses = SearchUtils.searchClassOnly(inst, classPattern + "(?!.*\\$\\$Lambda\\$).*", true, code);
|
||||
processExactMatch(process, affect, inst, matchedClasses, withInnerClasses);
|
||||
}
|
||||
} finally {
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst, Set<Class<?>> matchedClasses, Set<Class<?>> withInnerClasses) {
|
||||
Class<?> c = matchedClasses.iterator().next();
|
||||
matchedClasses = withInnerClasses;
|
||||
|
||||
try {
|
||||
ClassDumpTransformer transformer = new ClassDumpTransformer(matchedClasses);
|
||||
Enhancer.enhance(inst, transformer, matchedClasses);
|
||||
Map<Class<?>, File> classFiles = transformer.getDumpResult();
|
||||
File classFile = classFiles.get(c);
|
||||
|
||||
String source;
|
||||
source = decompileWithCFR(classFile.getAbsolutePath(), c, methodName);
|
||||
if (source != null) {
|
||||
source = pattern.matcher(source).replaceAll("");
|
||||
} else {
|
||||
source = "unknown";
|
||||
}
|
||||
|
||||
|
||||
process.write("\n");
|
||||
process.write(RenderUtil.render(new LabelElement("ClassLoader: ").style(Decoration.bold.fg(Color.red)), process.width()));
|
||||
process.write(RenderUtil.render(TypeRenderUtils.drawClassLoader(c), process.width()) + "\n");
|
||||
process.write(RenderUtil.render(new LabelElement("Location: ").style(Decoration.bold.fg(Color.red)), process.width()));
|
||||
process.write(RenderUtil.render(new LabelElement(SearchClassCommand.getCodeSource(
|
||||
c.getProtectionDomain().getCodeSource())).style(Decoration.bold.fg(Color.blue)), process.width()) + "\n");
|
||||
process.write(LangRenderUtil.render(source) + "\n");
|
||||
process.write(com.taobao.arthas.core.util.Constants.EMPTY_STRING);
|
||||
affect.rCnt(classFiles.keySet().size());
|
||||
} catch (Throwable t) {
|
||||
logger.error(null, "jad: fail to decompile class: " + c.getName(), t);
|
||||
}
|
||||
}
|
||||
|
||||
private void processMatches(CommandProcess process, Set<Class<?>> matchedClasses) {
|
||||
Element usage = new LabelElement("jad -c <hashcode> " + classPattern).style(Decoration.bold.fg(Color.blue));
|
||||
process.write("\n Found more than one class for: " + classPattern + ", Please use "
|
||||
+ RenderUtil.render(usage, process.width()));
|
||||
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(new LabelElement("HASHCODE").style(Decoration.bold.bold()),
|
||||
new LabelElement("CLASSLOADER").style(Decoration.bold.bold()));
|
||||
|
||||
for (Class<?> c : matchedClasses) {
|
||||
ClassLoader classLoader = c.getClassLoader();
|
||||
table.row(label(Integer.toHexString(classLoader.hashCode())).style(Decoration.bold.fg(Color.red)),
|
||||
TypeRenderUtils.drawClassLoader(c));
|
||||
}
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()) + "\n");
|
||||
}
|
||||
|
||||
private void processNoMatch(CommandProcess process) {
|
||||
process.write("No class found for: " + classPattern + "\n");
|
||||
}
|
||||
|
||||
private String decompileWithCFR(String classPath, Class<?> clazz, String methodName) {
|
||||
List<String> options = new ArrayList<String>();
|
||||
options.add(classPath);
|
||||
// options.add(clazz.getName());
|
||||
if (methodName != null) {
|
||||
options.add(methodName);
|
||||
}
|
||||
options.add(OUTPUTOPTION);
|
||||
options.add(DecompilePath);
|
||||
options.add(COMMENTS);
|
||||
options.add("false");
|
||||
String args[] = new String[options.size()];
|
||||
options.toArray(args);
|
||||
Main.main(args);
|
||||
String outputFilePath = DecompilePath + File.separator + Type.getInternalName(clazz) + ".java";
|
||||
File outputFile = new File(outputFilePath);
|
||||
if (outputFile.exists()) {
|
||||
try {
|
||||
return FileUtils.readFileToString(outputFile, Charset.defaultCharset());
|
||||
} catch (IOException e) {
|
||||
logger.error(null, "error read decompile result in: " + outputFilePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String[] names = {
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController",
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController$1$$Lambda$19/381016128",
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController$$Lambda$16/17741163",
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController$1",
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController$123",
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController$A",
|
||||
"com.taobao.container.web.arthas.mvc.AppInfoController$ABC"
|
||||
};
|
||||
|
||||
String pattern = "com.taobao.container.web.arthas.mvc.AppInfoController" + "(?!.*\\$\\$Lambda\\$).*";
|
||||
for(String name : names) {
|
||||
System.out.println(name + " " + Pattern.matches(pattern, name));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.instrument.ClassDefinition;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Redefine Classes.
|
||||
*
|
||||
* @author hengyunabc 2018-07-13
|
||||
* @see java.lang.instrument.Instrumentation#redefineClasses(ClassDefinition...)
|
||||
*/
|
||||
@Name("redefine")
|
||||
@Summary("Redefine classes. @see Instrumentation#redefineClasses(ClassDefinition...)")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" redefine -p /tmp/Test.class\n" +
|
||||
" redefine -c 327a647b -p /tmp/Test.class /tmp/Test\\$Inner.class \n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/redefine")
|
||||
public class RedefineCommand extends AnnotatedCommand {
|
||||
|
||||
private static final int MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
private String hashCode;
|
||||
|
||||
private List<String> paths;
|
||||
|
||||
@Option(shortName = "c", longName = "classloader")
|
||||
@Description("classLoader hashcode")
|
||||
public void setHashCode(String hashCode) {
|
||||
this.hashCode = hashCode;
|
||||
}
|
||||
|
||||
@Option(shortName = "p", longName = "path", acceptMultipleValues = true)
|
||||
@Description(".class file paths")
|
||||
public void setPathPatterns(List<String> paths) {
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
if (paths == null || paths.isEmpty()) {
|
||||
process.write("paths is empty.\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
|
||||
for (String path : paths) {
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
process.write("path is not exists, path:" + path + "\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
if (!file.isFile()) {
|
||||
process.write("path is not a normal file, path:" + path + "\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
if (file.length() >= MAX_FILE_SIZE) {
|
||||
process.write("file size: " + file.length() + " >= " + MAX_FILE_SIZE + ", path: " + path + "\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, byte[]> bytesMap = new HashMap<String, byte[]>();
|
||||
for (String path : paths) {
|
||||
RandomAccessFile f = null;
|
||||
try {
|
||||
f = new RandomAccessFile(path, "r");
|
||||
final byte[] bytes = new byte[(int) f.length()];
|
||||
f.readFully(bytes);
|
||||
|
||||
final String clazzName = readClassName(bytes);
|
||||
|
||||
bytesMap.put(clazzName, bytes);
|
||||
|
||||
} catch (Exception e) {
|
||||
process.write("" + e + "\n");
|
||||
} finally {
|
||||
if (f != null) {
|
||||
try {
|
||||
f.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bytesMap.size() != paths.size()) {
|
||||
process.write("paths may contains same class name!\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
|
||||
List<ClassDefinition> definitions = new ArrayList<ClassDefinition>();
|
||||
for (Class<?> clazz : inst.getAllLoadedClasses()) {
|
||||
if (bytesMap.containsKey(clazz.getName())) {
|
||||
if (hashCode != null && !Integer.toHexString(clazz.getClassLoader().hashCode()).equals(hashCode)) {
|
||||
continue;
|
||||
}
|
||||
definitions.add(new ClassDefinition(clazz, bytesMap.get(clazz.getName())));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
inst.redefineClasses(definitions.toArray(new ClassDefinition[0]));
|
||||
process.write("redefine success, size: " + definitions.size() + "\n");
|
||||
} catch (Exception e) {
|
||||
process.write("redefine error! " + e + "\n");
|
||||
}
|
||||
|
||||
process.end();
|
||||
}
|
||||
|
||||
private static String readClassName(final byte[] bytes) {
|
||||
return new ClassReader(bytes).getClassName().replace("/", ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
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.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.CodeSource;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* 展示类信息
|
||||
*
|
||||
* @author vlinux
|
||||
*/
|
||||
@Name("sc")
|
||||
@Summary("Search all the classes loaded by JVM")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" sc -E org\\\\.apache\\\\.commons\\\\.lang\\\\.StringUtils\n" +
|
||||
" sc -d org.apache.commons.lang.StringUtils\n" +
|
||||
" sc -d org/apache/commons/lang/StringUtils\n" +
|
||||
" sc -d *StringUtils\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/sc")
|
||||
public class SearchClassCommand extends AnnotatedCommand {
|
||||
private String classPattern;
|
||||
private boolean isDetail = false;
|
||||
private boolean isField = false;
|
||||
private boolean isRegEx = false;
|
||||
private Integer expand;
|
||||
|
||||
@Argument(argName = "class-pattern", index = 0)
|
||||
@Description("Class name pattern, use either '.' or '/' as separator")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Option(shortName = "d", longName = "details", flag = true)
|
||||
@Description("Display the details of class")
|
||||
public void setDetail(boolean detail) {
|
||||
isDetail = detail;
|
||||
}
|
||||
|
||||
@Option(shortName = "f", longName = "field", flag = true)
|
||||
@Description("Display all the member variables")
|
||||
public void setField(boolean field) {
|
||||
isField = field;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "x", longName = "expand")
|
||||
@Description("Expand level of object (0 by default)")
|
||||
public void setExpand(Integer expand) {
|
||||
this.expand = expand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
// TODO: null check
|
||||
RowAffect affect = new RowAffect();
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
Set<Class<?>> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx);
|
||||
|
||||
for (Class<?> clazz : matchedClasses) {
|
||||
processClass(process, clazz);
|
||||
}
|
||||
|
||||
affect.rCnt(matchedClasses.size());
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private void processClass(CommandProcess process, Class<?> clazz) {
|
||||
if (isDetail) {
|
||||
process.write(RenderUtil.render(renderClassInfo(clazz, isField), process.width()) + "\n");
|
||||
} else {
|
||||
process.write(clazz.getName() + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private Element renderClassInfo(Class<?> clazz, boolean isPrintField) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
CodeSource cs = clazz.getProtectionDomain().getCodeSource();
|
||||
|
||||
table.row(label("class-info").style(Decoration.bold.bold()), label(StringUtils.classname(clazz)))
|
||||
.row(label("code-source").style(Decoration.bold.bold()), label(getCodeSource(cs)))
|
||||
.row(label("name").style(Decoration.bold.bold()), label(StringUtils.classname(clazz)))
|
||||
.row(label("isInterface").style(Decoration.bold.bold()), label("" + clazz.isInterface()))
|
||||
.row(label("isAnnotation").style(Decoration.bold.bold()), label("" + clazz.isAnnotation()))
|
||||
.row(label("isEnum").style(Decoration.bold.bold()), label("" + clazz.isEnum()))
|
||||
.row(label("isAnonymousClass").style(Decoration.bold.bold()), label("" + clazz.isAnonymousClass()))
|
||||
.row(label("isArray").style(Decoration.bold.bold()), label("" + clazz.isArray()))
|
||||
.row(label("isLocalClass").style(Decoration.bold.bold()), label("" + clazz.isLocalClass()))
|
||||
.row(label("isMemberClass").style(Decoration.bold.bold()), label("" + clazz.isMemberClass()))
|
||||
.row(label("isPrimitive").style(Decoration.bold.bold()), label("" + clazz.isPrimitive()))
|
||||
.row(label("isSynthetic").style(Decoration.bold.bold()), label("" + clazz.isSynthetic()))
|
||||
.row(label("simple-name").style(Decoration.bold.bold()), label(clazz.getSimpleName()))
|
||||
.row(label("modifier").style(Decoration.bold.bold()), label(StringUtils.modifier(clazz.getModifiers(), ',')))
|
||||
.row(label("annotation").style(Decoration.bold.bold()), label(TypeRenderUtils.drawAnnotation(clazz)))
|
||||
.row(label("interfaces").style(Decoration.bold.bold()), label(TypeRenderUtils.drawInterface(clazz)))
|
||||
.row(label("super-class").style(Decoration.bold.bold()), TypeRenderUtils.drawSuperClass(clazz))
|
||||
.row(label("class-loader").style(Decoration.bold.bold()), TypeRenderUtils.drawClassLoader(clazz))
|
||||
.row(label("classLoaderHash").style(Decoration.bold.bold()), label(StringUtils.classLoaderHash(clazz)));
|
||||
|
||||
if (isPrintField) {
|
||||
table.row(label("fields"), TypeRenderUtils.drawField(clazz, expand));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
public static String getCodeSource(final CodeSource cs) {
|
||||
if (null == cs || null == cs.getLocation() || null == cs.getLocation().getFile()) {
|
||||
return com.taobao.arthas.core.util.Constants.EMPTY_STRING;
|
||||
}
|
||||
|
||||
return cs.getLocation().getFile();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.taobao.arthas.core.command.klass100;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.matcher.RegexMatcher;
|
||||
import com.taobao.arthas.core.util.matcher.WildcardMatcher;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
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.ui.Element;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.taobao.text.Decoration.bold;
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* 展示方法信息
|
||||
*
|
||||
* @author vlinux
|
||||
*/
|
||||
@Name("sm")
|
||||
@Summary("Search the method of classes loaded by JVM")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" sm -Ed org\\\\.apache\\\\.commons\\\\.lang\\.StringUtils .*\n" +
|
||||
" sm org.apache.commons.????.StringUtils *\n" +
|
||||
" sm -d org.apache.commons.lang.StringUtils\n" +
|
||||
" sm -d org/apache/commons/lang/StringUtils\n" +
|
||||
" sm *String????s *\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/sm")
|
||||
public class SearchMethodCommand extends AnnotatedCommand {
|
||||
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private boolean isDetail = false;
|
||||
private boolean isRegEx = false;
|
||||
|
||||
@Argument(argName = "class-pattern", index = 0)
|
||||
@Description("Class name pattern, use either '.' or '/' as separator")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "method-pattern", index = 1, required = false)
|
||||
@Description("Method name pattern")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Option(shortName = "d", longName = "details", flag = true)
|
||||
@Description("Display the details of method")
|
||||
public void setDetail(boolean detail) {
|
||||
isDetail = detail;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
Matcher<String> methodNameMatcher = methodNameMatcher();
|
||||
Set<Class<?>> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx);
|
||||
|
||||
for (Class<?> clazz : matchedClasses) {
|
||||
Set<String> methodNames = new HashSet<String>();
|
||||
for (Constructor constructor : clazz.getDeclaredConstructors()) {
|
||||
if (!methodNameMatcher.matching("<init>")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isDetail) {
|
||||
process.write(RenderUtil.render(renderConstructor(constructor), process.width()) + "\n");
|
||||
} else {
|
||||
if (methodNames.contains("<init>")) {
|
||||
continue;
|
||||
}
|
||||
methodNames.add("<init>");
|
||||
String line = format("%s->%s%n", clazz.getName(), "<init>");
|
||||
process.write(line);
|
||||
}
|
||||
affect.rCnt(1);
|
||||
}
|
||||
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (!methodNameMatcher.matching(method.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isDetail) {
|
||||
process.write(RenderUtil.render(renderMethod(method), process.width()) + "\n");
|
||||
} else {
|
||||
if (methodNames.contains(method.getName())) {
|
||||
continue;
|
||||
}
|
||||
methodNames.add(method.getName());
|
||||
String line = format("%s->%s%n", clazz.getName(), method.getName());
|
||||
process.write(line);
|
||||
}
|
||||
affect.rCnt(1);
|
||||
}
|
||||
}
|
||||
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private Matcher<String> methodNameMatcher() {
|
||||
// auto fix default methodPattern
|
||||
if (StringUtils.isBlank(methodPattern)) {
|
||||
methodPattern = isRegEx ? ".*" : "*";
|
||||
}
|
||||
return isRegEx ? new RegexMatcher(methodPattern) : new WildcardMatcher(methodPattern);
|
||||
}
|
||||
|
||||
private Element renderMethod(Method method) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
|
||||
table.row(label("declaring-class").style(bold.bold()), label(method.getDeclaringClass().getName()))
|
||||
.row(label("method-name").style(bold.bold()), label(method.getName()).style(bold.bold()))
|
||||
.row(label("modifier").style(bold.bold()), label(StringUtils.modifier(method.getModifiers(), ',')))
|
||||
.row(label("annotation").style(bold.bold()), label(TypeRenderUtils.drawAnnotation(method)))
|
||||
.row(label("parameters").style(bold.bold()), label(TypeRenderUtils.drawParameters(method)))
|
||||
.row(label("return").style(bold.bold()), label(TypeRenderUtils.drawReturn(method)))
|
||||
.row(label("exceptions").style(bold.bold()), label(TypeRenderUtils.drawExceptions(method)));
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element renderConstructor(Constructor constructor) {
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
|
||||
table.row(label("declaring-class").style(bold.bold()), label(constructor.getDeclaringClass().getName()))
|
||||
.row(label("constructor-name").style(bold.bold()), label("<init>").style(bold.bold()))
|
||||
.row(label("modifier").style(bold.bold()), label(StringUtils.modifier(constructor.getModifiers(), ',')))
|
||||
.row(label("annotation").style(bold.bold()), label(TypeRenderUtils.drawAnnotation(constructor.getDeclaredAnnotations())))
|
||||
.row(label("parameters").style(bold.bold()), label(TypeRenderUtils.drawParameters(constructor)))
|
||||
.row(label("exceptions").style(bold.bold()), label(TypeRenderUtils.drawExceptions(constructor)));
|
||||
return table;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.ThreadLocalWatch;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-01-06 16:02.
|
||||
*/
|
||||
public class AbstractTraceAdviceListener extends ReflectAdviceListenerAdapter {
|
||||
|
||||
protected final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
|
||||
protected TraceCommand command;
|
||||
protected CommandProcess process;
|
||||
|
||||
protected final ThreadLocal<TraceEntity> threadBoundEntity = new ThreadLocal<TraceEntity>() {
|
||||
|
||||
@Override
|
||||
protected TraceEntity initialValue() {
|
||||
return new TraceEntity();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public AbstractTraceAdviceListener(TraceCommand command, CommandProcess process) {
|
||||
this.command = command;
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
threadBoundEntity.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
threadBoundEntity.get().view.begin(clazz.getName() + ":" + method.getName() + "()");
|
||||
threadBoundEntity.get().deep++;
|
||||
// 开始计算本次方法调用耗时
|
||||
threadLocalWatch.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
threadBoundEntity.get().view.end();
|
||||
final Advice advice = Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject);
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Throwable throwable) throws Throwable {
|
||||
threadBoundEntity.get().view.begin("throw:" + throwable.getClass().getName() + "()").end().end();
|
||||
final Advice advice = Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable);
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
public TraceCommand getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
private void finishing(Advice advice) {
|
||||
// 本次调用的耗时
|
||||
double cost = threadLocalWatch.costInMillis();
|
||||
if (--threadBoundEntity.get().deep == 0) {
|
||||
try {
|
||||
if (isConditionMet(command.getConditionExpress(), advice, cost)) {
|
||||
// 满足输出条件
|
||||
if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) {
|
||||
// TODO: concurrency issue to abort process
|
||||
abortProcess(process, command.getNumberOfLimit());
|
||||
} else {
|
||||
process.times().incrementAndGet();
|
||||
// TODO: concurrency issues for process.write
|
||||
process.write(threadBoundEntity.get().view.draw() + "\n");
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LogUtil.getArthasLogger().warn("trace failed.", e);
|
||||
process.write("trace failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage()
|
||||
+ ", visit " + LogUtil.LOGGER_FILE + " for more details.\n");
|
||||
process.end();
|
||||
} finally {
|
||||
threadBoundEntity.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-01-11 14:57.
|
||||
*/
|
||||
public class CompleteContext {
|
||||
|
||||
private CompleteState state;
|
||||
|
||||
public CompleteContext() {
|
||||
this.state = CompleteState.INIT;
|
||||
}
|
||||
|
||||
public void setState(CompleteState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public CompleteState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The state transition diagram is:
|
||||
* INIT -> CLASS_NAME -> METHOD_NAME -> FINISHED
|
||||
*/
|
||||
enum CompleteState {
|
||||
|
||||
/**
|
||||
* the state that nothing is completed
|
||||
*/
|
||||
INIT,
|
||||
|
||||
/**
|
||||
* the state that class name is completed
|
||||
*/
|
||||
CLASS_COMPLETED,
|
||||
|
||||
/**
|
||||
* the state that method name is completed
|
||||
*/
|
||||
METHOD_COMPLETED,
|
||||
|
||||
/**
|
||||
* the state that express is completed
|
||||
*/
|
||||
EXPRESS_COMPLETED,
|
||||
|
||||
/**
|
||||
* the state that condition-express is completed
|
||||
*/
|
||||
CONDITION_EXPRESS_COMPLETED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.NetUtils;
|
||||
import com.taobao.arthas.core.util.NetUtils.Response;
|
||||
import com.taobao.arthas.core.util.ThreadUtil;
|
||||
import com.taobao.arthas.core.util.metrics.SumRateCounter;
|
||||
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.middleware.logger.Logger;
|
||||
import com.taobao.text.Color;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.Style;
|
||||
import com.taobao.text.renderers.ThreadRenderer;
|
||||
import com.taobao.text.ui.RowElement;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.management.BufferPoolMXBean;
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryPoolMXBean;
|
||||
import java.lang.management.MemoryType;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* @author hengyunabc 2015年11月19日 上午11:57:21
|
||||
*/
|
||||
@Name("dashboard")
|
||||
@Summary("Overview of target jvm's thread, memory, gc, vm, tomcat info.")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" dashboard\n" +
|
||||
" dashboard -n 10\n" +
|
||||
" dashboard -i 2000\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/dashboard")
|
||||
public class DashboardCommand extends AnnotatedCommand {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private SumRateCounter tomcatRequestCounter = new SumRateCounter();
|
||||
private SumRateCounter tomcatErrorCounter = new SumRateCounter();
|
||||
private SumRateCounter tomcatReceivedBytesCounter = new SumRateCounter();
|
||||
private SumRateCounter tomcatSentBytesCounter = new SumRateCounter();
|
||||
|
||||
private int numOfExecutions = Integer.MAX_VALUE;
|
||||
|
||||
private boolean batchMode;
|
||||
|
||||
private long interval = 5000;
|
||||
|
||||
private volatile long count = 0;
|
||||
private volatile Timer timer;
|
||||
private Boolean running = false;
|
||||
|
||||
@Option(shortName = "n", longName = "number-of-execution")
|
||||
@Description("The number of times this command will be executed.")
|
||||
public void setNumOfExecutions(int numOfExecutions) {
|
||||
this.numOfExecutions = numOfExecutions;
|
||||
}
|
||||
|
||||
@Option(shortName = "b", longName = "batch")
|
||||
@Description("Execute this command in batch mode.")
|
||||
public void setBatchMode(boolean batchMode) {
|
||||
this.batchMode = batchMode;
|
||||
}
|
||||
|
||||
@Option(shortName = "i", longName = "interval")
|
||||
@Description("The interval (in ms) between two executions, default is 5000 ms.")
|
||||
public void setInterval(long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void process(final CommandProcess process) {
|
||||
|
||||
Session session = process.session();
|
||||
timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);
|
||||
|
||||
// ctrl-C support
|
||||
process.interruptHandler(new DashboardInterruptHandler(process, timer));
|
||||
|
||||
/*
|
||||
* 通过handle回调,在suspend和end时停止timer,resume时重启timer
|
||||
*/
|
||||
Handler<Void> stopHandler = new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
Handler<Void> restartHandler = new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
restart(process);
|
||||
}
|
||||
};
|
||||
process.suspendHandler(stopHandler);
|
||||
process.resumeHandler(restartHandler);
|
||||
process.endHandler(stopHandler);
|
||||
|
||||
// start the timer
|
||||
timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
|
||||
running = true;
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void restart(CommandProcess process) {
|
||||
if (timer == null) {
|
||||
Session session = process.session();
|
||||
timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);
|
||||
timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumOfExecutions() {
|
||||
return numOfExecutions;
|
||||
}
|
||||
|
||||
public boolean isBatchMode() {
|
||||
return batchMode;
|
||||
}
|
||||
|
||||
public long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
private static String beautifyName(String name) {
|
||||
return name.replace(' ', '_').toLowerCase();
|
||||
}
|
||||
|
||||
private static void addBufferPoolMemoryInfo(TableElement table) {
|
||||
try {
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class bufferPoolMXBeanClass = Class.forName("java.lang.management.BufferPoolMXBean");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanClass);
|
||||
for (BufferPoolMXBean mbean : bufferPoolMXBeans) {
|
||||
long used = mbean.getMemoryUsed();
|
||||
long total = mbean.getTotalCapacity();
|
||||
new MemoryEntry(mbean.getName(), used, total, Long.MIN_VALUE).addTableRow(table);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private static void addRuntimeInfo(TableElement table) {
|
||||
table.row("os.name", System.getProperty("os.name"));
|
||||
table.row("os.version", System.getProperty("os.version"));
|
||||
table.row("java.version", System.getProperty("java.version"));
|
||||
table.row("java.home", System.getProperty("java.home"));
|
||||
table.row("systemload.average",
|
||||
String.format("%.2f", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage()));
|
||||
table.row("processors", "" + Runtime.getRuntime().availableProcessors());
|
||||
table.row("uptime", "" + ManagementFactory.getRuntimeMXBean().getUptime() / 1000 + "s");
|
||||
}
|
||||
|
||||
private static void addMemoryInfo(TableElement table) {
|
||||
MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
|
||||
MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
|
||||
|
||||
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
|
||||
|
||||
new MemoryEntry("heap", heapMemoryUsage).addTableRow(table, Decoration.bold.bold());
|
||||
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
|
||||
if (MemoryType.HEAP.equals(poolMXBean.getType())) {
|
||||
MemoryUsage usage = poolMXBean.getUsage();
|
||||
String poolName = beautifyName(poolMXBean.getName());
|
||||
new MemoryEntry(poolName, usage).addTableRow(table);
|
||||
}
|
||||
}
|
||||
|
||||
new MemoryEntry("nonheap", nonHeapMemoryUsage).addTableRow(table, Decoration.bold.bold());
|
||||
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
|
||||
if (MemoryType.NON_HEAP.equals(poolMXBean.getType())) {
|
||||
MemoryUsage usage = poolMXBean.getUsage();
|
||||
String poolName = beautifyName(poolMXBean.getName());
|
||||
new MemoryEntry(poolName, usage).addTableRow(table);
|
||||
}
|
||||
}
|
||||
|
||||
addBufferPoolMemoryInfo(table);
|
||||
}
|
||||
|
||||
private static void addGcInfo(TableElement table) {
|
||||
List<GarbageCollectorMXBean> garbageCollectorMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
|
||||
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) {
|
||||
String name = garbageCollectorMXBean.getName();
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("gc." + beautifyName(name) + ".count",
|
||||
"" + garbageCollectorMXBean.getCollectionCount()));
|
||||
table.row("gc." + beautifyName(name) + ".time(ms)", "" + garbageCollectorMXBean.getCollectionTime());
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatBytes(long size) {
|
||||
int unit = 1;
|
||||
String unitStr = "B";
|
||||
if (size / 1024 > 0) {
|
||||
unit = 1024;
|
||||
unitStr = "K";
|
||||
} else if (size / 1024 / 1024 > 0) {
|
||||
unit = 1024 * 1024;
|
||||
unitStr = "M";
|
||||
}
|
||||
|
||||
return String.format("%d%s", size / unit, unitStr);
|
||||
}
|
||||
|
||||
private void addTomcatInfo(TableElement table) {
|
||||
|
||||
String threadPoolPath = "http://localhost:8006/connector/threadpool";
|
||||
String connectorStatPath = "http://localhost:8006/connector/stats";
|
||||
Response connectorStatResponse = NetUtils.request(connectorStatPath);
|
||||
if (connectorStatResponse.isSuccess()) {
|
||||
List<JSONObject> connectorStats = JSON.parseArray(connectorStatResponse.getContent(), JSONObject.class);
|
||||
for (JSONObject stat : connectorStats) {
|
||||
String name = stat.getString("name").replace("\"", "");
|
||||
long bytesReceived = stat.getLongValue("bytesReceived");
|
||||
long bytesSent = stat.getLongValue("bytesSent");
|
||||
long processingTime = stat.getLongValue("processingTime");
|
||||
long requestCount = stat.getLongValue("requestCount");
|
||||
long errorCount = stat.getLongValue("errorCount");
|
||||
|
||||
tomcatRequestCounter.update(requestCount);
|
||||
tomcatErrorCounter.update(errorCount);
|
||||
tomcatReceivedBytesCounter.update(bytesReceived);
|
||||
tomcatSentBytesCounter.update(bytesSent);
|
||||
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("connector", name));
|
||||
table.row("QPS", String.format("%.2f", tomcatRequestCounter.rate()));
|
||||
table.row("RT(ms)", String.format("%.2f", processingTime / (double) requestCount));
|
||||
table.row("error/s", String.format("%.2f", tomcatErrorCounter.rate()));
|
||||
table.row("received/s", formatBytes((long) tomcatReceivedBytesCounter.rate()));
|
||||
table.row("sent/s", formatBytes((long) tomcatSentBytesCounter.rate()));
|
||||
}
|
||||
}
|
||||
|
||||
Response threadPoolResponse = NetUtils.request(threadPoolPath);
|
||||
if (threadPoolResponse.isSuccess()) {
|
||||
List<JSONObject> threadPoolInfos = JSON.parseArray(threadPoolResponse.getContent(), JSONObject.class);
|
||||
for (JSONObject info : threadPoolInfos) {
|
||||
String name = info.getString("name").replace("\"", "");
|
||||
long busy = info.getLongValue("threadBusy");
|
||||
long total = info.getLongValue("threadCount");
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("threadpool", name));
|
||||
table.row("busy", "" + busy);
|
||||
table.row("total", "" + total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String drawThreadInfo(int width, int height) {
|
||||
Map<String, Thread> threads = ThreadUtil.getThreads();
|
||||
return RenderUtil.render(threads.values().iterator(), new ThreadRenderer(), width, height);
|
||||
}
|
||||
|
||||
static String drawMemoryInfoAndGcInfo(int width, int height) {
|
||||
TableElement table = new TableElement(1, 1);
|
||||
|
||||
TableElement memoryInfoTable = new TableElement(3, 1, 1, 1, 1).rightCellPadding(1);
|
||||
memoryInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Memory",
|
||||
"used", "total", "max", "usage"));
|
||||
|
||||
addMemoryInfo(memoryInfoTable);
|
||||
|
||||
TableElement gcInfoTable = new TableElement(1, 1).rightCellPadding(1);
|
||||
gcInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("GC", ""));
|
||||
addGcInfo(gcInfoTable);
|
||||
|
||||
table.row(memoryInfoTable, gcInfoTable);
|
||||
return RenderUtil.render(table, width, height);
|
||||
}
|
||||
|
||||
String drawRuntineInfoAndTomcatInfo(int width, int height) {
|
||||
TableElement table = new TableElement(1, 1);
|
||||
|
||||
TableElement runtimeInfoTable = new TableElement(1, 1).rightCellPadding(1);
|
||||
runtimeInfoTable
|
||||
.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Runtime", ""));
|
||||
|
||||
addRuntimeInfo(runtimeInfoTable);
|
||||
|
||||
TableElement tomcatInfoTable = new TableElement(1, 1).rightCellPadding(1);
|
||||
|
||||
try {
|
||||
// 如果请求tomcat信息失败,则不显示tomcat信息
|
||||
if (NetUtils.request("http://localhost:8006").isSuccess()) {
|
||||
tomcatInfoTable
|
||||
.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Tomcat", ""));
|
||||
addTomcatInfo(tomcatInfoTable);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error(null, "get Tomcat Info error!", t);
|
||||
}
|
||||
|
||||
table.row(runtimeInfoTable, tomcatInfoTable);
|
||||
return RenderUtil.render(table, width, height);
|
||||
}
|
||||
|
||||
static class MemoryEntry {
|
||||
String name;
|
||||
long used;
|
||||
long total;
|
||||
long max;
|
||||
|
||||
int unit;
|
||||
String unitStr;
|
||||
|
||||
public MemoryEntry(String name, long used, long total, long max) {
|
||||
this.name = name;
|
||||
this.used = used;
|
||||
this.total = total;
|
||||
this.max = max;
|
||||
|
||||
unitStr = "K";
|
||||
unit = 1024;
|
||||
if (used / 1024 / 1024 > 0) {
|
||||
unitStr = "M";
|
||||
unit = 1024 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryEntry(String name, MemoryUsage usage) {
|
||||
this(name, usage.getUsed(), usage.getCommitted(), usage.getMax());
|
||||
}
|
||||
|
||||
private String format(long value) {
|
||||
String valueStr = "-";
|
||||
if (value == -1) {
|
||||
return "-1";
|
||||
}
|
||||
if (value != Long.MIN_VALUE) {
|
||||
valueStr = value / unit + unitStr;
|
||||
}
|
||||
return valueStr;
|
||||
}
|
||||
|
||||
public void addTableRow(TableElement table) {
|
||||
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
|
||||
|
||||
table.row(name, format(used), format(total), format(max), String.format("%.2f%%", usage));
|
||||
}
|
||||
|
||||
public void addTableRow(TableElement table, Style.Composite style) {
|
||||
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
|
||||
|
||||
table.add(new RowElement().style(style).add(name, format(used), format(total), format(max),
|
||||
String.format("%.2f%%", usage)));
|
||||
}
|
||||
}
|
||||
|
||||
private class DashboardTimerTask extends TimerTask {
|
||||
private CommandProcess process;
|
||||
|
||||
public DashboardTimerTask(CommandProcess process) {
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (count >= getNumOfExecutions()) {
|
||||
// stop the timer
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
process.write("Process ends after " + getNumOfExecutions() + " time(s).\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
|
||||
int width = process.width();
|
||||
int height = process.height();
|
||||
|
||||
// 上半部分放thread top。下半部分再切分为田字格,其中上面两格放memory, gc的信息。下面两格放tomcat,
|
||||
// runtime的信息
|
||||
int totalHeight = height - 1;
|
||||
int threadTopHeight = totalHeight / 2;
|
||||
int lowerHalf = totalHeight - threadTopHeight;
|
||||
|
||||
int runtimeInfoHeight = lowerHalf / 2;
|
||||
int heapInfoHeight = lowerHalf - runtimeInfoHeight;
|
||||
|
||||
String threadInfo = drawThreadInfo(width, threadTopHeight);
|
||||
String memoryAndGc = drawMemoryInfoAndGcInfo(width, runtimeInfoHeight);
|
||||
String runTimeAndTomcat = drawRuntineInfoAndTomcatInfo(width, heapInfoHeight);
|
||||
|
||||
process.write(threadInfo + memoryAndGc + runTimeAndTomcat);
|
||||
|
||||
count++;
|
||||
process.times().incrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.handlers.command.CommandInterruptHandler;
|
||||
|
||||
import java.util.Timer;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-01-09 13:37.
|
||||
*/
|
||||
public class DashboardInterruptHandler extends CommandInterruptHandler {
|
||||
|
||||
private volatile Timer timer;
|
||||
|
||||
public DashboardInterruptHandler(CommandProcess process, Timer timer) {
|
||||
super(process);
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
timer.cancel();
|
||||
super.handle(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.advisor.Enhancer;
|
||||
import com.taobao.arthas.core.advisor.InvokeTraceable;
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
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.shell.handlers.command.CommandInterruptHandler;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.affect.EnhancerAffect;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
import com.taobao.middleware.cli.annotations.CLIConfigurator;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 29/11/2016.
|
||||
*/
|
||||
public abstract class EnhancerCommand extends AnnotatedCommand {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
private static final int SIZE_LIMIT = 50;
|
||||
private static final int MINIMAL_COMPLETE_SIZE = 3;
|
||||
protected static final List<String> EMPTY = Collections.emptyList();
|
||||
private static final String[] EXPRESS_EXAMPLES = { "params", "returnObj", "throwExp", "target", "clazz", "method",
|
||||
"{params,returnObj}", "params[0]" };
|
||||
|
||||
protected Matcher classNameMatcher;
|
||||
protected Matcher methodNameMatcher;
|
||||
|
||||
/**
|
||||
* 类名匹配
|
||||
*
|
||||
* @return 获取类名匹配
|
||||
*/
|
||||
protected abstract Matcher getClassNameMatcher();
|
||||
|
||||
/**
|
||||
* 方法名匹配
|
||||
*
|
||||
* @return 获取方法名匹配
|
||||
*/
|
||||
protected abstract Matcher getMethodNameMatcher();
|
||||
|
||||
/**
|
||||
* 获取监听器
|
||||
*
|
||||
* @return 返回监听器
|
||||
*/
|
||||
protected abstract AdviceListener getAdviceListener(CommandProcess process);
|
||||
|
||||
@Override
|
||||
public void process(final CommandProcess process) {
|
||||
// ctrl-C support
|
||||
process.interruptHandler(new CommandInterruptHandler(process));
|
||||
|
||||
// start to enhance
|
||||
enhance(process);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(Completion completion) {
|
||||
List<CliToken> tokens = completion.lineTokens();
|
||||
CliToken lastToken = tokens.get(tokens.size() - 1);
|
||||
|
||||
CompleteContext completeContext = getCompleteContext(completion);
|
||||
if (completeContext == null) {
|
||||
completeDefault(completion, lastToken);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (completeContext.getState()) {
|
||||
case INIT:
|
||||
if (completeClassName(completion)) {
|
||||
completeContext.setState(CompleteContext.CompleteState.CLASS_COMPLETED);
|
||||
}
|
||||
break;
|
||||
case CLASS_COMPLETED:
|
||||
if (completeMethodName(completion)) {
|
||||
completeContext.setState(CompleteContext.CompleteState.METHOD_COMPLETED);
|
||||
}
|
||||
break;
|
||||
case METHOD_COMPLETED:
|
||||
if (completeExpress(completion)) {
|
||||
completeContext.setState(CompleteContext.CompleteState.EXPRESS_COMPLETED);
|
||||
}
|
||||
break;
|
||||
case EXPRESS_COMPLETED:
|
||||
if (completeConditionExpress(completion)) {
|
||||
completeContext.setState(CompleteContext.CompleteState.CONDITION_EXPRESS_COMPLETED);
|
||||
}
|
||||
break;
|
||||
case CONDITION_EXPRESS_COMPLETED:
|
||||
completion.complete(EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
protected void enhance(CommandProcess process) {
|
||||
Session session = process.session();
|
||||
if (!session.tryLock()) {
|
||||
process.write("someone else is enhancing classes, pls. wait.\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
int lock = session.getLock();
|
||||
try {
|
||||
Instrumentation inst = session.getInstrumentation();
|
||||
AdviceListener listener = getAdviceListener(process);
|
||||
if (listener == null) {
|
||||
warn(process, "advice listener is null");
|
||||
return;
|
||||
}
|
||||
boolean skipJDKTrace = false;
|
||||
if(listener instanceof AbstractTraceAdviceListener) {
|
||||
skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace();
|
||||
}
|
||||
|
||||
EnhancerAffect effect = Enhancer.enhance(inst, lock, listener instanceof InvokeTraceable,
|
||||
skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher());
|
||||
|
||||
if (effect.cCnt() == 0 || effect.mCnt() == 0) {
|
||||
// no class effected
|
||||
// might be method code too large
|
||||
process.write("No class or method is affected, try:\n"
|
||||
+ "1. sm CLASS_NAME METHOD_NAME to make sure the method you are tracing actually exists (it might be in your parent class).\n"
|
||||
+ "2. reset CLASS_NAME and try again, your method body might be too large.\n"
|
||||
+ "3. visit middleware-container/arthas/issues/278 for more detail\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃
|
||||
if (session.getLock() == lock) {
|
||||
// 注册通知监听器
|
||||
process.register(lock, listener);
|
||||
if (process.isForeground()) {
|
||||
process.echoTips(Constants.ABORT_MSG + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
process.write(effect + "\n");
|
||||
} catch (UnmodifiableClassException e) {
|
||||
logger.error(null, "error happens when enhancing class", e);
|
||||
} finally {
|
||||
if (session.getLock() == lock) {
|
||||
// enhance结束后解锁
|
||||
process.session().unLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the class name is successfully completed
|
||||
*/
|
||||
protected boolean completeClassName(Completion completion) {
|
||||
CliToken lastToken = completion.lineTokens().get(completion.lineTokens().size() - 1);
|
||||
if (lastToken.value().length() >= MINIMAL_COMPLETE_SIZE) {
|
||||
// complete class name
|
||||
Set<Class<?>> results = SearchUtils.searchClassOnly(completion.session().getInstrumentation(),
|
||||
"*" + lastToken.value() + "*", SIZE_LIMIT);
|
||||
if (results.size() >= SIZE_LIMIT) {
|
||||
Iterator<Class<?>> it = results.iterator();
|
||||
List<String> res = new ArrayList<String>(SIZE_LIMIT);
|
||||
while (it.hasNext()) {
|
||||
res.add(it.next().getName());
|
||||
}
|
||||
res.add("and possibly more...");
|
||||
completion.complete(res);
|
||||
} else if (results.size() == 1) {
|
||||
Class<?> clazz = results.iterator().next();
|
||||
completion.complete(clazz.getName().substring(lastToken.value().length()), true);
|
||||
return true;
|
||||
} else {
|
||||
List<String> res = new ArrayList<String>(results.size());
|
||||
for (Class clazz : results) {
|
||||
res.add(clazz.getName());
|
||||
}
|
||||
completion.complete(res);
|
||||
}
|
||||
} else {
|
||||
// forget to call completion.complete will cause terminal to stuck.
|
||||
completion.complete(Collections.singletonList("Too many classes to display, "
|
||||
+ "please try to input at least 3 characters to get auto complete working."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean completeMethodName(Completion completion) {
|
||||
List<CliToken> tokens = completion.lineTokens();
|
||||
CliToken lastToken = completion.lineTokens().get(tokens.size() - 1);
|
||||
|
||||
// retrieve the class name
|
||||
String className;
|
||||
if (" ".equals(lastToken.value())) {
|
||||
// tokens = { " ", "CLASS_NAME", " "}
|
||||
className = tokens.get(tokens.size() - 2).value();
|
||||
} else {
|
||||
// tokens = { " ", "CLASS_NAME", " ", "PARTIAL_METHOD_NAME"}
|
||||
className = tokens.get(tokens.size() - 3).value();
|
||||
}
|
||||
|
||||
Set<Class<?>> results = SearchUtils.searchClassOnly(completion.session().getInstrumentation(), className, 2);
|
||||
if (results.isEmpty() || results.size() > 1) {
|
||||
// no class found or multiple class found
|
||||
completion.complete(EMPTY);
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> clazz = results.iterator().next();
|
||||
|
||||
List<String> res = new ArrayList<String>();
|
||||
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (" ".equals(lastToken.value())) {
|
||||
res.add(method.getName());
|
||||
} else if (method.getName().contains(lastToken.value())) {
|
||||
res.add(method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (res.size() == 1) {
|
||||
completion.complete(res.get(0).substring(lastToken.value().length()), true);
|
||||
return true;
|
||||
} else {
|
||||
completion.complete(res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean completeExpress(Completion completion) {
|
||||
return CompletionUtils.complete(completion, Arrays.asList(EXPRESS_EXAMPLES));
|
||||
}
|
||||
|
||||
protected boolean completeConditionExpress(Completion completion) {
|
||||
completion.complete(EMPTY);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void completeDefault(Completion completion, CliToken lastToken) {
|
||||
CLI cli = CLIConfigurator.define(this.getClass());
|
||||
List<com.taobao.middleware.cli.Option> options = cli.getOptions();
|
||||
if (lastToken == null || lastToken.isBlank()) {
|
||||
// complete usage
|
||||
CompletionUtils.completeUsage(completion, cli);
|
||||
} else if (lastToken.value().startsWith("--")) {
|
||||
// complete long option
|
||||
CompletionUtils.completeLongOption(completion, lastToken, options);
|
||||
} else if (lastToken.value().startsWith("-")) {
|
||||
// complete short option
|
||||
CompletionUtils.completeShortOption(completion, lastToken, options);
|
||||
} else {
|
||||
completion.complete(EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
private CompleteContext getCompleteContext(Completion completion) {
|
||||
CompleteContext completeContext = new CompleteContext();
|
||||
List<CliToken> tokens = completion.lineTokens();
|
||||
CliToken lastToken = tokens.get(tokens.size() - 1);
|
||||
|
||||
if (lastToken.value().startsWith("-") || lastToken.value().startsWith("--")) {
|
||||
// this is the default case
|
||||
return null;
|
||||
}
|
||||
|
||||
int tokenCount = 0;
|
||||
|
||||
for (CliToken token : tokens) {
|
||||
if (" ".equals(token.value()) || token.value().startsWith("-") || token.value().startsWith("--")) {
|
||||
// filter irrelevant tokens
|
||||
continue;
|
||||
}
|
||||
tokenCount++;
|
||||
}
|
||||
|
||||
for (CompleteContext.CompleteState state : CompleteContext.CompleteState.values()) {
|
||||
if (tokenCount == state.ordinal() || tokenCount == state.ordinal() + 1 && !" ".equals(lastToken.value())) {
|
||||
completeContext.setState(state);
|
||||
return completeContext;
|
||||
}
|
||||
}
|
||||
|
||||
return completeContext;
|
||||
}
|
||||
|
||||
private static void warn(CommandProcess process, String message) {
|
||||
logger.error(null, message);
|
||||
process.write("cannot operate the current command, pls. check arthas.log\n");
|
||||
if (process.isForeground()) {
|
||||
process.echoTips(Constants.ABORT_MSG + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.command.ScriptSupportCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
|
||||
/**
|
||||
* Groovy support has been completed dropped in Arthas 3.0 because of severer memory leak.
|
||||
* @author beiwei30 on 01/12/2016.
|
||||
*/
|
||||
@Deprecated
|
||||
public class GroovyAdviceListener extends ReflectAdviceListenerAdapter {
|
||||
private ScriptSupportCommand.ScriptListener scriptListener;
|
||||
private ScriptSupportCommand.Output output;
|
||||
|
||||
public GroovyAdviceListener(ScriptSupportCommand.ScriptListener scriptListener, CommandProcess process) {
|
||||
this.scriptListener = scriptListener;
|
||||
this.output = new CommandProcessAdaptor(process);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
scriptListener.create(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
scriptListener.destroy(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
scriptListener.before(output, Advice.newForBefore(loader, clazz, method, target, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
scriptListener.afterReturning(output, Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Throwable throwable) throws Throwable {
|
||||
scriptListener.afterThrowing(output, Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable));
|
||||
}
|
||||
|
||||
private static class CommandProcessAdaptor implements ScriptSupportCommand.Output {
|
||||
private CommandProcess process;
|
||||
|
||||
public CommandProcessAdaptor(CommandProcess process) {
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptSupportCommand.Output print(String string) {
|
||||
process.write(string);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptSupportCommand.Output println(String string) {
|
||||
process.write(string).write("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptSupportCommand.Output finish() {
|
||||
process.end();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.ScriptSupportCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.middleware.cli.annotations.Argument;
|
||||
import com.taobao.middleware.cli.annotations.Description;
|
||||
import com.taobao.middleware.cli.annotations.Hidden;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Option;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
/**
|
||||
* Groovy support has been completed dropped in Arthas 3.0 because of severer memory leak.
|
||||
* 脚本增强命令
|
||||
*
|
||||
* @author vlinux on 15/5/31.
|
||||
*/
|
||||
@Name("groovy")
|
||||
@Hidden
|
||||
@Summary("Enhanced Groovy")
|
||||
@Description("Examples:\n" +
|
||||
" groovy -E org\\.apache\\.commons\\.lang\\.StringUtils isBlank /tmp/watch.groovy\n" +
|
||||
" groovy org.apache.commons.lang.StringUtils isBlank /tmp/watch.groovy\n" +
|
||||
" groovy *StringUtils isBlank /tmp/watch.groovy\n" +
|
||||
"\n" +
|
||||
"WIKI:\n" +
|
||||
" middleware-container/arthas/wikis/cmds/groovy")
|
||||
@Deprecated
|
||||
public class GroovyScriptCommand extends EnhancerCommand implements ScriptSupportCommand {
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private String scriptFilepath;
|
||||
private boolean isRegEx = false;
|
||||
|
||||
@Argument(index = 0, argName = "class-pattern")
|
||||
@Description("Path and classname of Pattern Matching")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 1, argName = "method-pattern")
|
||||
@Description("Method of Pattern Matching")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 2, argName = "script-filepath")
|
||||
@Description("Filepath of Groovy script")
|
||||
public void setScriptFilepath(String scriptFilepath) {
|
||||
this.scriptFilepath = scriptFilepath;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex")
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public String getScriptFilepath() {
|
||||
return scriptFilepath;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getClassNameMatcher() {
|
||||
throw new UnsupportedOperationException("groovy command is not supported yet!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getMethodNameMatcher() {
|
||||
throw new UnsupportedOperationException("groovy command is not supported yet!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(CommandProcess process) {
|
||||
throw new UnsupportedOperationException("groovy command is not supported yet!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.affect.RowAffect;
|
||||
import com.taobao.middleware.cli.annotations.Description;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.management.ClassLoadingMXBean;
|
||||
import java.lang.management.CompilationMXBean;
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryManagerMXBean;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* JVM info command
|
||||
*
|
||||
* @author vlinux on 15/6/6.
|
||||
*/
|
||||
@Name("jvm")
|
||||
@Summary("Display the target JVM information")
|
||||
@Description(Constants.WIKI + Constants.WIKI_HOME + "cmds/jvm")
|
||||
public class JvmCommand extends AnnotatedCommand {
|
||||
|
||||
private final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
|
||||
private final ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
|
||||
private final CompilationMXBean compilationMXBean = ManagementFactory.getCompilationMXBean();
|
||||
private final Collection<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
|
||||
private final Collection<MemoryManagerMXBean> memoryManagerMXBeans = ManagementFactory.getMemoryManagerMXBeans();
|
||||
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||
// private final Collection<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
|
||||
private final OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
TableElement table = new TableElement(2, 5).leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(true, label("RUNTIME").style(Decoration.bold.bold()));
|
||||
drawRuntimeTable(table);
|
||||
table.row("", "");
|
||||
table.row(true, label("CLASS-LOADING").style(Decoration.bold.bold()));
|
||||
drawClassLoadingTable(table);
|
||||
table.row("", "");
|
||||
table.row(true, label("COMPILATION").style(Decoration.bold.bold()));
|
||||
drawCompilationTable(table);
|
||||
|
||||
if (!garbageCollectorMXBeans.isEmpty()) {
|
||||
table.row("", "");
|
||||
table.row(true, label("GARBAGE-COLLECTORS").style(Decoration.bold.bold()));
|
||||
drawGarbageCollectorsTable(table);
|
||||
}
|
||||
|
||||
if (!memoryManagerMXBeans.isEmpty()) {
|
||||
table.row("", "");
|
||||
table.row(true, label("MEMORY-MANAGERS").style(Decoration.bold.bold()));
|
||||
drawMemoryManagersTable(table);
|
||||
}
|
||||
|
||||
table.row("", "");
|
||||
table.row(true, label("MEMORY").style(Decoration.bold.bold()));
|
||||
drawMemoryTable(table);
|
||||
table.row("", "");
|
||||
table.row(true, label("OPERATING-SYSTEM").style(Decoration.bold.bold()));
|
||||
drawOperatingSystemMXBeanTable(table);
|
||||
table.row("", "");
|
||||
table.row(true, label("THREAD").style(Decoration.bold.bold()));
|
||||
drawThreadTable(table);
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()));
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private String toCol(Collection<String> strings) {
|
||||
final StringBuilder colSB = new StringBuilder();
|
||||
if (strings.isEmpty()) {
|
||||
colSB.append("[]");
|
||||
} else {
|
||||
for (String str : strings) {
|
||||
colSB.append(str).append("\n");
|
||||
}
|
||||
}
|
||||
return colSB.toString();
|
||||
}
|
||||
|
||||
private String toCol(String... stringArray) {
|
||||
final StringBuilder colSB = new StringBuilder();
|
||||
if (null == stringArray
|
||||
|| stringArray.length == 0) {
|
||||
colSB.append("[]");
|
||||
} else {
|
||||
for (String str : stringArray) {
|
||||
colSB.append(str).append("\n");
|
||||
}
|
||||
}
|
||||
return colSB.toString();
|
||||
}
|
||||
|
||||
private Element drawRuntimeTable(TableElement table) {
|
||||
String bootClassPath = "";
|
||||
try {
|
||||
bootClassPath = runtimeMXBean.getBootClassPath();
|
||||
} catch (Exception e) {
|
||||
// under jdk9 will throw UnsupportedOperationException, ignore
|
||||
}
|
||||
table.row("MACHINE-NAME", runtimeMXBean.getName())
|
||||
.row("JVM-START-TIME", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(runtimeMXBean.getStartTime())))
|
||||
.row("MANAGEMENT-SPEC-VERSION", runtimeMXBean.getManagementSpecVersion())
|
||||
.row("SPEC-NAME", runtimeMXBean.getSpecName())
|
||||
.row("SPEC-VENDOR", runtimeMXBean.getSpecVendor())
|
||||
.row("SPEC-VERSION", runtimeMXBean.getSpecVersion())
|
||||
.row("VM-NAME", runtimeMXBean.getVmName())
|
||||
.row("VM-VENDOR", runtimeMXBean.getVmVendor())
|
||||
.row("VM-VERSION", runtimeMXBean.getVmVersion())
|
||||
.row("INPUT-ARGUMENTS", toCol(runtimeMXBean.getInputArguments()))
|
||||
.row("CLASS-PATH", runtimeMXBean.getClassPath())
|
||||
.row("BOOT-CLASS-PATH", bootClassPath)
|
||||
.row("LIBRARY-PATH", runtimeMXBean.getLibraryPath());
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element drawClassLoadingTable(TableElement table) {
|
||||
table.row("LOADED-CLASS-COUNT", "" + classLoadingMXBean.getLoadedClassCount())
|
||||
.row("TOTAL-LOADED-CLASS-COUNT", "" + classLoadingMXBean.getTotalLoadedClassCount())
|
||||
.row("UNLOADED-CLASS-COUNT", "" + classLoadingMXBean.getUnloadedClassCount())
|
||||
.row("IS-VERBOSE", "" + classLoadingMXBean.isVerbose());
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element drawCompilationTable(TableElement table) {
|
||||
table.row("NAME", compilationMXBean.getName());
|
||||
|
||||
if (compilationMXBean.isCompilationTimeMonitoringSupported()) {
|
||||
table.row("TOTAL-COMPILE-TIME", compilationMXBean.getTotalCompilationTime() + "(ms)");
|
||||
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element drawGarbageCollectorsTable(TableElement table) {
|
||||
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMXBeans) {
|
||||
table.row(garbageCollectorMXBean.getName() + "\n[count/time]",
|
||||
garbageCollectorMXBean.getCollectionCount() + "/" + garbageCollectorMXBean.getCollectionTime() + "(ms)");
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element drawMemoryManagersTable(TableElement table) {
|
||||
for (final MemoryManagerMXBean memoryManagerMXBean : memoryManagerMXBeans) {
|
||||
if (memoryManagerMXBean.isValid()) {
|
||||
final String name = memoryManagerMXBean.isValid()
|
||||
? memoryManagerMXBean.getName()
|
||||
: memoryManagerMXBean.getName() + "(Invalid)";
|
||||
|
||||
|
||||
table.row(name, toCol(memoryManagerMXBean.getMemoryPoolNames()));
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element drawMemoryTable(TableElement table) {
|
||||
table.row("HEAP-MEMORY-USAGE\n[committed/init/max/used]",
|
||||
memoryMXBean.getHeapMemoryUsage().getCommitted()
|
||||
+ "/" + memoryMXBean.getHeapMemoryUsage().getInit()
|
||||
+ "/" + memoryMXBean.getHeapMemoryUsage().getMax()
|
||||
+ "/" + memoryMXBean.getHeapMemoryUsage().getUsed()
|
||||
);
|
||||
|
||||
table.row("NO-HEAP-MEMORY-USAGE\n[committed/init/max/used]",
|
||||
memoryMXBean.getNonHeapMemoryUsage().getCommitted()
|
||||
+ "/" + memoryMXBean.getNonHeapMemoryUsage().getInit()
|
||||
+ "/" + memoryMXBean.getNonHeapMemoryUsage().getMax()
|
||||
+ "/" + memoryMXBean.getNonHeapMemoryUsage().getUsed()
|
||||
);
|
||||
|
||||
table.row("PENDING-FINALIZE-COUNT", "" + memoryMXBean.getObjectPendingFinalizationCount());
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
private Element drawOperatingSystemMXBeanTable(TableElement table) {
|
||||
table.row("OS", operatingSystemMXBean.getName()).row("ARCH", operatingSystemMXBean.getArch())
|
||||
.row("PROCESSORS-COUNT", "" + operatingSystemMXBean.getAvailableProcessors())
|
||||
.row("LOAD-AVERAGE", "" + operatingSystemMXBean.getSystemLoadAverage())
|
||||
.row("VERSION", operatingSystemMXBean.getVersion());
|
||||
return table;
|
||||
}
|
||||
|
||||
private Element drawThreadTable(TableElement table) {
|
||||
table.row("COUNT", "" + threadMXBean.getThreadCount())
|
||||
.row("DAEMON-COUNT", "" + threadMXBean.getDaemonThreadCount())
|
||||
.row("LIVE-COUNT", "" + threadMXBean.getPeakThreadCount())
|
||||
.row("STARTED-COUNT", "" + threadMXBean.getTotalStartedThreadCount());
|
||||
return table;
|
||||
}
|
||||
|
||||
}
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.util.ThreadLocalWatch;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
|
||||
/**
|
||||
* 输出的内容格式为:<br/>
|
||||
* <style type="text/css">
|
||||
* table, th, td {
|
||||
* borders:1px solid #cccccc;
|
||||
* borders-collapse:collapse;
|
||||
* }
|
||||
* </style>
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>时间戳</th>
|
||||
* <th>统计周期(s)</th>
|
||||
* <th>类全路径</th>
|
||||
* <th>方法名</th>
|
||||
* <th>调用总次数</th>
|
||||
* <th>成功次数</th>
|
||||
* <th>失败次数</th>
|
||||
* <th>平均耗时(ms)</th>
|
||||
* <th>失败率</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>2012-11-07 05:00:01</td>
|
||||
* <td>120</td>
|
||||
* <td>com.taobao.item.ItemQueryServiceImpl</td>
|
||||
* <td>queryItemForDetail</td>
|
||||
* <td>1500</td>
|
||||
* <td>1000</td>
|
||||
* <td>500</td>
|
||||
* <td>15</td>
|
||||
* <td>30%</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>2012-11-07 05:00:01</td>
|
||||
* <td>120</td>
|
||||
* <td>com.taobao.item.ItemQueryServiceImpl</td>
|
||||
* <td>queryItemById</td>
|
||||
* <td>900</td>
|
||||
* <td>900</td>
|
||||
* <td>0</td>
|
||||
* <td>7</td>
|
||||
* <td>0%</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @author beiwei30 on 28/11/2016.
|
||||
*/
|
||||
class MonitorAdviceListener extends ReflectAdviceListenerAdapter {
|
||||
// 输出定时任务
|
||||
private Timer timer;
|
||||
// 监控数据
|
||||
private ConcurrentHashMap<Key, AtomicReference<Data>> monitorData = new ConcurrentHashMap<Key, AtomicReference<Data>>();
|
||||
private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
|
||||
private MonitorCommand command;
|
||||
private CommandProcess process;
|
||||
|
||||
MonitorAdviceListener(MonitorCommand command, CommandProcess process) {
|
||||
this.command = command;
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void create() {
|
||||
if (timer == null) {
|
||||
timer = new Timer("Timer-for-arthas-monitor-" + process.session().getSessionId(), true);
|
||||
timer.scheduleAtFixedRate(new MonitorTimer(monitorData, process, command.getNumberOfLimit()),
|
||||
0, command.getCycle() * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void destroy() {
|
||||
if (null != timer) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
threadLocalWatch.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target,
|
||||
Object[] args, Object returnObject) throws Throwable {
|
||||
finishing(clazz, method, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target,
|
||||
Object[] args, Throwable throwable) {
|
||||
finishing(clazz, method, true);
|
||||
}
|
||||
|
||||
private void finishing(Class<?> clazz, ArthasMethod method, boolean isThrowing) {
|
||||
double cost = threadLocalWatch.costInMillis();
|
||||
final Key key = new Key(clazz.getName(), method.getName());
|
||||
|
||||
while (true) {
|
||||
AtomicReference<Data> value = monitorData.get(key);
|
||||
if (null == value) {
|
||||
monitorData.putIfAbsent(key, new AtomicReference<Data>(new Data()));
|
||||
continue;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Data oData = value.get();
|
||||
Data nData = new Data();
|
||||
nData.setCost(oData.getCost() + cost);
|
||||
if (isThrowing) {
|
||||
nData.setFailed(oData.getFailed() + 1);
|
||||
nData.setSuccess(oData.getSuccess());
|
||||
} else {
|
||||
nData.setFailed(oData.getFailed());
|
||||
nData.setSuccess(oData.getSuccess() + 1);
|
||||
}
|
||||
nData.setTotal(oData.getTotal() + 1);
|
||||
if (value.compareAndSet(oData, nData)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private class MonitorTimer extends TimerTask {
|
||||
private Map<Key, AtomicReference<Data>> monitorData;
|
||||
private CommandProcess process;
|
||||
private int limit;
|
||||
|
||||
MonitorTimer(Map<Key, AtomicReference<Data>> monitorData, CommandProcess process, int limit) {
|
||||
this.monitorData = monitorData;
|
||||
this.process = process;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (monitorData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 超过次数上限,则不在输出,命令终止
|
||||
if (process.times().getAndIncrement() >= limit) {
|
||||
this.cancel();
|
||||
abortProcess(process, limit);
|
||||
return;
|
||||
}
|
||||
|
||||
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
table.row(true, label("timestamp").style(Decoration.bold.bold()),
|
||||
label("class").style(Decoration.bold.bold()),
|
||||
label("method").style(Decoration.bold.bold()),
|
||||
label("total").style(Decoration.bold.bold()),
|
||||
label("success").style(Decoration.bold.bold()),
|
||||
label("fail").style(Decoration.bold.bold()),
|
||||
label("avg-rt(ms)").style(Decoration.bold.bold()),
|
||||
label("fail-rate").style(Decoration.bold.bold()));
|
||||
|
||||
for (Map.Entry<Key, AtomicReference<Data>> entry : monitorData.entrySet()) {
|
||||
final AtomicReference<Data> value = entry.getValue();
|
||||
|
||||
Data data;
|
||||
while (true) {
|
||||
data = value.get();
|
||||
if (value.compareAndSet(data, new Data())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null != data) {
|
||||
|
||||
final DecimalFormat df = new DecimalFormat("0.00");
|
||||
|
||||
table.row(
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),
|
||||
entry.getKey().getClassName(),
|
||||
entry.getKey().getMethodName(),
|
||||
"" + data.getTotal(),
|
||||
"" + data.getSuccess(),
|
||||
"" + data.getFailed(),
|
||||
df.format(div(data.getCost(), data.getTotal())),
|
||||
df.format(100.0d * div(data.getFailed(), data.getTotal())) + "%"
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()) + "\n");
|
||||
}
|
||||
|
||||
private double div(double a, double b) {
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
}
|
||||
return a / b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据监控用的Key
|
||||
*
|
||||
* @author vlinux
|
||||
*/
|
||||
private static class Key {
|
||||
private final String className;
|
||||
private final String methodName;
|
||||
|
||||
Key(String className, String behaviorName) {
|
||||
this.className = className;
|
||||
this.methodName = behaviorName;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return className.hashCode() + methodName.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (null == obj
|
||||
|| !(obj instanceof Key)) {
|
||||
return false;
|
||||
}
|
||||
Key okey = (Key) obj;
|
||||
return isEquals(okey.className, className) && isEquals(okey.methodName, methodName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据监控用的value
|
||||
*
|
||||
* @author vlinux
|
||||
*/
|
||||
private static class Data {
|
||||
private int total;
|
||||
private int success;
|
||||
private int failed;
|
||||
private double cost;
|
||||
|
||||
public int getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public int getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(int success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public int getFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public void setFailed(int failed) {
|
||||
this.failed = failed;
|
||||
}
|
||||
|
||||
public double getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 监控请求命令<br/>
|
||||
* @author vlinux
|
||||
*/
|
||||
@Name("monitor")
|
||||
@Summary("Monitor method execution statistics, e.g. total/success/failure count, average rt, fail rate, etc. ")
|
||||
@Description("\nExamples:\n" +
|
||||
" monitor org.apache.commons.lang.StringUtils isBlank\n" +
|
||||
" monitor org.apache.commons.lang.StringUtils isBlank -c 5\n" +
|
||||
" monitor -E org\\.apache\\.commons\\.lang\\.StringUtils isBlank\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/monitor")
|
||||
public class MonitorCommand extends EnhancerCommand {
|
||||
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private int cycle = 60;
|
||||
private boolean isRegEx = false;
|
||||
private int numberOfLimit = 100;
|
||||
|
||||
@Argument(argName = "class-pattern", index = 0)
|
||||
@Description("Path and classname of Pattern Matching")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "method-pattern", index = 1)
|
||||
@Description("Method of Pattern Matching")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Option(shortName = "c", longName = "cycle")
|
||||
@Description("The monitor interval (in seconds), 60 seconds by default")
|
||||
public void setCycle(int cycle) {
|
||||
this.cycle = cycle;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex")
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "limits")
|
||||
@Description("Threshold of execution times")
|
||||
public void setNumberOfLimit(int numberOfLimit) {
|
||||
this.numberOfLimit = numberOfLimit;
|
||||
}
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public int getCycle() {
|
||||
return cycle;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
public int getNumberOfLimit() {
|
||||
return numberOfLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getClassNameMatcher() {
|
||||
if (classNameMatcher == null) {
|
||||
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
|
||||
}
|
||||
return classNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getMethodNameMatcher() {
|
||||
if (methodNameMatcher == null) {
|
||||
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
|
||||
}
|
||||
return methodNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(CommandProcess process) {
|
||||
final AdviceListener listener = new MonitorAdviceListener(this, process);
|
||||
/*
|
||||
* 通过handle回调,在suspend时停止timer,resume时重启timer
|
||||
*/
|
||||
process.suspendHandler(new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
listener.destroy();
|
||||
}
|
||||
});
|
||||
process.resumeHandler(new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
listener.create();
|
||||
}
|
||||
});
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean completeExpress(Completion completion) {
|
||||
completion.complete(EMPTY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-01-05 13:59.
|
||||
*/
|
||||
public class PathTraceAdviceListener extends AbstractTraceAdviceListener {
|
||||
|
||||
public PathTraceAdviceListener(TraceCommand command, CommandProcess process) {
|
||||
super(command, process);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.util.DateUtils;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.ThreadLocalWatch;
|
||||
import com.taobao.arthas.core.util.ThreadUtil;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 29/11/2016.
|
||||
*/
|
||||
public class StackAdviceListener extends ReflectAdviceListenerAdapter {
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private final ThreadLocal<String> stackThreadLocal = new ThreadLocal<String>();
|
||||
private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
|
||||
private StackCommand command;
|
||||
private CommandProcess process;
|
||||
|
||||
public StackAdviceListener(StackCommand command, CommandProcess process) {
|
||||
this.command = command;
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
stackThreadLocal.set(ThreadUtil.getThreadStack(Thread.currentThread()));
|
||||
// 开始计算本次方法调用耗时
|
||||
threadLocalWatch.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Throwable throwable) throws Throwable {
|
||||
Advice advice = Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable);
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
Advice advice = Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject);
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
private void finishing(Advice advice) {
|
||||
// 本次调用的耗时
|
||||
try {
|
||||
double cost = threadLocalWatch.costInMillis();
|
||||
if (isConditionMet(command.getConditionExpress(), advice, cost)) {
|
||||
// TODO: concurrency issues for process.write
|
||||
process.write("ts=" + DateUtils.getCurrentDate() + ";" + stackThreadLocal.get() + "\n");
|
||||
process.times().incrementAndGet();
|
||||
if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) {
|
||||
abortProcess(process, command.getNumberOfLimit());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("stack failed.", e);
|
||||
process.write("stack failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage()
|
||||
+ ", visit " + LogUtil.LOGGER_FILE + " for more details.\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Jstack命令<br/>
|
||||
* 负责输出当前方法执行上下文
|
||||
*
|
||||
* @author vlinux
|
||||
* @author hengyunabc 2016-10-31
|
||||
*/
|
||||
@Name("stack")
|
||||
@Summary("Display the stack trace for the specified class and method")
|
||||
@Description(Constants.EXPRESS_DESCRIPTION + Constants.EXAMPLE +
|
||||
" stack -E org\\.apache\\.commons\\.lang\\.StringUtils isBlank\n" +
|
||||
" stack org.apache.commons.lang.StringUtils isBlank\n" +
|
||||
" stack *StringUtils isBlank\n" +
|
||||
" stack *StringUtils isBlank params[0].length==1\n" +
|
||||
" stack *StringUtils isBlank '#cost>100'\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/stack")
|
||||
public class StackCommand extends EnhancerCommand {
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private String conditionExpress;
|
||||
private boolean isRegEx = false;
|
||||
private int numberOfLimit = 100;
|
||||
|
||||
@Argument(index = 0, argName = "class-pattern")
|
||||
@Description("Path and classname of Pattern Matching")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 1, argName = "method-pattern", required = false)
|
||||
@Description("Method of Pattern Matching")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 2, argName = "condition-express", required = false)
|
||||
@Description(Constants.CONDITION_EXPRESS)
|
||||
public void setConditionExpress(String conditionExpress) {
|
||||
this.conditionExpress = conditionExpress;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "limits")
|
||||
@Description("Threshold of execution times")
|
||||
public void setNumberOfLimit(int numberOfLimit) {
|
||||
this.numberOfLimit = numberOfLimit;
|
||||
}
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public String getConditionExpress() {
|
||||
return conditionExpress;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
public int getNumberOfLimit() {
|
||||
return numberOfLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getClassNameMatcher() {
|
||||
if (classNameMatcher == null) {
|
||||
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
|
||||
}
|
||||
return classNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getMethodNameMatcher() {
|
||||
if (methodNameMatcher == null) {
|
||||
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
|
||||
}
|
||||
return methodNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(CommandProcess process) {
|
||||
return new StackAdviceListener(this, process);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean completeExpress(Completion completion) {
|
||||
completion.complete(EMPTY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.util.ArrayUtils;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.ThreadUtil;
|
||||
import com.taobao.arthas.core.util.affect.Affect;
|
||||
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.renderers.ThreadRenderer;
|
||||
import com.taobao.text.ui.LabelElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.lang.Thread.State;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author hengyunabc 2015年12月7日 下午2:06:21
|
||||
*/
|
||||
@Name("thread")
|
||||
@Summary("Display thread info, thread stack")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" thread\n" +
|
||||
" thread 51\n" +
|
||||
" thread -n -1\n" +
|
||||
" thread -n 5\n" +
|
||||
" thread -b\n" +
|
||||
" thread -i 2000\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/thread")
|
||||
public class ThreadCommand extends AnnotatedCommand {
|
||||
|
||||
private static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
||||
|
||||
private long id = -1;
|
||||
private Integer topNBusy = null;
|
||||
private boolean findMostBlockingThread = false;
|
||||
private int sampleInterval = 100;
|
||||
|
||||
@Argument(index = 0, required = false, argName = "id")
|
||||
@Description("Show thread stack")
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "top-n-threads")
|
||||
@Description("The number of thread(s) to show, ordered by cpu utilization, -1 to show all.")
|
||||
public void setTopNBusy(Integer topNBusy) {
|
||||
this.topNBusy = topNBusy;
|
||||
}
|
||||
|
||||
@Option(shortName = "b", longName = "include-blocking-thread", flag = true)
|
||||
@Description("Find the thread who is holding a lock that blocks the most number of threads.")
|
||||
public void setFindMostBlockingThread(boolean findMostBlockingThread) {
|
||||
this.findMostBlockingThread = findMostBlockingThread;
|
||||
}
|
||||
|
||||
@Option(shortName = "i", longName = "sample-interval")
|
||||
@Description("Specify the sampling interval (in ms) when calculating cpu usage.")
|
||||
public void setSampleInterval(int sampleInterval) {
|
||||
this.sampleInterval = sampleInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
Affect affect = new RowAffect();
|
||||
try {
|
||||
if (id > 0) {
|
||||
processThread(process);
|
||||
} else if (topNBusy != null) {
|
||||
processTopBusyThreads(process);
|
||||
} else if (findMostBlockingThread) {
|
||||
processBlockingThread(process);
|
||||
} else {
|
||||
processAllThreads(process);
|
||||
}
|
||||
} finally {
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void processAllThreads(CommandProcess process) {
|
||||
Map<String, Thread> threads = ThreadUtil.getThreads();
|
||||
|
||||
// 统计各种线程状态
|
||||
StringBuilder threadStat = new StringBuilder();
|
||||
Map<State, Integer> stateCountMap = new HashMap<State, Integer>();
|
||||
for (State s : State.values()) {
|
||||
stateCountMap.put(s, 0);
|
||||
}
|
||||
|
||||
for (Thread thread : threads.values()) {
|
||||
State threadState = thread.getState();
|
||||
Integer count = stateCountMap.get(threadState);
|
||||
stateCountMap.put(threadState, count + 1);
|
||||
}
|
||||
|
||||
threadStat.append("Threads Total: ").append(threads.values().size());
|
||||
for (State s : State.values()) {
|
||||
Integer count = stateCountMap.get(s);
|
||||
threadStat.append(", ").append(s.name()).append(": ").append(count);
|
||||
}
|
||||
|
||||
String stat = RenderUtil.render(new LabelElement(threadStat), process.width());
|
||||
String content = RenderUtil.render(threads.values().iterator(),
|
||||
new ThreadRenderer(sampleInterval), process.width());
|
||||
process.write(stat + content);
|
||||
}
|
||||
|
||||
private void processBlockingThread(CommandProcess process) {
|
||||
ThreadUtil.BlockingLockInfo blockingLockInfo = ThreadUtil.findMostBlockingLock();
|
||||
|
||||
if (blockingLockInfo.threadInfo == null) {
|
||||
process.write("No most blocking thread found!\n");
|
||||
} else {
|
||||
String stacktrace = ThreadUtil.getFullStacktrace(blockingLockInfo);
|
||||
process.write(stacktrace);
|
||||
}
|
||||
}
|
||||
|
||||
private void processTopBusyThreads(CommandProcess process) {
|
||||
Map<Long, Long> topNThreads = ThreadUtil.getTopNThreads(sampleInterval, topNBusy);
|
||||
Long[] tids = topNThreads.keySet().toArray(new Long[topNThreads.keySet().size()]);
|
||||
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ArrayUtils.toPrimitive(tids), true, true);
|
||||
if (threadInfos == null) {
|
||||
process.write("thread do not exist! id: " + id + "\n");
|
||||
} else {
|
||||
for (ThreadInfo info : threadInfos) {
|
||||
String stacktrace = ThreadUtil.getFullStacktrace(info, topNThreads.get(info.getThreadId()));
|
||||
process.write(stacktrace + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processThread(CommandProcess process) {
|
||||
String content;
|
||||
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(new long[]{id}, true, true);
|
||||
if (threadInfos == null || threadInfos[0] == null) {
|
||||
content = "thread do not exist! id: " + id + "\n";
|
||||
} else {
|
||||
// no cpu usage info
|
||||
content = ThreadUtil.getFullStacktrace(threadInfos[0], -1);
|
||||
}
|
||||
process.write(content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 时间碎片
|
||||
*/
|
||||
class TimeFragment {
|
||||
|
||||
public TimeFragment(Advice advice, Date gmtCreate, double cost) {
|
||||
this.advice = advice;
|
||||
this.gmtCreate = gmtCreate;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
private final Advice advice;
|
||||
private final Date gmtCreate;
|
||||
private final double cost;
|
||||
|
||||
public Advice getAdvice() {
|
||||
return advice;
|
||||
}
|
||||
|
||||
public Date getGmtCreate() {
|
||||
return gmtCreate;
|
||||
}
|
||||
|
||||
public double getCost() {
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.ThreadLocalWatch;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.createTable;
|
||||
import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.fillTableHeader;
|
||||
import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.fillTableRow;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 30/11/2016.
|
||||
*/
|
||||
public class TimeTunnelAdviceListener extends ReflectAdviceListenerAdapter {
|
||||
|
||||
private TimeTunnelCommand command;
|
||||
private CommandProcess process;
|
||||
|
||||
// 第一次启动标记
|
||||
private volatile boolean isFirst = true;
|
||||
|
||||
// 方法执行时间戳
|
||||
private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
|
||||
|
||||
public TimeTunnelAdviceListener(TimeTunnelCommand command, CommandProcess process) {
|
||||
this.command = command;
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
threadLocalWatch.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
afterFinishing(Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Throwable throwable) {
|
||||
afterFinishing(Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable));
|
||||
}
|
||||
|
||||
private void afterFinishing(Advice advice) {
|
||||
double cost = threadLocalWatch.costInMillis();
|
||||
TimeFragment timeTunnel = new TimeFragment(advice, new Date(), cost);
|
||||
|
||||
// reset the timestamp
|
||||
threadLocalWatch.clear();
|
||||
|
||||
boolean match = false;
|
||||
try {
|
||||
match = isConditionMet(command.getConditionExpress(), advice, cost);
|
||||
} catch (ExpressException e) {
|
||||
LogUtil.getArthasLogger().warn("tt failed.", e);
|
||||
process.write("tt failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage()
|
||||
+ ", visit " + LogUtil.LOGGER_FILE + " for more details.\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = command.putTimeTunnel(timeTunnel);
|
||||
TableElement table = createTable();
|
||||
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
|
||||
// 填充表格头部
|
||||
fillTableHeader(table);
|
||||
}
|
||||
|
||||
// 填充表格内容
|
||||
fillTableRow(table, index, timeTunnel);
|
||||
|
||||
// TODO: concurrency issues for process.write
|
||||
process.write(RenderUtil.render(table, process.width()));
|
||||
process.times().incrementAndGet();
|
||||
if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) {
|
||||
abortProcess(process, command.getNumberOfLimit());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.command.express.ExpressFactory;
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.shell.handlers.command.CommandInterruptHandler;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.util.affect.RowAffect;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.view.ObjectView;
|
||||
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.ui.TableElement;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.lang.Integer.toHexString;
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* 时光隧道命令<br/>
|
||||
* 参数w/d依赖于参数i所传递的记录编号<br/>
|
||||
*
|
||||
* @author vlinux on 14/11/15.
|
||||
*/
|
||||
@Name("tt")
|
||||
@Summary("Time Tunnel")
|
||||
@Description(Constants.EXPRESS_DESCRIPTION + Constants.EXAMPLE +
|
||||
" tt -t *StringUtils isEmpty\n" +
|
||||
" tt -t *StringUtils isEmpty params[0].length==1\n" +
|
||||
" tt -l\n" +
|
||||
" tt -D\n" +
|
||||
" tt -i 1000 -w params[0]\n" +
|
||||
" tt -i 1000 -d\n" +
|
||||
" tt -i 1000\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/tt")
|
||||
public class TimeTunnelCommand extends EnhancerCommand {
|
||||
// 时间隧道(时间碎片的集合)
|
||||
private static final Map<Integer, TimeFragment> timeFragmentMap = new LinkedHashMap<Integer, TimeFragment>();
|
||||
// 时间碎片序列生成器
|
||||
private static final AtomicInteger sequence = new AtomicInteger(1000);
|
||||
// TimeTunnel the method call
|
||||
private boolean isTimeTunnel = false;
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private String conditionExpress;
|
||||
// list the TimeTunnel
|
||||
private boolean isList = false;
|
||||
private boolean isDeleteAll = false;
|
||||
// index of TimeTunnel
|
||||
private Integer index;
|
||||
// expand of TimeTunnel
|
||||
private Integer expand = 1;
|
||||
// upper size limit
|
||||
private Integer sizeLimit = 10 * 1024 * 1024;
|
||||
// watch the index TimeTunnel
|
||||
private String watchExpress = com.taobao.arthas.core.util.Constants.EMPTY_STRING;
|
||||
private String searchExpress = com.taobao.arthas.core.util.Constants.EMPTY_STRING;
|
||||
// play the index TimeTunnel
|
||||
private boolean isPlay = false;
|
||||
// delete the index TimeTunnel
|
||||
private boolean isDelete = false;
|
||||
private boolean isRegEx = false;
|
||||
private int numberOfLimit = 100;
|
||||
|
||||
@Argument(index = 0, argName = "class-pattern", required = false)
|
||||
@Description("Path and classname of Pattern Matching")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 1, argName = "method-pattern", required = false)
|
||||
@Description("Method of Pattern Matching")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 2, argName = "condition-express", required = false)
|
||||
@Description(Constants.CONDITION_EXPRESS)
|
||||
public void setConditionExpress(String conditionExpress) {
|
||||
this.conditionExpress = conditionExpress;
|
||||
}
|
||||
|
||||
@Option(shortName = "t", longName = "time-tunnel", flag = true)
|
||||
@Description("Record the method invocation within time fragments")
|
||||
public void setTimeTunnel(boolean timeTunnel) {
|
||||
isTimeTunnel = timeTunnel;
|
||||
}
|
||||
|
||||
@Option(shortName = "l", longName = "list", flag = true)
|
||||
@Description("List all the time fragments")
|
||||
public void setList(boolean list) {
|
||||
isList = list;
|
||||
}
|
||||
|
||||
@Option(shortName = "D", longName = "delete-all", flag = true)
|
||||
@Description("Delete all the time fragments")
|
||||
public void setDeleteAll(boolean deleteAll) {
|
||||
isDeleteAll = deleteAll;
|
||||
}
|
||||
|
||||
@Option(shortName = "i", longName = "index")
|
||||
@Description("Display the detailed information from specified time fragment")
|
||||
public void setIndex(Integer index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Option(shortName = "x", longName = "expand")
|
||||
@Description("Expand level of object (1 by default)")
|
||||
public void setExpand(Integer expand) {
|
||||
this.expand = expand;
|
||||
}
|
||||
|
||||
@Option(shortName = "M", longName = "sizeLimit")
|
||||
@Description("Upper size limit in bytes for the result (10 * 1024 * 1024 by default)")
|
||||
public void setSizeLimit(Integer sizeLimit) {
|
||||
this.sizeLimit = sizeLimit;
|
||||
}
|
||||
|
||||
@Option(shortName = "w", longName = "watch-express")
|
||||
@Description(value = "watch the time fragment by ognl express.\n" + Constants.EXPRESS_EXAMPLES)
|
||||
public void setWatchExpress(String watchExpress) {
|
||||
this.watchExpress = watchExpress;
|
||||
}
|
||||
|
||||
@Option(shortName = "s", longName = "search-express")
|
||||
@Description("Search-expression, to search the time fragments by ognl express.\n" +
|
||||
"The structure of 'advice' like conditional expression")
|
||||
public void setSearchExpress(String searchExpress) {
|
||||
this.searchExpress = searchExpress;
|
||||
}
|
||||
|
||||
@Option(shortName = "p", longName = "play", flag = true)
|
||||
@Description("Replay the time fragment specified by index")
|
||||
public void setPlay(boolean play) {
|
||||
isPlay = play;
|
||||
}
|
||||
|
||||
@Option(shortName = "d", longName = "delete", flag = true)
|
||||
@Description("Delete time fragment specified by index")
|
||||
public void setDelete(boolean delete) {
|
||||
isDelete = delete;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "limits")
|
||||
@Description("Threshold of execution times")
|
||||
public void setNumberOfLimit(int numberOfLimit) {
|
||||
this.numberOfLimit = numberOfLimit;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public String getConditionExpress() {
|
||||
return conditionExpress;
|
||||
}
|
||||
|
||||
public int getNumberOfLimit() {
|
||||
return numberOfLimit;
|
||||
}
|
||||
|
||||
|
||||
private boolean hasWatchExpress() {
|
||||
return !StringUtils.isEmpty(watchExpress);
|
||||
}
|
||||
|
||||
private boolean hasSearchExpress() {
|
||||
return !StringUtils.isEmpty(searchExpress);
|
||||
}
|
||||
|
||||
private boolean isNeedExpand() {
|
||||
return null != expand && expand > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查参数是否合法
|
||||
*/
|
||||
private void checkArguments() {
|
||||
// 检查d/p参数是否有i参数配套
|
||||
if ((isDelete || isPlay) && null == index) {
|
||||
throw new IllegalArgumentException("Time fragment index is expected, please type -i to specify");
|
||||
}
|
||||
|
||||
// 在t参数下class-pattern,method-pattern
|
||||
if (isTimeTunnel) {
|
||||
if (StringUtils.isEmpty(classPattern)) {
|
||||
throw new IllegalArgumentException("Class-pattern is expected, please type the wildcard expression to match");
|
||||
}
|
||||
if (StringUtils.isEmpty(methodPattern)) {
|
||||
throw new IllegalArgumentException("Method-pattern is expected, please type the wildcard expression to match");
|
||||
}
|
||||
}
|
||||
|
||||
// 一个参数都没有是不行滴
|
||||
if (null == index && !isTimeTunnel && !isDeleteAll && StringUtils.isEmpty(watchExpress)
|
||||
&& !isList && StringUtils.isEmpty(searchExpress)) {
|
||||
throw new IllegalArgumentException("Argument(s) is/are expected, type 'help tt' to read usage");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 记录时间片段
|
||||
*/
|
||||
int putTimeTunnel(TimeFragment tt) {
|
||||
int indexOfSeq = sequence.getAndIncrement();
|
||||
timeFragmentMap.put(indexOfSeq, tt);
|
||||
return indexOfSeq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(final CommandProcess process) {
|
||||
// 检查参数
|
||||
checkArguments();
|
||||
|
||||
// ctrl-C support
|
||||
process.interruptHandler(new CommandInterruptHandler(process));
|
||||
|
||||
if (isTimeTunnel) {
|
||||
enhance(process);
|
||||
} else if (isPlay) {
|
||||
processPlay(process);
|
||||
} else if (isList) {
|
||||
processList(process);
|
||||
} else if (isDeleteAll) {
|
||||
processDeleteAll(process);
|
||||
} else if (isDelete) {
|
||||
processDelete(process);
|
||||
} else if (hasSearchExpress()) {
|
||||
processSearch(process);
|
||||
} else if (index != null) {
|
||||
if (hasWatchExpress()) {
|
||||
processWatch(process);
|
||||
} else {
|
||||
processShow(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getClassNameMatcher() {
|
||||
if (classNameMatcher == null) {
|
||||
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
|
||||
}
|
||||
return classNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getMethodNameMatcher() {
|
||||
if (methodNameMatcher == null) {
|
||||
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
|
||||
}
|
||||
return methodNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(CommandProcess process) {
|
||||
return new TimeTunnelAdviceListener(this, process);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean completeExpress(Completion completion) {
|
||||
completion.complete(EMPTY);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 展示指定记录
|
||||
private void processShow(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
try {
|
||||
TimeFragment tf = timeFragmentMap.get(index);
|
||||
if (null == tf) {
|
||||
process.write(format("Time fragment[%d] does not exist.", index)).write("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Advice advice = tf.getAdvice();
|
||||
String className = advice.getClazz().getName();
|
||||
String methodName = advice.getMethod().getName();
|
||||
String objectAddress = advice.getTarget() == null ? "NULL" : "0x" + toHexString(advice.getTarget().hashCode());
|
||||
|
||||
TableElement table = TimeTunnelTable.createDefaultTable();
|
||||
TimeTunnelTable.drawTimeTunnel(tf, index, table);
|
||||
TimeTunnelTable.drawMethod(advice, className, methodName, objectAddress, table);
|
||||
TimeTunnelTable.drawParameters(advice, table, isNeedExpand(), expand);
|
||||
TimeTunnelTable.drawReturnObj(advice, table, isNeedExpand(), expand, sizeLimit);
|
||||
TimeTunnelTable.drawThrowException(advice, table, isNeedExpand(), expand);
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()));
|
||||
affect.rCnt(1);
|
||||
} finally {
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
// 查看记录信息
|
||||
private void processWatch(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
try {
|
||||
final TimeFragment tf = timeFragmentMap.get(index);
|
||||
if (null == tf) {
|
||||
process.write(format("Time fragment[%d] does not exist.", index)).write("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Advice advice = tf.getAdvice();
|
||||
Object value = ExpressFactory.newExpress(advice).get(watchExpress);
|
||||
if (isNeedExpand()) {
|
||||
process.write(new ObjectView(value, expand, sizeLimit).draw()).write("\n");
|
||||
} else {
|
||||
process.write(StringUtils.objectToString(value)).write("\n");
|
||||
}
|
||||
|
||||
affect.rCnt(1);
|
||||
} catch (ExpressException e) {
|
||||
LogUtil.getArthasLogger().warn("tt failed.", e);
|
||||
process.write(e.getMessage() + ", visit " + LogUtil.LOGGER_FILE + " for more detail\n");
|
||||
} finally {
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
// do search timeFragmentMap
|
||||
private void processSearch(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
try {
|
||||
// 匹配的时间片段
|
||||
Map<Integer, TimeFragment> matchingTimeSegmentMap = new LinkedHashMap<Integer, TimeFragment>();
|
||||
for (Map.Entry<Integer, TimeFragment> entry : timeFragmentMap.entrySet()) {
|
||||
int index = entry.getKey();
|
||||
TimeFragment tf = entry.getValue();
|
||||
Advice advice = tf.getAdvice();
|
||||
|
||||
// 搜索出匹配的时间片段
|
||||
if ((ExpressFactory.newExpress(advice)).is(searchExpress)) {
|
||||
matchingTimeSegmentMap.put(index, tf);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWatchExpress()) {
|
||||
// 执行watchExpress
|
||||
TableElement table = TimeTunnelTable.createDefaultTable();
|
||||
TimeTunnelTable.drawWatchTableHeader(table);
|
||||
TimeTunnelTable.drawWatchExpress(matchingTimeSegmentMap, table, watchExpress, isNeedExpand(), expand, sizeLimit);
|
||||
process.write(RenderUtil.render(table, process.width()));
|
||||
} else {
|
||||
// 单纯的列表格
|
||||
process.write(RenderUtil.render(TimeTunnelTable.drawTimeTunnelTable(matchingTimeSegmentMap), process.width()));
|
||||
}
|
||||
|
||||
affect.rCnt(matchingTimeSegmentMap.size());
|
||||
} catch (ExpressException e) {
|
||||
LogUtil.getArthasLogger().warn("tt failed.", e);
|
||||
process.write(e.getMessage() + ", visit " + LogUtil.LOGGER_FILE + " for more detail\n");
|
||||
} finally {
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
// 删除指定记录
|
||||
private void processDelete(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
if (timeFragmentMap.remove(index) != null) {
|
||||
affect.rCnt(1);
|
||||
}
|
||||
process.write(format("Time fragment[%d] successfully deleted.", index)).write("\n");
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private void processDeleteAll(CommandProcess process) {
|
||||
int count = timeFragmentMap.size();
|
||||
RowAffect affect = new RowAffect(count);
|
||||
timeFragmentMap.clear();
|
||||
process.write("Time fragments are cleaned.\n");
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
private void processList(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
process.write(RenderUtil.render(TimeTunnelTable.drawTimeTunnelTable(timeFragmentMap), process.width()));
|
||||
affect.rCnt(timeFragmentMap.size());
|
||||
process.write(affect.toString()).write("\n");
|
||||
process.end();
|
||||
}
|
||||
|
||||
// 重放指定记录
|
||||
private void processPlay(CommandProcess process) {
|
||||
RowAffect affect = new RowAffect();
|
||||
try {
|
||||
TimeFragment tf = timeFragmentMap.get(index);
|
||||
if (null == tf) {
|
||||
process.write(format("Time fragment[%d] does not exist.", index) + "\n");
|
||||
process.write(affect + "\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
|
||||
Advice advice = tf.getAdvice();
|
||||
String className = advice.getClazz().getName();
|
||||
String methodName = advice.getMethod().getName();
|
||||
String objectAddress = advice.getTarget() == null ? "NULL" : "0x" + toHexString(advice.getTarget().hashCode());
|
||||
|
||||
TableElement table = TimeTunnelTable.createDefaultTable();
|
||||
TimeTunnelTable.drawPlayHeader(className, methodName, objectAddress, index, table);
|
||||
TimeTunnelTable.drawParameters(advice, table, isNeedExpand(), expand);
|
||||
|
||||
ArthasMethod method = advice.getMethod();
|
||||
boolean accessible = advice.getMethod().isAccessible();
|
||||
try {
|
||||
method.setAccessible(true);
|
||||
Object returnObj = method.invoke(advice.getTarget(), advice.getParams());
|
||||
TimeTunnelTable.drawPlayResult(table, returnObj, isNeedExpand(), expand, sizeLimit);
|
||||
} catch (Throwable t) {
|
||||
TimeTunnelTable.drawPlayException(table, t, isNeedExpand(), expand);
|
||||
} finally {
|
||||
method.setAccessible(accessible);
|
||||
}
|
||||
|
||||
process.write(RenderUtil.render(table, process.width()))
|
||||
.write(format("Time fragment[%d] successfully replayed.", index))
|
||||
.write("\n");
|
||||
affect.rCnt(1);
|
||||
process.write(affect.toString()).write("\n");
|
||||
} finally {
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.command.express.ExpressFactory;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.view.ObjectView;
|
||||
import com.taobao.text.Decoration;
|
||||
import com.taobao.text.ui.Element;
|
||||
import com.taobao.text.ui.LabelElement;
|
||||
import com.taobao.text.ui.TableElement;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.taobao.text.ui.Element.label;
|
||||
import static java.lang.Integer.toHexString;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 30/11/2016.
|
||||
*/
|
||||
public class TimeTunnelTable {
|
||||
// 各列宽度
|
||||
private static final int[] TABLE_COL_WIDTH = new int[]{
|
||||
8, // index
|
||||
20, // timestamp
|
||||
10, // cost(ms)
|
||||
8, // isRet
|
||||
8, // isExp
|
||||
15, // object address
|
||||
30, // class
|
||||
30, // method
|
||||
};
|
||||
|
||||
// 各列名称
|
||||
private static final String[] TABLE_COL_TITLE = new String[]{
|
||||
"INDEX",
|
||||
"TIMESTAMP",
|
||||
"COST(ms)",
|
||||
"IS-RET",
|
||||
"IS-EXP",
|
||||
"OBJECT",
|
||||
"CLASS",
|
||||
"METHOD"
|
||||
|
||||
};
|
||||
|
||||
static TableElement createTable() {
|
||||
return new TableElement(TABLE_COL_WIDTH).leftCellPadding(1).rightCellPadding(1);
|
||||
}
|
||||
|
||||
static TableElement createDefaultTable() {
|
||||
return new TableElement().leftCellPadding(1).rightCellPadding(1);
|
||||
}
|
||||
|
||||
static TableElement fillTableHeader(TableElement table) {
|
||||
LabelElement[] headers = new LabelElement[TABLE_COL_TITLE.length];
|
||||
for (int i = 0; i < TABLE_COL_TITLE.length; ++i) {
|
||||
headers[i] = label(TABLE_COL_TITLE[i]).style(Decoration.bold.bold());
|
||||
}
|
||||
table.row(true, headers);
|
||||
return table;
|
||||
}
|
||||
|
||||
// 绘制TimeTunnel表格
|
||||
static Element drawTimeTunnelTable(Map<Integer, TimeFragment> timeTunnelMap) {
|
||||
TableElement table = fillTableHeader(createTable());
|
||||
for (Map.Entry<Integer, TimeFragment> entry : timeTunnelMap.entrySet()) {
|
||||
final int index = entry.getKey();
|
||||
final TimeFragment tf = entry.getValue();
|
||||
fillTableRow(table, index, tf);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
// 填充表格行
|
||||
static TableElement fillTableRow(TableElement table, int index, TimeFragment tf) {
|
||||
Advice advice = tf.getAdvice();
|
||||
return table.row(
|
||||
"" + index,
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tf.getGmtCreate()),
|
||||
"" + tf.getCost(),
|
||||
"" + advice.isAfterReturning(),
|
||||
"" + advice.isAfterThrowing(),
|
||||
advice.getTarget() == null
|
||||
? "NULL"
|
||||
: "0x" + toHexString(advice.getTarget().hashCode()),
|
||||
StringUtils.substringAfterLast("." + advice.getClazz().getName(), "."),
|
||||
advice.getMethod().getName()
|
||||
);
|
||||
}
|
||||
|
||||
static void drawTimeTunnel(TimeFragment tf, Integer index, TableElement table) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
table.row("INDEX", "" + index)
|
||||
.row("GMT-CREATE", sdf.format(tf.getGmtCreate()))
|
||||
.row("COST(ms)", "" + tf.getCost());
|
||||
}
|
||||
|
||||
static void drawMethod(Advice advice, String className, String methodName, String objectAddress, TableElement table) {
|
||||
table.row("OBJECT", objectAddress)
|
||||
.row("CLASS", className)
|
||||
.row("METHOD", methodName)
|
||||
.row("IS-RETURN", "" + advice.isAfterReturning())
|
||||
.row("IS-EXCEPTION", "" + advice.isAfterThrowing());
|
||||
}
|
||||
|
||||
static void drawThrowException(Advice advice, TableElement table, boolean isNeedExpand, int expandLevel) {
|
||||
if (advice.isAfterThrowing()) {
|
||||
//noinspection ThrowableResultOfMethodCallIgnored
|
||||
Throwable throwable = advice.getThrowExp();
|
||||
if (isNeedExpand) {
|
||||
table.row("THROW-EXCEPTION", new ObjectView(advice.getThrowExp(), expandLevel).draw());
|
||||
} else {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
try {
|
||||
throwable.printStackTrace(printWriter);
|
||||
table.row("THROW-EXCEPTION", stringWriter.toString());
|
||||
} finally {
|
||||
printWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawReturnObj(Advice advice, TableElement table, boolean isNeedExpand, int expandLevel, int sizeLimit) {
|
||||
// fill the returnObj
|
||||
if (advice.isAfterReturning()) {
|
||||
if (isNeedExpand) {
|
||||
table.row("RETURN-OBJ", new ObjectView(advice.getReturnObj(), expandLevel, sizeLimit).draw());
|
||||
} else {
|
||||
table.row("RETURN-OBJ", "" + StringUtils.objectToString(advice.getReturnObj()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawParameters(Advice advice, TableElement table, boolean isNeedExpand, int expandLevel) {
|
||||
// fill the parameters
|
||||
if (null != advice.getParams()) {
|
||||
int paramIndex = 0;
|
||||
for (Object param : advice.getParams()) {
|
||||
if (isNeedExpand) {
|
||||
table.row("PARAMETERS[" + paramIndex++ + "]", new ObjectView(param, expandLevel).draw());
|
||||
} else {
|
||||
table.row("PARAMETERS[" + paramIndex++ + "]", "" + StringUtils.objectToString(param));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawWatchTableHeader(TableElement table) {
|
||||
table.row(true, label("INDEX").style(Decoration.bold.bold()), label("SEARCH-RESULT")
|
||||
.style(Decoration.bold.bold()));
|
||||
}
|
||||
|
||||
static void drawWatchExpress(Map<Integer, TimeFragment> matchingTimeSegmentMap, TableElement table,
|
||||
String watchExpress, boolean isNeedExpand, int expandLevel, int sizeLimit)
|
||||
throws ExpressException {
|
||||
for (Map.Entry<Integer, TimeFragment> entry : matchingTimeSegmentMap.entrySet()) {
|
||||
Object value = ExpressFactory.newExpress(entry.getValue().getAdvice()).get(watchExpress);
|
||||
table.row("" + entry.getKey(), "" +
|
||||
(isNeedExpand ? new ObjectView(value, expandLevel, sizeLimit).draw() : StringUtils.objectToString(value)));
|
||||
}
|
||||
}
|
||||
|
||||
static TableElement drawPlayHeader(String className, String methodName, String objectAddress, int index,
|
||||
TableElement table) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return table.row("RE-INDEX", "" + index)
|
||||
.row("GMT-REPLAY", sdf.format(new Date()))
|
||||
.row("OBJECT", objectAddress)
|
||||
.row("CLASS", className)
|
||||
.row("METHOD", methodName);
|
||||
}
|
||||
|
||||
static void drawPlayResult(TableElement table, Object returnObj, boolean isNeedExpand, int expandLevel,
|
||||
int sizeLimit) {
|
||||
// 执行成功:输出成功状态
|
||||
table.row("IS-RETURN", "" + true);
|
||||
table.row("IS-EXCEPTION", "" + false);
|
||||
|
||||
// 执行成功:输出成功结果
|
||||
if (isNeedExpand) {
|
||||
table.row("RETURN-OBJ", new ObjectView(returnObj, expandLevel, sizeLimit).draw());
|
||||
} else {
|
||||
table.row("RETURN-OBJ", "" + StringUtils.objectToString(returnObj));
|
||||
}
|
||||
}
|
||||
|
||||
static void drawPlayException(TableElement table, Throwable t, boolean isNeedExpand, int expandLevel) {
|
||||
// 执行失败:输出失败状态
|
||||
table.row("IS-RETURN", "" + false);
|
||||
table.row("IS-EXCEPTION", "" + true);
|
||||
|
||||
// 执行失败:输出失败异常信息
|
||||
Throwable cause;
|
||||
if (t instanceof InvocationTargetException) {
|
||||
cause = t.getCause();
|
||||
} else {
|
||||
cause = t;
|
||||
}
|
||||
|
||||
if (isNeedExpand) {
|
||||
table.row("THROW-EXCEPTION", new ObjectView(cause, expandLevel).draw());
|
||||
} else {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
try {
|
||||
cause.printStackTrace(printWriter);
|
||||
table.row("THROW-EXCEPTION", stringWriter.toString());
|
||||
} finally {
|
||||
printWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.InvokeTraceable;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 29/11/2016.
|
||||
*/
|
||||
public class TraceAdviceListener extends AbstractTraceAdviceListener implements InvokeTraceable {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public TraceAdviceListener(TraceCommand command, CommandProcess process) {
|
||||
super(command, process);
|
||||
}
|
||||
|
||||
/**
|
||||
* trace 会在被观测的方法体中,在每个方法调用前后插入字节码,所以方法调用开始,结束,抛异常的时候,都会回调下面的接口
|
||||
*/
|
||||
@Override
|
||||
public void invokeBeforeTracing(String tracingClassName, String tracingMethodName, String tracingMethodDesc)
|
||||
throws Throwable {
|
||||
threadBoundEntity.get().view.begin(
|
||||
StringUtils.normalizeClassName(tracingClassName) + ":" + tracingMethodName + "()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeAfterTracing(String tracingClassName, String tracingMethodName, String tracingMethodDesc)
|
||||
throws Throwable {
|
||||
threadBoundEntity.get().view.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeThrowTracing(String tracingClassName, String tracingMethodName, String tracingMethodDesc)
|
||||
throws Throwable {
|
||||
threadBoundEntity.get().view.end("throws Exception");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.matcher.GroupMatcher;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
import com.taobao.arthas.core.util.matcher.RegexMatcher;
|
||||
import com.taobao.arthas.core.util.matcher.TrueMatcher;
|
||||
import com.taobao.arthas.core.util.matcher.WildcardMatcher;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 调用跟踪命令<br/>
|
||||
* 负责输出一个类中的所有方法调用路径
|
||||
*
|
||||
* @author vlinux on 15/5/27.
|
||||
*/
|
||||
@Name("trace")
|
||||
@Summary("Trace the execution time of specified method invocation.")
|
||||
@Description(value = Constants.EXPRESS_DESCRIPTION + Constants.EXAMPLE +
|
||||
" trace -E org\\\\.apache\\\\.commons\\\\.lang\\\\.StringUtils isBlank\n" +
|
||||
" trace org.apache.commons.lang.StringUtils isBlank\n" +
|
||||
" trace *StringUtils isBlank\n" +
|
||||
" trace *StringUtils isBlank params[0].length==1\n" +
|
||||
" trace *StringUtils isBlank '#cost>100'\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/trace")
|
||||
public class TraceCommand extends EnhancerCommand {
|
||||
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private String conditionExpress;
|
||||
private boolean isRegEx = false;
|
||||
private int numberOfLimit = 100;
|
||||
private List<String> pathPatterns;
|
||||
private boolean skipJDKTrace;
|
||||
|
||||
@Argument(argName = "class-pattern", index = 0)
|
||||
@Description("Class name pattern, use either '.' or '/' as separator")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "method-pattern", index = 1)
|
||||
@Description("Method name pattern")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Argument(argName = "condition-express", index = 2, required = false)
|
||||
@Description(Constants.CONDITION_EXPRESS)
|
||||
public void setConditionExpress(String conditionExpress) {
|
||||
this.conditionExpress = conditionExpress;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "limits")
|
||||
@Description("Threshold of execution times")
|
||||
public void setNumberOfLimit(int numberOfLimit) {
|
||||
this.numberOfLimit = numberOfLimit;
|
||||
}
|
||||
|
||||
@Option(shortName = "p", longName = "path", acceptMultipleValues = true)
|
||||
@Description("path tracing pattern")
|
||||
public void setPathPatterns(List<String> pathPatterns) {
|
||||
this.pathPatterns = pathPatterns;
|
||||
}
|
||||
|
||||
@Option(shortName = "j", longName = "jdkMethodSkip")
|
||||
@Description("skip jdk method trace")
|
||||
public void setSkipJDKTrace(boolean skipJDKTrace) {
|
||||
this.skipJDKTrace = skipJDKTrace;
|
||||
}
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public String getConditionExpress() {
|
||||
return conditionExpress;
|
||||
}
|
||||
|
||||
public boolean isSkipJDKTrace() {
|
||||
return skipJDKTrace;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
public int getNumberOfLimit() {
|
||||
return numberOfLimit;
|
||||
}
|
||||
|
||||
public List<String> getPathPatterns() {
|
||||
return pathPatterns;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getClassNameMatcher() {
|
||||
if (classNameMatcher == null) {
|
||||
if (pathPatterns == null || pathPatterns.isEmpty()) {
|
||||
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
|
||||
} else {
|
||||
classNameMatcher = getPathTracingClassMatcher();
|
||||
}
|
||||
}
|
||||
return classNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getMethodNameMatcher() {
|
||||
if (methodNameMatcher == null) {
|
||||
if (pathPatterns == null || pathPatterns.isEmpty()) {
|
||||
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
|
||||
} else {
|
||||
methodNameMatcher = getPathTracingMethodMatcher();
|
||||
}
|
||||
}
|
||||
return methodNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(CommandProcess process) {
|
||||
if (pathPatterns == null || pathPatterns.isEmpty()) {
|
||||
return new TraceAdviceListener(this, process);
|
||||
} else {
|
||||
return new PathTraceAdviceListener(this, process);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean completeExpress(Completion completion) {
|
||||
completion.complete(EMPTY);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造追踪路径匹配
|
||||
*/
|
||||
private Matcher<String> getPathTracingClassMatcher() {
|
||||
|
||||
List<Matcher<String>> matcherList = new ArrayList<Matcher<String>>();
|
||||
matcherList.add(SearchUtils.classNameMatcher(getClassPattern(), isRegEx()));
|
||||
|
||||
if (null != getPathPatterns()) {
|
||||
for (String pathPattern : getPathPatterns()) {
|
||||
if (isRegEx()) {
|
||||
matcherList.add(new RegexMatcher(pathPattern));
|
||||
} else {
|
||||
matcherList.add(new WildcardMatcher(pathPattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new GroupMatcher.Or<String>(matcherList);
|
||||
}
|
||||
|
||||
private Matcher<String> getPathTracingMethodMatcher() {
|
||||
return new TrueMatcher<String>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.util.DateUtils;
|
||||
import com.taobao.arthas.core.util.ThreadUtil;
|
||||
import com.taobao.arthas.core.view.TreeView;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用于在ThreadLocal中传递的实体
|
||||
* @author ralf0131 2017-01-05 14:05.
|
||||
*/
|
||||
public class TraceEntity {
|
||||
|
||||
protected TreeView view;
|
||||
protected int deep;
|
||||
|
||||
public TraceEntity() {
|
||||
this.view = createTreeView();
|
||||
this.deep = 0;
|
||||
}
|
||||
|
||||
public TreeView getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
public void setView(TreeView view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public int getDeep() {
|
||||
return deep;
|
||||
}
|
||||
|
||||
public void setDeep(int deep) {
|
||||
this.deep = deep;
|
||||
}
|
||||
|
||||
private TreeView createTreeView() {
|
||||
String threadTitle = "ts=" + DateUtils.getCurrentDate()+ ";" + ThreadUtil.getThreadTitle(Thread.currentThread());
|
||||
return new TreeView(true, threadTitle);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.advisor.ArthasMethod;
|
||||
import com.taobao.arthas.core.util.DateUtils;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
import com.taobao.arthas.core.util.ThreadLocalWatch;
|
||||
import com.taobao.arthas.core.view.ObjectView;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 29/11/2016.
|
||||
*/
|
||||
class WatchAdviceListener extends ReflectAdviceListenerAdapter {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
|
||||
private WatchCommand command;
|
||||
private CommandProcess process;
|
||||
|
||||
public WatchAdviceListener(WatchCommand command, CommandProcess process) {
|
||||
this.command = command;
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
private boolean isFinish() {
|
||||
return command.isFinish() || !command.isBefore() && !command.isException() && !command.isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
|
||||
throws Throwable {
|
||||
// 开始计算本次方法调用耗时
|
||||
threadLocalWatch.start();
|
||||
if (command.isBefore()) {
|
||||
watching(Advice.newForBefore(loader, clazz, method, target, args));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Object returnObject) throws Throwable {
|
||||
Advice advice = Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject);
|
||||
if (command.isSuccess()) {
|
||||
watching(advice);
|
||||
}
|
||||
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
|
||||
Throwable throwable) {
|
||||
Advice advice = Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable);
|
||||
if (command.isException()) {
|
||||
watching(advice);
|
||||
}
|
||||
|
||||
finishing(advice);
|
||||
}
|
||||
|
||||
private void finishing(Advice advice) {
|
||||
if (isFinish()) {
|
||||
watching(advice);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNeedExpand() {
|
||||
Integer expand = command.getExpand();
|
||||
return null != expand && expand >= 0;
|
||||
}
|
||||
|
||||
private void watching(Advice advice) {
|
||||
try {
|
||||
// 本次调用的耗时
|
||||
double cost = threadLocalWatch.costInMillis();
|
||||
if (isConditionMet(command.getConditionExpress(), advice, cost)) {
|
||||
// TODO: concurrency issues for process.write
|
||||
Object value = getExpressionResult(command.getExpress(), advice, cost);
|
||||
String result = StringUtils.objectToString(
|
||||
isNeedExpand() ? new ObjectView(value, command.getExpand(), command.getSizeLimit()).draw() : value);
|
||||
process.write("ts=" + DateUtils.getCurrentDate() + ";result=" + result + "\n");
|
||||
process.times().incrementAndGet();
|
||||
if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) {
|
||||
abortProcess(process, command.getNumberOfLimit());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("watch failed.", e);
|
||||
process.write("watch failed, condition is: " + command.getConditionExpress() + ", express is: "
|
||||
+ command.getExpress() + ", " + e.getMessage() + ", visit " + LogUtil.LOGGER_FILE
|
||||
+ " for more details.\n");
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.util.matcher.Matcher;
|
||||
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;
|
||||
|
||||
@Name("watch")
|
||||
@Summary("Display the input/output parameter, return object, and thrown exception of specified method invocation")
|
||||
@Description(Constants.EXPRESS_DESCRIPTION + "\nExamples:\n" +
|
||||
" watch -Eb org\\.apache\\.commons\\.lang\\.StringUtils isBlank params[0]\n" +
|
||||
" watch -b org.apache.commons.lang.StringUtils isBlank params[0]\n" +
|
||||
" watch -f org.apache.commons.lang.StringUtils isBlank returnObj\n" +
|
||||
" watch -bf *StringUtils isBlank params[0]\n" +
|
||||
" watch *StringUtils isBlank params[0]\n" +
|
||||
" watch *StringUtils isBlank params[0] params[0].length==1\n" +
|
||||
" watch *StringUtils isBlank '#cost>100'\n" +
|
||||
Constants.WIKI + Constants.WIKI_HOME + "cmds/watch")
|
||||
public class WatchCommand extends EnhancerCommand {
|
||||
|
||||
private String classPattern;
|
||||
private String methodPattern;
|
||||
private String express;
|
||||
private String conditionExpress;
|
||||
private boolean isBefore = false;
|
||||
private boolean isFinish = false;
|
||||
private boolean isException = false;
|
||||
private boolean isSuccess = false;
|
||||
private Integer expand = 1;
|
||||
private Integer sizeLimit = 10 * 1024 * 1024;
|
||||
private boolean isRegEx = false;
|
||||
private int numberOfLimit = 100;
|
||||
|
||||
@Argument(index = 0, argName = "class-pattern")
|
||||
@Description("The full qualified class name you want to watch")
|
||||
public void setClassPattern(String classPattern) {
|
||||
this.classPattern = classPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 1, argName = "method-pattern")
|
||||
@Description("The method name you want to watch")
|
||||
public void setMethodPattern(String methodPattern) {
|
||||
this.methodPattern = methodPattern;
|
||||
}
|
||||
|
||||
@Argument(index = 2, argName = "express")
|
||||
@Description("the content you want to watch, written by ognl.\n" + Constants.EXPRESS_EXAMPLES)
|
||||
public void setExpress(String express) {
|
||||
this.express = express;
|
||||
}
|
||||
|
||||
@Argument(index = 3, argName = "condition-express", required = false)
|
||||
@Description(Constants.CONDITION_EXPRESS)
|
||||
public void setConditionExpress(String conditionExpress) {
|
||||
this.conditionExpress = conditionExpress;
|
||||
}
|
||||
|
||||
@Option(shortName = "b", longName = "before", flag = true)
|
||||
@Description("Watch before invocation")
|
||||
public void setBefore(boolean before) {
|
||||
isBefore = before;
|
||||
}
|
||||
|
||||
@Option(shortName = "f", longName = "finish", flag = true)
|
||||
@Description("Watch after invocation, enable by default")
|
||||
public void setFinish(boolean finish) {
|
||||
isFinish = finish;
|
||||
}
|
||||
|
||||
@Option(shortName = "e", longName = "exception", flag = true)
|
||||
@Description("Watch after throw exception")
|
||||
public void setException(boolean exception) {
|
||||
isException = exception;
|
||||
}
|
||||
|
||||
@Option(shortName = "s", longName = "success", flag = true)
|
||||
@Description("Watch after successful invocation")
|
||||
public void setSuccess(boolean success) {
|
||||
isSuccess = success;
|
||||
}
|
||||
|
||||
@Option(shortName = "M", longName = "sizeLimit")
|
||||
@Description("Upper size limit in bytes for the result (10 * 1024 * 1024 by default)")
|
||||
public void setSizeLimit(Integer sizeLimit) {
|
||||
this.sizeLimit = sizeLimit;
|
||||
}
|
||||
|
||||
@Option(shortName = "x", longName = "expand")
|
||||
@Description("Expand level of object (1 by default)")
|
||||
public void setExpand(Integer expand) {
|
||||
this.expand = expand;
|
||||
}
|
||||
|
||||
@Option(shortName = "E", longName = "regex", flag = true)
|
||||
@Description("Enable regular expression to match (wildcard matching by default)")
|
||||
public void setRegEx(boolean regEx) {
|
||||
isRegEx = regEx;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "limits")
|
||||
@Description("Threshold of execution times")
|
||||
public void setNumberOfLimit(int numberOfLimit) {
|
||||
this.numberOfLimit = numberOfLimit;
|
||||
}
|
||||
|
||||
public String getClassPattern() {
|
||||
return classPattern;
|
||||
}
|
||||
|
||||
public String getMethodPattern() {
|
||||
return methodPattern;
|
||||
}
|
||||
|
||||
public String getExpress() {
|
||||
return express;
|
||||
}
|
||||
|
||||
public String getConditionExpress() {
|
||||
return conditionExpress;
|
||||
}
|
||||
|
||||
public boolean isBefore() {
|
||||
return isBefore;
|
||||
}
|
||||
|
||||
public boolean isFinish() {
|
||||
return isFinish;
|
||||
}
|
||||
|
||||
public boolean isException() {
|
||||
return isException;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public Integer getExpand() {
|
||||
return expand;
|
||||
}
|
||||
|
||||
public Integer getSizeLimit() {
|
||||
return sizeLimit;
|
||||
}
|
||||
|
||||
public boolean isRegEx() {
|
||||
return isRegEx;
|
||||
}
|
||||
|
||||
public int getNumberOfLimit() {
|
||||
return numberOfLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getClassNameMatcher() {
|
||||
if (classNameMatcher == null) {
|
||||
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
|
||||
}
|
||||
return classNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher getMethodNameMatcher() {
|
||||
if (methodNameMatcher == null) {
|
||||
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
|
||||
}
|
||||
return methodNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AdviceListener getAdviceListener(CommandProcess process) {
|
||||
return new WatchAdviceListener(this, process);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.taobao.arthas.core.config;
|
||||
|
||||
import com.taobao.arthas.core.util.reflect.ArthasReflectUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.reflect.Modifier.isStatic;
|
||||
|
||||
/**
|
||||
* 配置类
|
||||
*
|
||||
* @author vlinux
|
||||
*/
|
||||
public class Configure {
|
||||
|
||||
private String ip;
|
||||
private int telnetPort;
|
||||
private int httpPort;
|
||||
private int javaPid;
|
||||
private String arthasCore;
|
||||
private String arthasAgent;
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = normalizeIp(ip);
|
||||
}
|
||||
|
||||
public int getTelnetPort() {
|
||||
return telnetPort;
|
||||
}
|
||||
|
||||
public void setTelnetPort(int telnetPort) {
|
||||
this.telnetPort = telnetPort;
|
||||
}
|
||||
|
||||
public void setHttpPort(int httpPort) {
|
||||
this.httpPort = httpPort;
|
||||
}
|
||||
|
||||
public int getHttpPort() {
|
||||
return httpPort;
|
||||
}
|
||||
|
||||
public int getJavaPid() {
|
||||
return javaPid;
|
||||
}
|
||||
|
||||
public void setJavaPid(int javaPid) {
|
||||
this.javaPid = javaPid;
|
||||
}
|
||||
|
||||
public String getArthasAgent() {
|
||||
return arthasAgent;
|
||||
}
|
||||
|
||||
public void setArthasAgent(String arthasAgent) {
|
||||
this.arthasAgent = arthasAgent;
|
||||
}
|
||||
|
||||
public String getArthasCore() {
|
||||
return arthasCore;
|
||||
}
|
||||
|
||||
public void setArthasCore(String arthasCore) {
|
||||
this.arthasCore = arthasCore;
|
||||
}
|
||||
|
||||
// 对象的编码解码器
|
||||
private final static FeatureCodec codec = new FeatureCodec(';', '=');
|
||||
|
||||
/**
|
||||
* 序列化成字符串
|
||||
*
|
||||
* @return 序列化字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
final Map<String, String> map = new HashMap<String, String>();
|
||||
for (Field field : ArthasReflectUtils.getFields(Configure.class)) {
|
||||
|
||||
// 过滤掉静态类
|
||||
if (isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 非静态的才需要纳入非序列化过程
|
||||
try {
|
||||
map.put(field.getName(), String.valueOf(ArthasReflectUtils.getFieldValueByField(this, field)));
|
||||
} catch (Throwable t) {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return codec.toString(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化字符串成对象
|
||||
*
|
||||
* @param toString 序列化字符串
|
||||
* @return 反序列化的对象
|
||||
*/
|
||||
public static Configure toConfigure(String toString) {
|
||||
final Configure configure = new Configure();
|
||||
final Map<String, String> map = codec.toMap(toString);
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
try {
|
||||
final Field field = ArthasReflectUtils.getField(Configure.class, entry.getKey());
|
||||
if (null != field && !isStatic(field.getModifiers())) {
|
||||
ArthasReflectUtils.set(field, ArthasReflectUtils.valueOf(field.getType(), entry.getValue()), configure);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
//
|
||||
}
|
||||
}
|
||||
return configure;
|
||||
}
|
||||
|
||||
private String normalizeIp(String ip){
|
||||
if ("127.0.0.1".equals(ip)) {
|
||||
// bind to all network interfaces, allowing remote connections
|
||||
return "0.0.0.0";
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.taobao.arthas.core.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
|
||||
import static com.taobao.arthas.core.util.ArthasCheckUtils.isIn;
|
||||
import static com.taobao.arthas.core.util.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
* Feature编解器(线程安全)<br/>
|
||||
* <p/>
|
||||
* 用于封装系统内部features/attribute等扩展字段的管理
|
||||
* Created by dukun on 15/3/31.
|
||||
*/
|
||||
public class FeatureCodec {
|
||||
|
||||
/**
|
||||
* KV片段分割符<br/>
|
||||
* KV片段定义为一个完整的KV对,例如字符串<span>;k1=v1;k2=v2;</span>
|
||||
* 其中<b>;</b>即为KV片段分隔符
|
||||
*/
|
||||
private final char kvSegmentSeparator;
|
||||
|
||||
/**
|
||||
* KV分割符<br/>
|
||||
* KV定义为一个KV对区分K和V的分割符号,例如字符串<span>k1=v1</span>
|
||||
* 其中<b>=</b>即为KV分隔符
|
||||
*/
|
||||
private final char kvSeparator;
|
||||
|
||||
/**
|
||||
* 转义前缀符
|
||||
*/
|
||||
private static final char ESCAPE_PREFIX_CHAR = '\\';
|
||||
|
||||
/**
|
||||
* 使用指定的KV分割符构造FeatureParser<br/>
|
||||
*
|
||||
* @param kvSegmentSeparator KV对之间的分隔符
|
||||
* @param kvSeparator K与V之间的分隔符
|
||||
*/
|
||||
public FeatureCodec(final char kvSegmentSeparator, final char kvSeparator) {
|
||||
|
||||
// 分隔符禁止与转义前缀符相等
|
||||
if (isIn(ESCAPE_PREFIX_CHAR, kvSegmentSeparator, kvSeparator)) {
|
||||
throw new IllegalArgumentException("separator can not init to '" + ESCAPE_PREFIX_CHAR + "'.");
|
||||
}
|
||||
|
||||
this.kvSegmentSeparator = kvSegmentSeparator;
|
||||
this.kvSeparator = kvSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* map集合转换到feature字符串
|
||||
*
|
||||
* @param map map集合
|
||||
* @return feature字符串
|
||||
*/
|
||||
public String toString(final Map<String, String> map) {
|
||||
|
||||
final StringBuilder featureSB = new StringBuilder().append(kvSegmentSeparator);
|
||||
|
||||
if (null == map
|
||||
|| map.isEmpty()) {
|
||||
return featureSB.toString();
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
|
||||
featureSB
|
||||
.append(escapeEncode(entry.getKey()))
|
||||
.append(kvSeparator)
|
||||
.append(escapeEncode(entry.getValue()))
|
||||
.append(kvSegmentSeparator)
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
return featureSB.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* feature字符串转换到map集合
|
||||
*
|
||||
* @param featureString the feature string
|
||||
* @return the map
|
||||
*/
|
||||
public Map<String, String> toMap(final String featureString) {
|
||||
|
||||
final Map<String, String> map = new HashMap<String, String>();
|
||||
|
||||
if (isBlank(featureString)) {
|
||||
return map;
|
||||
}
|
||||
|
||||
for (String kv : escapeSplit(featureString, kvSegmentSeparator)) {
|
||||
|
||||
if (isBlank(kv)) {
|
||||
// 过滤掉为空的字符串片段
|
||||
continue;
|
||||
}
|
||||
|
||||
final String[] ar = escapeSplit(kv, kvSeparator);
|
||||
if (ar.length != 2) {
|
||||
// 过滤掉不符合K:V单目的情况
|
||||
continue;
|
||||
}
|
||||
|
||||
final String k = ar[0];
|
||||
final String v = ar[1];
|
||||
if (!isBlank(k)
|
||||
&& !isBlank(v)) {
|
||||
map.put(escapeDecode(k), escapeDecode(v));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义编码
|
||||
*
|
||||
* @param string 原始字符串
|
||||
* @return 转义编码后的字符串
|
||||
*/
|
||||
private String escapeEncode(final String string) {
|
||||
final StringBuilder returnSB = new StringBuilder();
|
||||
for (final char c : string.toCharArray()) {
|
||||
if (isIn(c, kvSegmentSeparator, kvSeparator, ESCAPE_PREFIX_CHAR)) {
|
||||
returnSB.append(ESCAPE_PREFIX_CHAR);
|
||||
}
|
||||
returnSB.append(c);
|
||||
}
|
||||
|
||||
return returnSB.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义解码
|
||||
*
|
||||
* @param string 编码字符串
|
||||
* @return 转义解码后的字符串
|
||||
*/
|
||||
private String escapeDecode(String string) {
|
||||
|
||||
final StringBuilder segmentSB = new StringBuilder();
|
||||
final int stringLength = string.length();
|
||||
|
||||
for (int index = 0; index < stringLength; index++) {
|
||||
|
||||
final char c = string.charAt(index);
|
||||
|
||||
if (isEquals(c, ESCAPE_PREFIX_CHAR)
|
||||
&& index < stringLength - 1) {
|
||||
|
||||
final char nextChar = string.charAt(++index);
|
||||
|
||||
// 下一个字符是转义符
|
||||
if (isIn(nextChar, kvSegmentSeparator, kvSeparator, ESCAPE_PREFIX_CHAR)) {
|
||||
segmentSB.append(nextChar);
|
||||
}
|
||||
|
||||
// 如果不是转义字符,则需要两个都放入
|
||||
else {
|
||||
segmentSB.append(c);
|
||||
segmentSB.append(nextChar);
|
||||
}
|
||||
} else {
|
||||
segmentSB.append(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return segmentSB.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码字符串拆分
|
||||
*
|
||||
* @param string 编码字符串
|
||||
* @param splitEscapeChar 分割符
|
||||
* @return 拆分后的字符串数组
|
||||
*/
|
||||
private String[] escapeSplit(String string, char splitEscapeChar) {
|
||||
|
||||
final ArrayList<String> segmentArrayList = new ArrayList<String>();
|
||||
final Stack<Character> decodeStack = new Stack<Character>();
|
||||
final int stringLength = string.length();
|
||||
|
||||
for (int index = 0; index < stringLength; index++) {
|
||||
|
||||
boolean isArchive = false;
|
||||
|
||||
final char c = string.charAt(index);
|
||||
|
||||
// 匹配到转义前缀符
|
||||
if (isEquals(c, ESCAPE_PREFIX_CHAR)) {
|
||||
|
||||
decodeStack.push(c);
|
||||
if (index < stringLength - 1) {
|
||||
final char nextChar = string.charAt(++index);
|
||||
decodeStack.push(nextChar);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 匹配到分割符
|
||||
else if (isEquals(c, splitEscapeChar)) {
|
||||
isArchive = true;
|
||||
}
|
||||
|
||||
// 匹配到其他字符
|
||||
else {
|
||||
decodeStack.push(c);
|
||||
}
|
||||
|
||||
if (isArchive
|
||||
|| index == stringLength - 1) {
|
||||
final StringBuilder segmentSB = new StringBuilder(decodeStack.size());
|
||||
while (!decodeStack.isEmpty()) {
|
||||
segmentSB.append(decodeStack.pop());
|
||||
}
|
||||
|
||||
segmentArrayList.add(
|
||||
segmentSB
|
||||
.reverse() // 因为堆栈中是逆序的,所以需要对逆序的字符串再次逆序
|
||||
.toString() // toString
|
||||
.trim() // 考虑到字符串片段可能会出现首尾空格的场景,这里做一个过滤
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return segmentArrayList.toArray(new String[segmentArrayList.size()]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.taobao.arthas.core.server;
|
||||
|
||||
import com.taobao.arthas.core.config.Configure;
|
||||
import com.taobao.arthas.core.command.BuiltinCommandPack;
|
||||
import com.taobao.arthas.core.shell.ShellServer;
|
||||
import com.taobao.arthas.core.shell.ShellServerOptions;
|
||||
import com.taobao.arthas.core.shell.command.CommandResolver;
|
||||
import com.taobao.arthas.core.shell.handlers.BindHandler;
|
||||
import com.taobao.arthas.core.shell.impl.ShellServerImpl;
|
||||
import com.taobao.arthas.core.shell.term.impl.HttpTermServer;
|
||||
import com.taobao.arthas.core.shell.term.impl.TelnetTermServer;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.arthas.core.util.UserStatUtil;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
/**
|
||||
* @author vlinux on 15/5/2.
|
||||
*/
|
||||
public class ArthasBootstrap {
|
||||
|
||||
private static Logger logger = LogUtil.getArthasLogger();
|
||||
private static ArthasBootstrap arthasBootstrap;
|
||||
|
||||
private AtomicBoolean isBindRef = new AtomicBoolean(false);
|
||||
private int pid;
|
||||
private Instrumentation instrumentation;
|
||||
private Thread shutdown;
|
||||
private ShellServer shellServer;
|
||||
private ExecutorService executorService;
|
||||
|
||||
private ArthasBootstrap(int pid, Instrumentation instrumentation) {
|
||||
this.pid = pid;
|
||||
this.instrumentation = instrumentation;
|
||||
|
||||
executorService = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
final Thread t = new Thread(r, "as-command-execute-daemon");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
shutdown = new Thread("as-shutdown-hooker") {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ArthasBootstrap.this.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(shutdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap arthas server
|
||||
*
|
||||
* @param configure 配置信息
|
||||
* @throws IOException 服务器启动失败
|
||||
*/
|
||||
public void bind(Configure configure) throws Throwable {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
if (!isBindRef.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException("already bind");
|
||||
}
|
||||
|
||||
try {
|
||||
ShellServerOptions options = new ShellServerOptions().setInstrumentation(instrumentation).setPid(pid);
|
||||
shellServer = new ShellServerImpl(options, this);
|
||||
BuiltinCommandPack builtinCommands = new BuiltinCommandPack();
|
||||
List<CommandResolver> resolvers = new ArrayList<CommandResolver>();
|
||||
resolvers.add(builtinCommands);
|
||||
// TODO: discover user provided command resolver
|
||||
shellServer.registerTermServer(new TelnetTermServer(
|
||||
configure.getIp(), configure.getTelnetPort(), options.getConnectionTimeout()));
|
||||
shellServer.registerTermServer(new HttpTermServer(
|
||||
configure.getIp(), configure.getHttpPort(), options.getConnectionTimeout()));
|
||||
|
||||
for (CommandResolver resolver : resolvers) {
|
||||
shellServer.registerCommandResolver(resolver);
|
||||
}
|
||||
|
||||
shellServer.listen(new BindHandler(isBindRef));
|
||||
|
||||
logger.info("as-server listening on network={};telnet={};http={};timeout={};", configure.getIp(),
|
||||
configure.getTelnetPort(), configure.getHttpPort(), options.getConnectionTimeout());
|
||||
// 异步回报启动次数
|
||||
UserStatUtil.arthasStart();
|
||||
|
||||
logger.info("as-server started in {} ms", System.currentTimeMillis() - start );
|
||||
} catch (Throwable e) {
|
||||
logger.error(null, "Error during bind to port " + configure.getTelnetPort(), e);
|
||||
if (shellServer != null) {
|
||||
shellServer.close();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断服务端是否已经启动
|
||||
*
|
||||
* @return true:服务端已经启动;false:服务端关闭
|
||||
*/
|
||||
public boolean isBind() {
|
||||
return isBindRef.get();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
executorService.shutdownNow();
|
||||
UserStatUtil.destroy();
|
||||
// clear the reference in Spy class.
|
||||
cleanUpSpyReference();
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdown);
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
logger.info("as-server destroy completed.");
|
||||
// see middleware-container/arthas/issues/123
|
||||
LogUtil.closeResultLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*
|
||||
* @param instrumentation JVM增强
|
||||
* @return ArthasServer单例
|
||||
*/
|
||||
public synchronized static ArthasBootstrap getInstance(int javaPid, Instrumentation instrumentation) {
|
||||
if (arthasBootstrap == null) {
|
||||
arthasBootstrap = new ArthasBootstrap(javaPid, instrumentation);
|
||||
}
|
||||
return arthasBootstrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArthasServer单例
|
||||
*/
|
||||
public static ArthasBootstrap getInstance() {
|
||||
if (arthasBootstrap == null) {
|
||||
throw new IllegalStateException("ArthasBootstrap must be initialized before!");
|
||||
}
|
||||
return arthasBootstrap;
|
||||
}
|
||||
|
||||
public void execute(Runnable command) {
|
||||
executorService.execute(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除spy中对classloader的引用,避免内存泄露
|
||||
*/
|
||||
private void cleanUpSpyReference() {
|
||||
try {
|
||||
// 从ArthasClassLoader中加载Spy
|
||||
Class<?> spyClass = this.getClass().getClassLoader().loadClass(Constants.SPY_CLASSNAME);
|
||||
Method agentDestroyMethod = spyClass.getMethod("destroy");
|
||||
agentDestroyMethod.invoke(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.error(null, "Spy load failed from ArthasClassLoader, which should not happen", e);
|
||||
} catch (Exception e) {
|
||||
logger.error(null, "Spy destroy failed: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.taobao.arthas.core.shell;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.arthas.core.shell.system.Job;
|
||||
import com.taobao.arthas.core.shell.system.JobController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An interactive session between a consumer and a shell.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public interface Shell {
|
||||
|
||||
/**
|
||||
* Create a job, the created job should then be executed with the {@link Job#run()} method.
|
||||
*
|
||||
* @param line the command line creating this job
|
||||
* @return the created job
|
||||
*/
|
||||
Job createJob(List<CliToken> line);
|
||||
|
||||
/**
|
||||
* See {@link #createJob(List)}
|
||||
*/
|
||||
Job createJob(String line);
|
||||
|
||||
/**
|
||||
* @return the shell's job controller
|
||||
*/
|
||||
JobController jobController();
|
||||
|
||||
/**
|
||||
* @return the current shell session
|
||||
*/
|
||||
Session session();
|
||||
|
||||
/**
|
||||
* Close the shell.
|
||||
*/
|
||||
void close(String reason);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.taobao.arthas.core.shell;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.CommandResolver;
|
||||
import com.taobao.arthas.core.shell.future.Future;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.handlers.NoOpHandler;
|
||||
import com.taobao.arthas.core.shell.impl.ShellServerImpl;
|
||||
import com.taobao.arthas.core.shell.term.Term;
|
||||
import com.taobao.arthas.core.shell.term.TermServer;
|
||||
|
||||
/**
|
||||
* The shell server.<p/>
|
||||
* <p>
|
||||
* A shell server is associated with a collection of {@link TermServer term servers}: the {@link #registerTermServer(TermServer)}
|
||||
* method registers a term server. Term servers life cycle are managed by this server.<p/>
|
||||
* <p>
|
||||
* When a {@link TermServer term server} receives an incoming connection, a {@link com.taobao.arthas.core.shell.system.JobController} instance is created and
|
||||
* associated with this connection.<p/>
|
||||
* <p>
|
||||
* The {@link #createShell()} method can be used to create {@link com.taobao.arthas.core.shell.system.JobController} instance for testing purposes.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public abstract class ShellServer {
|
||||
|
||||
/**
|
||||
* Create a new shell server with default options.
|
||||
*
|
||||
* @param options the options
|
||||
* @return the created shell server
|
||||
*/
|
||||
public static ShellServer create(ShellServerOptions options) {
|
||||
return new ShellServerImpl(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new shell server with specific options.
|
||||
*
|
||||
* @return the created shell server
|
||||
*/
|
||||
public static ShellServer create() {
|
||||
return new ShellServerImpl(new ShellServerOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command resolver for this server.
|
||||
*
|
||||
* @param resolver the resolver
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public abstract ShellServer registerCommandResolver(CommandResolver resolver);
|
||||
|
||||
/**
|
||||
* Register a term server to this shell server, the term server lifecycle methods are managed by this shell server.
|
||||
*
|
||||
* @param termServer the term server to add
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public abstract ShellServer registerTermServer(TermServer termServer);
|
||||
|
||||
/**
|
||||
* Create a new shell, the returned shell should be closed explicitely.
|
||||
*
|
||||
* @param term the shell associated terminal
|
||||
* @return the created shell
|
||||
*/
|
||||
public abstract Shell createShell(Term term);
|
||||
|
||||
/**
|
||||
* Create a new shell, the returned shell should be closed explicitely.
|
||||
*
|
||||
* @return the created shell
|
||||
*/
|
||||
public abstract Shell createShell();
|
||||
|
||||
/**
|
||||
* Start the shell service, this is an asynchronous start.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ShellServer listen() {
|
||||
return listen(new NoOpHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the shell service, this is an asynchronous start.
|
||||
*
|
||||
* @param listenHandler handler for getting notified when service is started
|
||||
*/
|
||||
public abstract ShellServer listen(Handler<Future<Void>> listenHandler);
|
||||
|
||||
/**
|
||||
* Close the shell server, this is an asynchronous close.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void close() {
|
||||
close(new NoOpHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the shell server, this is an asynchronous close.
|
||||
*
|
||||
* @param completionHandler handler for getting notified when service is stopped
|
||||
*/
|
||||
public abstract void close(Handler<Future<Void>> completionHandler);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.taobao.arthas.core.shell;
|
||||
|
||||
import com.taobao.arthas.core.util.ArthasBanner;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
/**
|
||||
* The configurations options for the shell server.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public class ShellServerOptions {
|
||||
|
||||
/**
|
||||
* Default of how often, in ms, to check for expired sessions
|
||||
*/
|
||||
public static final long DEFAULT_REAPER_INTERVAL = 60 * 1000; // 60 seconds
|
||||
|
||||
/**
|
||||
* Default time, in ms, that a shell session lasts for without being accessed before expiring.
|
||||
*/
|
||||
public static final long DEFAULT_SESSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
/**
|
||||
* Default time, in ms, that a server waits for a client to connect
|
||||
*/
|
||||
public static final long DEFAULT_CONNECTION_TIMEOUT = 6000; // 6 seconds
|
||||
|
||||
public static final String DEFAULT_WELCOME_MESSAGE = ArthasBanner.welcome();
|
||||
|
||||
public static final String DEFAULT_INPUTRC = "com/taobao/arthas/core/shell/term/readline/inputrc";
|
||||
|
||||
private String welcomeMessage;
|
||||
private long sessionTimeout;
|
||||
private long reaperInterval;
|
||||
private long connectionTimeout;
|
||||
private int pid;
|
||||
private Instrumentation instrumentation;
|
||||
|
||||
public ShellServerOptions() {
|
||||
welcomeMessage = DEFAULT_WELCOME_MESSAGE;
|
||||
sessionTimeout = DEFAULT_SESSION_TIMEOUT;
|
||||
connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
|
||||
reaperInterval = DEFAULT_REAPER_INTERVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the shell welcome message
|
||||
*/
|
||||
public String getWelcomeMessage() {
|
||||
return welcomeMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shell welcome message, i.e the message displayed in the user console when he connects to the shell.
|
||||
*
|
||||
* @param welcomeMessage the welcome message
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public ShellServerOptions setWelcomeMessage(String welcomeMessage) {
|
||||
this.welcomeMessage = welcomeMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the session timeout
|
||||
*/
|
||||
public long getSessionTimeout() {
|
||||
return sessionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the session timeout.
|
||||
*
|
||||
* @param sessionTimeout the new session timeout
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public ShellServerOptions setSessionTimeout(long sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the reaper interval
|
||||
*/
|
||||
public long getReaperInterval() {
|
||||
return reaperInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the repear interval, i.e the period at which session eviction is performed.
|
||||
*
|
||||
* @param reaperInterval the new repeat interval
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public ShellServerOptions setReaperInterval(long reaperInterval) {
|
||||
this.reaperInterval = reaperInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShellServerOptions setPid(int pid) {
|
||||
this.pid = pid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShellServerOptions setInstrumentation(Instrumentation instrumentation) {
|
||||
this.instrumentation = instrumentation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
public Instrumentation getInstrumentation() {
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
public long getConnectionTimeout() {
|
||||
return connectionTimeout;
|
||||
}
|
||||
|
||||
public void setConnectionTimeout(long connectionTimeout) {
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.taobao.arthas.core.shell.cli;
|
||||
|
||||
public interface CliToken {
|
||||
/**
|
||||
* @return the token value
|
||||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* @return the raw token value, that may contain unescaped chars, for instance {@literal "ab\"cd"}
|
||||
*/
|
||||
String raw();
|
||||
|
||||
/**
|
||||
* @return true when it's a text token
|
||||
*/
|
||||
boolean isText();
|
||||
|
||||
/**
|
||||
* @return true when it's a blank token
|
||||
*/
|
||||
boolean isBlank();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.taobao.arthas.core.shell.cli;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.impl.CliTokenImpl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 09/11/2016.
|
||||
*/
|
||||
public class CliTokens {
|
||||
/**
|
||||
* Create a text token.
|
||||
*
|
||||
* @param text the text
|
||||
* @return the token
|
||||
*/
|
||||
public static CliToken createText(String text) {
|
||||
return new CliTokenImpl(true, text, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new blank token.
|
||||
*
|
||||
* @param blank the blank value
|
||||
* @return the token
|
||||
*/
|
||||
public static CliToken createBlank(String blank) {
|
||||
return new CliTokenImpl(false, blank, blank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize the string argument and return a list of tokens.
|
||||
*
|
||||
* @param s the tokenized string
|
||||
* @return the tokens
|
||||
*/
|
||||
public static List<CliToken> tokenize(String s) {
|
||||
return CliTokenImpl.tokenize(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.core.shell.cli;
|
||||
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The completion object
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public interface Completion {
|
||||
|
||||
/**
|
||||
* @return the shell current session, useful for accessing data like the current path for file completion, etc...
|
||||
*/
|
||||
Session session();
|
||||
|
||||
/**
|
||||
* @return the current line being completed in raw format, i.e without any char escape performed
|
||||
*/
|
||||
String rawLine();
|
||||
|
||||
/**
|
||||
* @return the current line being completed as preparsed tokens
|
||||
*/
|
||||
List<CliToken> lineTokens();
|
||||
|
||||
/**
|
||||
* End the completion with a list of candidates, these candidates will be displayed by the shell on the console.
|
||||
*
|
||||
* @param candidates the candidates
|
||||
*/
|
||||
void complete(List<String> candidates);
|
||||
|
||||
/**
|
||||
* End the completion with a value that will be inserted to complete the line.
|
||||
*
|
||||
* @param value the value to complete with
|
||||
* @param terminal true if the value is terminal, i.e can be further completed
|
||||
*/
|
||||
void complete(String value, boolean terminal);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.taobao.arthas.core.shell.cli;
|
||||
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.arthas.core.shell.term.Tty;
|
||||
import com.taobao.arthas.core.util.usage.StyledUsageFormatter;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
import com.taobao.middleware.cli.Option;
|
||||
import com.taobao.middleware.cli.annotations.CLIConfigurator;
|
||||
import io.termd.core.util.Helper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 09/11/2016.
|
||||
*/
|
||||
public class CompletionUtils {
|
||||
|
||||
public static String findLongestCommonPrefix(Collection<String> values) {
|
||||
List<int[]> entries = new LinkedList<int[]>();
|
||||
for (String value : values) {
|
||||
int[] entry = Helper.toCodePoints(value);
|
||||
entries.add(entry);
|
||||
}
|
||||
return Helper.fromCodePoints(io.termd.core.readline.Completion.findLongestCommonPrefix(entries));
|
||||
}
|
||||
|
||||
public static void complete(Completion completion, Class<?> clazz) {
|
||||
List<CliToken> tokens = completion.lineTokens();
|
||||
CliToken lastToken = tokens.get(tokens.size() - 1);
|
||||
CLI cli = CLIConfigurator.define(clazz);
|
||||
List<com.taobao.middleware.cli.Option> options = cli.getOptions();
|
||||
if (lastToken == null || lastToken.isBlank()) {
|
||||
completeUsage(completion, cli);
|
||||
} else if (lastToken.value().startsWith("--")) {
|
||||
completeLongOption(completion, lastToken, options);
|
||||
} else if (lastToken.value().startsWith("-")) {
|
||||
completeShortOption(completion, lastToken, options);
|
||||
} else {
|
||||
completion.complete(Collections.<String>emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的查询数组中查询匹配的对象,并进行自动补全
|
||||
*/
|
||||
public static boolean complete(Completion completion, Collection<String> searchScope) {
|
||||
List<CliToken> tokens = completion.lineTokens();
|
||||
CliToken lastToken = tokens.get(tokens.size() - 1);
|
||||
List<String> candidates = new ArrayList<String>();
|
||||
for (String name: searchScope) {
|
||||
if (" ".equals(lastToken.value()) || name.startsWith(lastToken.value())) {
|
||||
candidates.add(name);
|
||||
}
|
||||
}
|
||||
if (candidates.size() == 1) {
|
||||
completion.complete(candidates.get(0).substring(lastToken.value().length()), true);
|
||||
return true;
|
||||
} else {
|
||||
completion.complete(candidates);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void completeShortOption(Completion completion, CliToken lastToken, List<Option> options) {
|
||||
String prefix = lastToken.value().substring(1);
|
||||
List<String> candidates = new ArrayList<String>();
|
||||
for (Option option : options) {
|
||||
if (option.getShortName().startsWith(prefix)) {
|
||||
candidates.add(option.getShortName());
|
||||
}
|
||||
}
|
||||
complete(completion, prefix, candidates);
|
||||
}
|
||||
|
||||
public static void completeLongOption(Completion completion, CliToken lastToken, List<Option> options) {
|
||||
String prefix = lastToken.value().substring(2);
|
||||
List<String> candidates = new ArrayList<String>();
|
||||
for (Option option : options) {
|
||||
if (option.getLongName().startsWith(prefix)) {
|
||||
candidates.add(option.getLongName());
|
||||
}
|
||||
}
|
||||
complete(completion, prefix, candidates);
|
||||
}
|
||||
|
||||
public static void completeUsage(Completion completion, CLI cli) {
|
||||
Tty tty = completion.session().get(Session.TTY);
|
||||
String usage = StyledUsageFormatter.styledUsage(cli, tty.width());
|
||||
completion.complete(Collections.singletonList(usage));
|
||||
}
|
||||
|
||||
private static void complete(Completion completion, String prefix, List<String> candidates) {
|
||||
if (candidates.size() == 1) {
|
||||
completion.complete(candidates.get(0).substring(prefix.length()), true);
|
||||
} else {
|
||||
String commonPrefix = CompletionUtils.findLongestCommonPrefix(candidates);
|
||||
if (commonPrefix.length() > 0) {
|
||||
completion.complete(commonPrefix, false);
|
||||
} else {
|
||||
completion.complete(candidates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.taobao.arthas.core.shell.cli.impl;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import io.termd.core.readline.LineStatus;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public class CliTokenImpl implements CliToken {
|
||||
|
||||
final boolean text;
|
||||
final String raw;
|
||||
final String value;
|
||||
|
||||
public CliTokenImpl(boolean text, String value) {
|
||||
this(text, value, value);
|
||||
}
|
||||
|
||||
public CliTokenImpl(boolean text, String raw, String value) {
|
||||
this.text = text;
|
||||
this.raw = raw;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlank() {
|
||||
return !text;
|
||||
}
|
||||
|
||||
public String raw() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof CliTokenImpl) {
|
||||
CliTokenImpl that = (CliTokenImpl) obj;
|
||||
return text == that.text && value.equals(that.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CliToken[text=" + text + ",value=" + value + "]";
|
||||
}
|
||||
|
||||
public static List<CliToken> tokenize(String s) {
|
||||
|
||||
List<CliToken> tokens = new LinkedList<CliToken>();
|
||||
|
||||
tokenize(s, 0, tokens);
|
||||
|
||||
return tokens;
|
||||
|
||||
}
|
||||
|
||||
private static void tokenize(String s, int index, List<CliToken> builder) {
|
||||
while (index < s.length()) {
|
||||
char c = s.charAt(index);
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
index = blankToken(s, index, builder);
|
||||
break;
|
||||
default:
|
||||
index = textToken(s, index, builder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Todo use code points and not chars
|
||||
private static int textToken(String s, int index, List<CliToken> builder) {
|
||||
LineStatus quoter = new LineStatus();
|
||||
int from = index;
|
||||
StringBuilder value = new StringBuilder();
|
||||
while (index < s.length()) {
|
||||
char c = s.charAt(index);
|
||||
quoter.accept(c);
|
||||
if (!quoter.isQuoted() && !quoter.isEscaped() && isBlank(c)) {
|
||||
break;
|
||||
}
|
||||
if (quoter.isCodePoint()) {
|
||||
if (quoter.isEscaped() && quoter.isWeaklyQuoted() && c != '"') {
|
||||
value.append('\\');
|
||||
}
|
||||
value.append(c);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
builder.add(new CliTokenImpl(true, s.substring(from, index), value.toString()));
|
||||
return index;
|
||||
}
|
||||
|
||||
private static int blankToken(String s, int index, List<CliToken> builder) {
|
||||
int from = index;
|
||||
while (index < s.length() && isBlank(s.charAt(index))) {
|
||||
index++;
|
||||
}
|
||||
builder.add(new CliTokenImpl(false, s.substring(from, index)));
|
||||
return index;
|
||||
}
|
||||
|
||||
private static boolean isBlank(char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.taobao.arthas.core.shell.command;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.cli.CompletionUtils;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The base command class that Java annotated command should extend.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public abstract class AnnotatedCommand {
|
||||
|
||||
/**
|
||||
* @return the command name
|
||||
*/
|
||||
public String name() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the command line interface, can be null
|
||||
*/
|
||||
public CLI cli() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the command, when the command is done processing it should call the {@link CommandProcess#end()} method.
|
||||
*
|
||||
* @param process the command process
|
||||
*/
|
||||
public abstract void process(CommandProcess process);
|
||||
|
||||
/**
|
||||
* Perform command completion, when the command is done completing it should call {@link Completion#complete(List)}
|
||||
* or {@link Completion#complete(String, boolean)} )} method to signal completion is done.
|
||||
*
|
||||
* @param completion the completion object
|
||||
*/
|
||||
public void complete(Completion completion) {
|
||||
CompletionUtils.complete(completion, this.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.taobao.arthas.core.shell.command;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Command {
|
||||
|
||||
/**
|
||||
* Create a command from a Java class, annotated with CLI annotations.
|
||||
*
|
||||
* @param clazz the class of the command
|
||||
* @return the command object
|
||||
*/
|
||||
public static Command create(final Class<? extends AnnotatedCommand> clazz) {
|
||||
return new AnnotatedCommandImpl(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the command name
|
||||
*/
|
||||
public String name() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the command line interface, can be null
|
||||
*/
|
||||
public CLI cli() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new process with the passed arguments.
|
||||
*
|
||||
* @return the process handler
|
||||
*/
|
||||
public abstract Handler<CommandProcess> processHandler();
|
||||
|
||||
/**
|
||||
* Perform command completion, when the command is done completing it should call {@link Completion#complete(List)}
|
||||
* or {@link Completion#complete(String, boolean)} )} method to signal completion is done.
|
||||
*
|
||||
* @param completion the completion object
|
||||
*/
|
||||
public void complete(Completion completion) {
|
||||
completion.complete(Collections.<String>emptyList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.taobao.arthas.core.shell.command;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.impl.CommandBuilderImpl;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
|
||||
/**
|
||||
* command builder
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public abstract class CommandBuilder {
|
||||
|
||||
/**
|
||||
* Create a new commmand builder, the command is responsible for managing the options and arguments via the
|
||||
* {@link CommandProcess#args() arguments}.
|
||||
*
|
||||
* @param name the command name
|
||||
* @return the command
|
||||
*/
|
||||
public static CommandBuilder command(String name) {
|
||||
return new CommandBuilderImpl(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new commmand with its {@link CLI} descriptor. This command can then retrieve the parsed
|
||||
* {@link CommandProcess#commandLine()} when it executes to know get the command arguments and options.
|
||||
*
|
||||
* @param cli the cli to use
|
||||
* @return the command
|
||||
*/
|
||||
public static CommandBuilder command(CLI cli) {
|
||||
return new CommandBuilderImpl(cli.getName(), cli);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command process handler, the process handler is called when the command is executed.
|
||||
*
|
||||
* @param handler the process handler
|
||||
* @return this command object
|
||||
*/
|
||||
public abstract CommandBuilder processHandler(Handler<CommandProcess> handler);
|
||||
|
||||
/**
|
||||
* Set the command completion handler, the completion handler when the user asks for contextual command line
|
||||
* completion, usually hitting the <i>tab</i> key.
|
||||
*
|
||||
* @param handler the completion handler
|
||||
* @return this command object
|
||||
*/
|
||||
public abstract CommandBuilder completionHandler(Handler<Completion> handler);
|
||||
|
||||
/**
|
||||
* Build the command
|
||||
*
|
||||
* @return the built command
|
||||
*/
|
||||
public abstract Command build();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package com.taobao.arthas.core.shell.command;
|
||||
|
||||
import com.taobao.arthas.core.advisor.AdviceListener;
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.arthas.core.shell.term.Tty;
|
||||
import com.taobao.middleware.cli.CommandLine;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* The command process provides interaction with the process of the command.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public interface CommandProcess extends Tty {
|
||||
/**
|
||||
* @return the unparsed arguments tokens
|
||||
*/
|
||||
List<CliToken> argsTokens();
|
||||
|
||||
/**
|
||||
* @return the actual string arguments of the command
|
||||
*/
|
||||
List<String> args();
|
||||
|
||||
/**
|
||||
* @return the command line object or null
|
||||
*/
|
||||
CommandLine commandLine();
|
||||
|
||||
/**
|
||||
* @return the shell session
|
||||
*/
|
||||
Session session();
|
||||
|
||||
/**
|
||||
* @return true if the command is running in foreground
|
||||
*/
|
||||
boolean isForeground();
|
||||
|
||||
CommandProcess stdinHandler(Handler<String> handler);
|
||||
|
||||
/**
|
||||
* Set an interrupt handler, this handler is called when the command is interrupted, for instance user
|
||||
* press <code>Ctrl-C</code>.
|
||||
*
|
||||
* @param handler the interrupt handler
|
||||
* @return this command
|
||||
*/
|
||||
CommandProcess interruptHandler(Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* Set a suspend handler, this handler is called when the command is suspended, for instance user
|
||||
* press <code>Ctrl-Z</code>.
|
||||
*
|
||||
* @param handler the interrupt handler
|
||||
* @return this command
|
||||
*/
|
||||
CommandProcess suspendHandler(Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* Set a resume handler, this handler is called when the command is resumed, for instance user
|
||||
* types <code>bg</code> or <code>fg</code> to resume the command.
|
||||
*
|
||||
* @param handler the interrupt handler
|
||||
* @return this command
|
||||
*/
|
||||
CommandProcess resumeHandler(Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* Set an end handler, this handler is called when the command is ended, for instance the command is running
|
||||
* and the shell closes.
|
||||
*
|
||||
* @param handler the end handler
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
CommandProcess endHandler(Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* Write some text to the standard output.
|
||||
*
|
||||
* @param data the text
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
CommandProcess write(String data);
|
||||
|
||||
/**
|
||||
* Set a background handler, this handler is called when the command is running and put to background.
|
||||
*
|
||||
* @param handler the background handler
|
||||
* @return this command
|
||||
*/
|
||||
CommandProcess backgroundHandler(Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* Set a foreground handler, this handler is called when the command is running and put to foreground.
|
||||
*
|
||||
* @param handler the foreground handler
|
||||
* @return this command
|
||||
*/
|
||||
CommandProcess foregroundHandler(Handler<Void> handler);
|
||||
|
||||
@Override
|
||||
CommandProcess resizehandler(Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* End the process with the exit status {@literal 0}
|
||||
*/
|
||||
void end();
|
||||
|
||||
/**
|
||||
* End the process.
|
||||
*
|
||||
* @param status the exit status.
|
||||
*/
|
||||
void end(int status);
|
||||
|
||||
|
||||
/**
|
||||
* Register listener
|
||||
*
|
||||
* @param lock the lock for enhance class
|
||||
* @param listener
|
||||
*/
|
||||
void register(int lock, AdviceListener listener);
|
||||
|
||||
/**
|
||||
* Unregister listener
|
||||
*/
|
||||
void unregister();
|
||||
|
||||
/**
|
||||
* Execution times
|
||||
*
|
||||
* @return execution times
|
||||
*/
|
||||
AtomicInteger times();
|
||||
|
||||
/**
|
||||
* Resume process
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* Suspend process
|
||||
*/
|
||||
void suspend();
|
||||
|
||||
/**
|
||||
* echo tips
|
||||
*
|
||||
* @param tips process tips
|
||||
*/
|
||||
void echoTips(String tips);
|
||||
|
||||
/**
|
||||
* Get cache file location
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String cacheLocation();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.taobao.arthas.core.shell.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A registry that contains the commands known by a shell.<p/>
|
||||
* <p>
|
||||
* It is a mutable command resolver.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public class CommandRegistry implements CommandResolver {
|
||||
final ConcurrentHashMap<String, Command> commandMap = new ConcurrentHashMap<String, Command>();
|
||||
|
||||
/**
|
||||
* Create a new registry.
|
||||
*
|
||||
* @return the created registry
|
||||
*/
|
||||
public static CommandRegistry create() {
|
||||
return new CommandRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single command.
|
||||
*/
|
||||
public CommandRegistry registerCommand(Class<? extends AnnotatedCommand> command) {
|
||||
return registerCommand(Command.create(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command
|
||||
*
|
||||
* @param command the command to register
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public CommandRegistry registerCommand(Command command) {
|
||||
return registerCommands(Collections.singletonList(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a list of commands.
|
||||
*
|
||||
* @param commands the commands to register
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public CommandRegistry registerCommands(List<Command> commands) {
|
||||
for (Command command : commands) {
|
||||
commandMap.put(command.name(), command);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unregister a command.
|
||||
*
|
||||
* @param commandName the command name
|
||||
* @return a reference to this, so the API can be used fluently
|
||||
*/
|
||||
public CommandRegistry unregisterCommand(String commandName) {
|
||||
commandMap.remove(commandName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Command> commands() {
|
||||
return new ArrayList<Command>(commandMap.values());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.taobao.arthas.core.shell.command;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A resolver for commands, so the shell can discover commands.
|
||||
*
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public interface CommandResolver {
|
||||
/**
|
||||
* @return the current commands
|
||||
*/
|
||||
List<Command> commands();
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.taobao.arthas.core.shell.command.impl;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.Command;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.util.UserStatUtil;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
import com.taobao.middleware.cli.Option;
|
||||
import com.taobao.middleware.cli.annotations.CLIConfigurator;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 10/11/2016.
|
||||
*/
|
||||
public class AnnotatedCommandImpl extends Command {
|
||||
|
||||
private CLI cli;
|
||||
private Class<? extends AnnotatedCommand> clazz;
|
||||
private Handler<CommandProcess> processHandler = new ProcessHandler();
|
||||
|
||||
public AnnotatedCommandImpl(Class<? extends AnnotatedCommand> clazz) {
|
||||
this.clazz = clazz;
|
||||
cli = CLIConfigurator.define(clazz);
|
||||
cli.addOption(new Option().setArgName("help").setFlag(true).setShortName("h").setLongName("help")
|
||||
.setDescription("this help").setHelp(true));
|
||||
}
|
||||
|
||||
private boolean shouldOverridesName(Class<? extends AnnotatedCommand> clazz) {
|
||||
try {
|
||||
clazz.getDeclaredMethod("name");
|
||||
return true;
|
||||
} catch (NoSuchMethodException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldOverrideCli(Class<? extends AnnotatedCommand> clazz) {
|
||||
try {
|
||||
clazz.getDeclaredMethod("cli");
|
||||
return true;
|
||||
} catch (NoSuchMethodException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
if (shouldOverridesName(clazz)) {
|
||||
try {
|
||||
return clazz.newInstance().name();
|
||||
} catch (Exception ignore) {
|
||||
// Use cli.getName() instead
|
||||
}
|
||||
}
|
||||
return cli.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CLI cli() {
|
||||
if (shouldOverrideCli(clazz)) {
|
||||
try {
|
||||
return clazz.newInstance().cli();
|
||||
} catch (Exception ignore) {
|
||||
// Use cli instead
|
||||
}
|
||||
}
|
||||
return cli;
|
||||
}
|
||||
|
||||
private void process(CommandProcess process) {
|
||||
AnnotatedCommand instance;
|
||||
try {
|
||||
instance = clazz.newInstance();
|
||||
} catch (Exception e) {
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
CLIConfigurator.inject(process.commandLine(), instance);
|
||||
instance.process(process);
|
||||
UserStatUtil.arthasUsageSuccess(name(), process.args());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Handler<CommandProcess> processHandler() {
|
||||
return processHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(final Completion completion) {
|
||||
final AnnotatedCommand instance;
|
||||
try {
|
||||
instance = clazz.newInstance();
|
||||
} catch (Exception e) {
|
||||
super.complete(completion);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
instance.complete(completion);
|
||||
} catch (Throwable t) {
|
||||
completion.complete(Collections.<String>emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcessHandler implements Handler<CommandProcess> {
|
||||
@Override
|
||||
public void handle(CommandProcess process) {
|
||||
process(process);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.taobao.arthas.core.shell.command.impl;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.command.Command;
|
||||
import com.taobao.arthas.core.shell.command.CommandBuilder;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.middleware.cli.CLI;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
*/
|
||||
public class CommandBuilderImpl extends CommandBuilder {
|
||||
|
||||
private final String name;
|
||||
private final CLI cli;
|
||||
private Handler<CommandProcess> processHandler;
|
||||
private Handler<Completion> completeHandler;
|
||||
|
||||
public CommandBuilderImpl(String name, CLI cli) {
|
||||
this.name = name;
|
||||
this.cli = cli;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandBuilderImpl processHandler(Handler<CommandProcess> handler) {
|
||||
processHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandBuilderImpl completionHandler(Handler<Completion> handler) {
|
||||
completeHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command build() {
|
||||
return new CommandImpl();
|
||||
}
|
||||
|
||||
private class CommandImpl extends Command {
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CLI cli() {
|
||||
return cli;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Handler<CommandProcess> processHandler() {
|
||||
return processHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(final Completion completion) {
|
||||
if (completeHandler != null) {
|
||||
try {
|
||||
completeHandler.handle(completion);
|
||||
} catch (Throwable t) {
|
||||
completion.complete(Collections.<String>emptyList());
|
||||
}
|
||||
} else {
|
||||
super.complete(completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import io.termd.core.function.Function;
|
||||
|
||||
/**
|
||||
* @author diecui1202 on 2017/11/2.
|
||||
*/
|
||||
public interface CloseFunction extends Function<String, String> {
|
||||
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import com.taobao.middleware.cli.Argument;
|
||||
import com.taobao.middleware.cli.CLIs;
|
||||
import com.taobao.middleware.cli.CommandLine;
|
||||
import com.taobao.middleware.cli.Option;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 12/12/2016.
|
||||
*/
|
||||
public class GrepHandler extends StdoutHandler {
|
||||
public static final String NAME = "grep";
|
||||
|
||||
private String keyword;
|
||||
private boolean ignoreCase;
|
||||
|
||||
public static StdoutHandler inject(List<CliToken> tokens) {
|
||||
List<String> args = StdoutHandler.parseArgs(tokens, NAME);
|
||||
CommandLine commandLine = CLIs.create(NAME)
|
||||
.addOption(new Option().setShortName("i").setLongName("ignore-case").setFlag(true))
|
||||
.addArgument(new Argument().setArgName("keyword").setIndex(0))
|
||||
.parse(args);
|
||||
Boolean ignoreCase = commandLine.isFlagEnabled("ignore-case");
|
||||
String keyword = commandLine.getArgumentValue(0);
|
||||
return new GrepHandler(keyword, ignoreCase);
|
||||
}
|
||||
|
||||
private GrepHandler(String keyword, boolean ignoreCase) {
|
||||
this.keyword = keyword;
|
||||
this.ignoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String input) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
String[] lines = input.split("\n");
|
||||
for (String line : lines) {
|
||||
if (ignoreCase) {
|
||||
line = line.toLowerCase();
|
||||
keyword = keyword.toLowerCase();
|
||||
}
|
||||
|
||||
if (line.contains(keyword)) {
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import com.taobao.text.util.RenderUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 20/12/2016.
|
||||
*/
|
||||
public class PlainTextHandler extends StdoutHandler {
|
||||
public static String NAME = "plaintext";
|
||||
|
||||
public static StdoutHandler inject(List<CliToken> tokens) {
|
||||
return new PlainTextHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String s) {
|
||||
return RenderUtil.ansiToPlainText(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.middleware.logger.LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* 重定向处理类
|
||||
*
|
||||
* @author gehui 2017年7月27日 上午11:38:40
|
||||
*/
|
||||
public class RedirectHandler extends PlainTextHandler implements CloseFunction {
|
||||
|
||||
private Logger logger = null;
|
||||
|
||||
public RedirectHandler() {
|
||||
|
||||
}
|
||||
|
||||
public RedirectHandler(String name) {
|
||||
com.taobao.middleware.logger.Logger log = LoggerFactory.getLogger(name);
|
||||
log.activateAppenderWithSizeRolling("arthas-cache", name, "UTF-8", "200MB", 3);
|
||||
log.setAdditivity(false);
|
||||
log.activateAsync(128, -1);
|
||||
logger = (Logger) log.getDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String data) {
|
||||
data = super.apply(data);
|
||||
if (logger != null) {
|
||||
logger.info(data);
|
||||
} else {
|
||||
LogUtil.getResultLogger().info(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LogUtil.closeSlf4jLogger(logger);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import io.termd.core.function.Function;
|
||||
|
||||
/**
|
||||
* 统计类Function的接口
|
||||
*
|
||||
* @author diecui1202 on 2017/10/24.
|
||||
*/
|
||||
public interface StatisticsFunction extends Function<String, String> {
|
||||
|
||||
String result();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import io.termd.core.function.Function;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 20/12/2016.
|
||||
*/
|
||||
public abstract class StdoutHandler implements Function<String, String> {
|
||||
|
||||
public static StdoutHandler inject(List<CliToken> tokens) {
|
||||
CliToken firstTextToken = null;
|
||||
for (CliToken token : tokens) {
|
||||
if (token.isText()) {
|
||||
firstTextToken = token;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstTextToken == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (firstTextToken.value().equals(GrepHandler.NAME)) {
|
||||
return GrepHandler.inject(tokens);
|
||||
} else if (firstTextToken.value().equals(PlainTextHandler.NAME)) {
|
||||
return PlainTextHandler.inject(tokens);
|
||||
} else if (firstTextToken.value().equals(WordCountHandler.NAME)) {
|
||||
return WordCountHandler.inject(tokens);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> parseArgs(List<CliToken> tokens, String command) {
|
||||
List<String> args = new LinkedList<String>();
|
||||
boolean found = false;
|
||||
for (CliToken token : tokens) {
|
||||
if (token.isText() && token.value().equals(command)) {
|
||||
found = true;
|
||||
} else if (token.isText() && found) {
|
||||
args.add(token.value());
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String s) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import com.taobao.arthas.core.shell.term.Term;
|
||||
|
||||
/**
|
||||
* 将数据写到term
|
||||
*
|
||||
* @author gehui 2017年7月26日 上午11:20:00
|
||||
*/
|
||||
public class TermHandler extends StdoutHandler {
|
||||
private Term term;
|
||||
|
||||
public TermHandler(Term term) {
|
||||
this.term = term;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String data) {
|
||||
term.write(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.taobao.arthas.core.shell.command.internal;
|
||||
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import com.taobao.middleware.cli.CLIs;
|
||||
import com.taobao.middleware.cli.CommandLine;
|
||||
import com.taobao.middleware.cli.Option;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-02-23 23:28.
|
||||
*/
|
||||
public class WordCountHandler extends StdoutHandler implements StatisticsFunction {
|
||||
|
||||
public static final String NAME = "wc";
|
||||
|
||||
private boolean lineMode;
|
||||
|
||||
private String result = null;
|
||||
private volatile int total = 0;
|
||||
|
||||
public static StdoutHandler inject(List<CliToken> tokens) {
|
||||
List<String> args = StdoutHandler.parseArgs(tokens, NAME);
|
||||
CommandLine commandLine = CLIs.create(NAME)
|
||||
.addOption(new Option().setShortName("l").setFlag(true))
|
||||
.parse(args);
|
||||
Boolean lineMode = commandLine.isFlagEnabled("l");
|
||||
return new WordCountHandler(lineMode);
|
||||
}
|
||||
|
||||
private WordCountHandler(boolean lineMode) {
|
||||
this.lineMode = lineMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String input) {
|
||||
if (!this.lineMode) {
|
||||
// TODO the default behavior should be equivalent to `wc -l -w -c`
|
||||
result = "wc currently only support wc -l!\n";
|
||||
} else {
|
||||
if (input != null && !"".equals(input.trim())) {
|
||||
total += input.split("\n").length;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String result() {
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return total + "\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.taobao.arthas.core.shell.future;
|
||||
|
||||
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
|
||||
public class Future<T> {
|
||||
private boolean failed;
|
||||
private boolean succeeded;
|
||||
private Handler<Future<T>> handler;
|
||||
private T result;
|
||||
private Throwable throwable;
|
||||
|
||||
public Future() {
|
||||
}
|
||||
|
||||
public Future(Throwable t) {
|
||||
fail(t);
|
||||
}
|
||||
|
||||
public Future(String failureMessage) {
|
||||
this(new Throwable(failureMessage));
|
||||
}
|
||||
|
||||
public Future(T result) {
|
||||
complete(result);
|
||||
}
|
||||
|
||||
public static <T> Future<T> future() {
|
||||
return new Future<T>();
|
||||
}
|
||||
|
||||
public static <T> Future<T> succeededFuture() {
|
||||
return new Future<T>((T) null);
|
||||
}
|
||||
|
||||
public static <T> Future<T> succeededFuture(T result) {
|
||||
return new Future<T>(result);
|
||||
}
|
||||
|
||||
public static <T> Future<T> failedFuture(Throwable t) {
|
||||
return new Future<T>(t);
|
||||
}
|
||||
|
||||
public static <T> Future<T> failedFuture(String failureMessage) {
|
||||
return new Future<T>(failureMessage);
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return failed || succeeded;
|
||||
}
|
||||
|
||||
public Future<T> setHandler(Handler<Future<T>> handler) {
|
||||
this.handler = handler;
|
||||
checkCallHandler();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public void complete(T result) {
|
||||
checkComplete();
|
||||
this.result = result;
|
||||
succeeded = true;
|
||||
checkCallHandler();
|
||||
}
|
||||
|
||||
public void complete() {
|
||||
complete(null);
|
||||
}
|
||||
|
||||
public void fail(Throwable throwable) {
|
||||
checkComplete();
|
||||
this.throwable = throwable;
|
||||
failed = true;
|
||||
checkCallHandler();
|
||||
}
|
||||
|
||||
public void fail(String failureMessage) {
|
||||
fail(new Throwable(failureMessage));
|
||||
}
|
||||
|
||||
public T result() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Throwable cause() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
public boolean succeeded() {
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
public boolean failed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public Handler<Future<T>> completer() {
|
||||
return new Handler<Future<T>>() {
|
||||
@Override
|
||||
public void handle(Future<T> ar) {
|
||||
if (ar.succeeded()) {
|
||||
complete(ar.result());
|
||||
} else {
|
||||
fail(ar.cause());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void checkCallHandler() {
|
||||
if (handler != null && isComplete()) {
|
||||
handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkComplete() {
|
||||
if (succeeded || failed) {
|
||||
throw new IllegalStateException("Result is already complete: " + (succeeded ? "succeeded" : "failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.taobao.arthas.core.shell.handlers;
|
||||
|
||||
import com.taobao.arthas.core.shell.future.Future;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-04-24 18:23.
|
||||
*/
|
||||
public class BindHandler implements Handler<Future<Void>> {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
private AtomicBoolean isBindRef;
|
||||
|
||||
public BindHandler(AtomicBoolean isBindRef) {
|
||||
this.isBindRef = isBindRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Future<Void> event) {
|
||||
if (event.failed()) {
|
||||
logger.error(null, "Error listening term server:", event.cause());
|
||||
isBindRef.compareAndSet(true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.taobao.arthas.core.shell.handlers;
|
||||
|
||||
public interface Handler<E> {
|
||||
/**
|
||||
* Something has happened, so handle it.
|
||||
*
|
||||
* @param event the event to handle
|
||||
*/
|
||||
void handle(E event);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.taobao.arthas.core.shell.handlers;
|
||||
|
||||
import com.taobao.arthas.core.shell.future.Future;
|
||||
import com.taobao.arthas.core.util.LogUtil;
|
||||
import com.taobao.middleware.logger.Logger;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 22/11/2016.
|
||||
*/
|
||||
public class NoOpHandler implements Handler {
|
||||
|
||||
private static final Logger logger = LogUtil.getArthasLogger();
|
||||
|
||||
@Override
|
||||
public void handle(Object event) {
|
||||
if (event instanceof Future && ((Future) event).failed()) {
|
||||
logger.error(null, "Error listening term server:", ((Future) event).cause());
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.taobao.arthas.core.shell.handlers.command;
|
||||
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2017-01-09 13:23.
|
||||
*/
|
||||
public class CommandInterruptHandler implements Handler<Void> {
|
||||
|
||||
private CommandProcess process;
|
||||
|
||||
public CommandInterruptHandler(CommandProcess process) {
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
process.end();
|
||||
process.session().unLock();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.taobao.arthas.core.shell.handlers.server;
|
||||
|
||||
import com.taobao.arthas.core.shell.future.Future;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.impl.ShellImpl;
|
||||
import com.taobao.arthas.core.shell.impl.ShellServerImpl;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 23/11/2016.
|
||||
*/
|
||||
public class SessionClosedHandler implements Handler<Future<Void>> {
|
||||
private ShellServerImpl shellServer;
|
||||
private final ShellImpl session;
|
||||
|
||||
public SessionClosedHandler(ShellServerImpl shellServer, ShellImpl session) {
|
||||
this.shellServer = shellServer;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Future<Void> ar) {
|
||||
shellServer.removeSession(session);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.taobao.arthas.core.shell.handlers.server;
|
||||
|
||||
import com.taobao.arthas.core.shell.future.Future;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 23/11/2016.
|
||||
*/
|
||||
public class SessionsClosedHandler implements Handler<Future<Void>> {
|
||||
private final AtomicInteger count;
|
||||
private final Handler<Future<Void>> completionHandler;
|
||||
|
||||
public SessionsClosedHandler(AtomicInteger count, Handler<Future<Void>> completionHandler) {
|
||||
this.count = count;
|
||||
this.completionHandler = completionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Future<Void> event) {
|
||||
if (count.decrementAndGet() == 0) {
|
||||
completionHandler.handle(Future.<Void>succeededFuture());
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.taobao.arthas.core.shell.handlers.server;
|
||||
|
||||
import com.taobao.arthas.core.shell.future.Future;
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.impl.ShellServerImpl;
|
||||
import com.taobao.arthas.core.shell.term.TermServer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 23/11/2016.
|
||||
*/
|
||||
public class TermServerListenHandler implements Handler<Future<TermServer>> {
|
||||
private ShellServerImpl shellServer;
|
||||
private Handler<Future<Void>> listenHandler;
|
||||
private List<TermServer> toStart;
|
||||
private AtomicInteger count;
|
||||
private AtomicBoolean failed;
|
||||
|
||||
public TermServerListenHandler(ShellServerImpl shellServer, Handler<Future<Void>> listenHandler, List<TermServer> toStart) {
|
||||
this.shellServer = shellServer;
|
||||
this.listenHandler = listenHandler;
|
||||
this.toStart = toStart;
|
||||
this.count = new AtomicInteger(toStart.size());
|
||||
this.failed = new AtomicBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Future<TermServer> ar) {
|
||||
if (ar.failed()) {
|
||||
failed.set(true);
|
||||
}
|
||||
|
||||
if (count.decrementAndGet() == 0) {
|
||||
if (failed.get()) {
|
||||
listenHandler.handle(Future.<Void>failedFuture(ar.cause()));
|
||||
for (TermServer termServer : toStart) {
|
||||
termServer.close();
|
||||
}
|
||||
} else {
|
||||
shellServer.setClosed(false);
|
||||
shellServer.setTimer();
|
||||
listenHandler.handle(Future.<Void>succeededFuture());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.taobao.arthas.core.shell.handlers.server;
|
||||
|
||||
import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.impl.ShellServerImpl;
|
||||
import com.taobao.arthas.core.shell.term.Term;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 23/11/2016.
|
||||
*/
|
||||
public class TermServerTermHandler implements Handler<Term> {
|
||||
private ShellServerImpl shellServer;
|
||||
|
||||
public TermServerTermHandler(ShellServerImpl shellServer) {
|
||||
this.shellServer = shellServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Term term) {
|
||||
shellServer.handleTerm(term);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user