This commit is contained in:
hengyunabc
2018-08-31 11:49:48 +08:00
parent ebfb0c6f96
commit 3fbfab4288
235 changed files with 30452 additions and 0 deletions
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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时停止timerresume时重启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();
}
}
}
@@ -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");
}
}
}
@@ -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;
}
}
@@ -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时停止timerresume时重启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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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();
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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());
}
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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());
}
}
}
@@ -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());
}
}
}
}
@@ -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