mirror of
https://gitee.com/arthas/arthas.git
synced 2024-12-01 19:58:30 +08:00
init
This commit is contained in:
parent
ebfb0c6f96
commit
3fbfab4288
23
TODO.md
Normal file
23
TODO.md
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
* 代码还是很乱,需要继续重构
|
||||
* 依赖需要清理,几个问题:
|
||||
* 所有 apache 的 common 库应当不需要
|
||||
* json 库有好几份
|
||||
* `jopt-simple` 看下能不能用 `cli` 取代
|
||||
* `cli`, `termd` 的 artifactId, version 需要想下。是不是应该直接拿进来。他们的依赖也需要仔细看一下
|
||||
* termd 依赖 netty,感觉有点重,而且第一次 attach 比较慢,不确定是 netty 的问题还是 attach 的问题
|
||||
* 目前 web console 依赖 termd 中自带的 term.js 和 css,需要美化,需要想下如何集成到研发门户上
|
||||
* 因为现在没有 Java 客户端了,所以 batch mode 也就没有了
|
||||
* `com.taobao.arthas.core.shell.session.Session` 的能力需要和以前的 session 的实现对标。其中:
|
||||
* 真的需要 textmode 吗?我觉得这个应该是 option 的事情
|
||||
* 真的需要 encoding 吗?我觉得仍然应该在 option 中定义,就算是真的需要,因为我觉得就应该是 UTF-8
|
||||
* duration 是应当展示的,session 的列表也许也应当展示
|
||||
* 需要仔细看下 session 过期是否符合预期
|
||||
* 多人协作的时候 session 原来是在多人之间共享的吗?
|
||||
* 所有的命令现在实现的是 AnnotatedCommand,需要继续增强的是:
|
||||
* Help 中的格式化输出被删除。需要为 `@Description` 定义一套统一的格式
|
||||
* 命令的输入以及输出的日志 (record logger) 被删除,需要重新实现,因为现在是用 `CommandProcess` 来输出,所以,需要在 `CommandProcess` 的实现里打日志
|
||||
* `com.taobao.arthas.core.GlobalOptions` 看上去好奇怪,感觉是 OptionCommand 应当做的事情
|
||||
* `com.taobao.arthas.core.config.Configure` 需要清理,尤其是和 http 相关的
|
||||
* 需要合并 develop 分支上后续的修复
|
||||
* 代码中的 TODO/FIXME
|
69
agent/pom.xml
Normal file
69
agent/pom.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-all</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>arthas-agent</artifactId>
|
||||
<name>arthas-agent</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-spy</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>arthas-agent</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>attached</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Premain-Class>com.taobao.arthas.agent.AgentBootstrap</Premain-Class>
|
||||
<Agent-Class>com.taobao.arthas.agent.AgentBootstrap</Agent-Class>
|
||||
<Can-Redefine-Classes>true</Can-Redefine-Classes>
|
||||
<Can-Retransform-Classes>true</Can-Retransform-Classes>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
168
agent/src/main/java/com/taobao/arthas/agent/AgentBootstrap.java
Normal file
168
agent/src/main/java/com/taobao/arthas/agent/AgentBootstrap.java
Normal file
@ -0,0 +1,168 @@
|
||||
package com.taobao.arthas.agent;
|
||||
|
||||
import java.arthas.Spy;
|
||||
import java.io.*;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* 代理启动类
|
||||
*
|
||||
* @author vlinux on 15/5/19.
|
||||
*/
|
||||
public class AgentBootstrap {
|
||||
|
||||
private static final String ADVICEWEAVER = "com.taobao.arthas.core.advisor.AdviceWeaver";
|
||||
private static final String ON_BEFORE = "methodOnBegin";
|
||||
private static final String ON_RETURN = "methodOnReturnEnd";
|
||||
private static final String ON_THROWS = "methodOnThrowingEnd";
|
||||
private static final String BEFORE_INVOKE = "methodOnInvokeBeforeTracing";
|
||||
private static final String AFTER_INVOKE = "methodOnInvokeAfterTracing";
|
||||
private static final String THROW_INVOKE = "methodOnInvokeThrowTracing";
|
||||
private static final String RESET = "resetArthasClassLoader";
|
||||
private static final String ARTHAS_SPY_JAR = "arthas-spy.jar";
|
||||
private static final String ARTHAS_CONFIGURE = "com.taobao.arthas.core.config.Configure";
|
||||
private static final String ARTHAS_BOOTSTRAP = "com.taobao.arthas.core.server.ArthasBootstrap";
|
||||
private static final String TO_CONFIGURE = "toConfigure";
|
||||
private static final String GET_JAVA_PID = "getJavaPid";
|
||||
private static final String GET_INSTANCE = "getInstance";
|
||||
private static final String IS_BIND = "isBind";
|
||||
private static final String BIND = "bind";
|
||||
|
||||
private static PrintStream ps = System.err;
|
||||
static {
|
||||
try {
|
||||
File log = new File(System.getProperty("user.home") + File.separator + "logs" + File.separator
|
||||
+ "arthas" + File.separator + "arthas.log");
|
||||
if (!log.exists()) {
|
||||
log.getParentFile().mkdir();
|
||||
log.createNewFile();
|
||||
}
|
||||
ps = new PrintStream(new FileOutputStream(log, true));
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(ps);
|
||||
}
|
||||
}
|
||||
|
||||
// 全局持有classloader用于隔离 Arthas 实现
|
||||
private static volatile ClassLoader arthasClassLoader;
|
||||
|
||||
public static void premain(String args, Instrumentation inst) {
|
||||
main(args, inst);
|
||||
}
|
||||
|
||||
public static void agentmain(String args, Instrumentation inst) {
|
||||
main(args, inst);
|
||||
}
|
||||
|
||||
/**
|
||||
* 让下次再次启动时有机会重新加载
|
||||
*/
|
||||
public synchronized static void resetArthasClassLoader() {
|
||||
arthasClassLoader = null;
|
||||
}
|
||||
|
||||
private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File agentJarFile) throws Throwable {
|
||||
// 将Spy添加到BootstrapClassLoader
|
||||
inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));
|
||||
|
||||
// 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
|
||||
return loadOrDefineClassLoader(agentJarFile);
|
||||
}
|
||||
|
||||
private static ClassLoader loadOrDefineClassLoader(File agentJar) throws Throwable {
|
||||
if (arthasClassLoader == null) {
|
||||
arthasClassLoader = new ArthasClassloader(new URL[]{agentJar.toURI().toURL()});
|
||||
}
|
||||
return arthasClassLoader;
|
||||
}
|
||||
|
||||
private static void initSpy(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
|
||||
Class<?> adviceWeaverClass = classLoader.loadClass(ADVICEWEAVER);
|
||||
Method onBefore = adviceWeaverClass.getMethod(ON_BEFORE, int.class, ClassLoader.class, String.class,
|
||||
String.class, String.class, Object.class, Object[].class);
|
||||
Method onReturn = adviceWeaverClass.getMethod(ON_RETURN, Object.class);
|
||||
Method onThrows = adviceWeaverClass.getMethod(ON_THROWS, Throwable.class);
|
||||
Method beforeInvoke = adviceWeaverClass.getMethod(BEFORE_INVOKE, int.class, String.class, String.class, String.class);
|
||||
Method afterInvoke = adviceWeaverClass.getMethod(AFTER_INVOKE, int.class, String.class, String.class, String.class);
|
||||
Method throwInvoke = adviceWeaverClass.getMethod(THROW_INVOKE, int.class, String.class, String.class, String.class);
|
||||
Method reset = AgentBootstrap.class.getMethod(RESET);
|
||||
Spy.initForAgentLauncher(classLoader, onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke, reset);
|
||||
}
|
||||
|
||||
private static synchronized void main(final String args, final Instrumentation inst) {
|
||||
try {
|
||||
ps.println("Arthas server agent start...");
|
||||
// 传递的args参数分两个部分:agentJar路径和agentArgs, 分别是Agent的JAR包路径和期望传递到服务端的参数
|
||||
int index = args.indexOf(';');
|
||||
String agentJar = args.substring(0, index);
|
||||
final String agentArgs = args.substring(index, args.length());
|
||||
|
||||
File agentJarFile = new File(agentJar);
|
||||
if (!agentJarFile.exists()) {
|
||||
ps.println("Agent jar file does not exist: " + agentJarFile);
|
||||
return;
|
||||
}
|
||||
|
||||
File spyJarFile = new File(agentJarFile.getParentFile(), ARTHAS_SPY_JAR);
|
||||
if (!spyJarFile.exists()) {
|
||||
ps.println("Spy jar file does not exist: " + spyJarFile);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a dedicated thread to run the binding logic to prevent possible memory leak. #195
|
||||
*/
|
||||
final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, agentJarFile);
|
||||
initSpy(agentLoader);
|
||||
|
||||
Thread bindingThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
bind(inst, agentLoader, agentArgs);
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace(ps);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bindingThread.setName("arthas-binding-thread");
|
||||
bindingThread.start();
|
||||
bindingThread.join();
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(ps);
|
||||
try {
|
||||
if (ps != System.err) {
|
||||
ps.close();
|
||||
}
|
||||
} catch (Throwable tt) {
|
||||
// ignore
|
||||
}
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void bind(Instrumentation inst, ClassLoader agentLoader, String args) throws Throwable {
|
||||
Class<?> classOfConfigure = agentLoader.loadClass(ARTHAS_CONFIGURE);
|
||||
Object configure = classOfConfigure.getMethod(TO_CONFIGURE, String.class).invoke(null, args);
|
||||
int javaPid = (Integer) classOfConfigure.getMethod(GET_JAVA_PID).invoke(configure);
|
||||
Class<?> bootstrapClass = agentLoader.loadClass(ARTHAS_BOOTSTRAP);
|
||||
Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, int.class, Instrumentation.class).invoke(null, javaPid, inst);
|
||||
boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
|
||||
if (!isBind) {
|
||||
try {
|
||||
ps.println("Arthas start to bind...");
|
||||
bootstrapClass.getMethod(BIND, classOfConfigure).invoke(bootstrap, configure);
|
||||
ps.println("Arthas server bind success.");
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ps.println("Arthas server port binding failed! Please check $HOME/logs/arthas/arthas.log for more details.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
ps.println("Arthas server already bind.");
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.taobao.arthas.agent;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* @author beiwei30 on 09/12/2016.
|
||||
*/
|
||||
public class ArthasClassloader extends URLClassLoader {
|
||||
public ArthasClassloader(URL[] urls) {
|
||||
super(urls, ClassLoader.getSystemClassLoader().getParent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
final Class<?> loadedClass = findLoadedClass(name);
|
||||
if (loadedClass != null) {
|
||||
return loadedClass;
|
||||
}
|
||||
|
||||
// 优先从parent(SystemClassLoader)里加载系统类,避免抛出ClassNotFoundException
|
||||
if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
try {
|
||||
Class<?> aClass = findClass(name);
|
||||
if (resolve) {
|
||||
resolveClass(aClass);
|
||||
}
|
||||
return aClass;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
}
|
67
as-package.sh
Executable file
67
as-package.sh
Executable file
@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
CUR_VERSION="3.0"
|
||||
|
||||
# arthas's target dir
|
||||
ARTHAS_TARGET_DIR=$DIR/target/arthas
|
||||
|
||||
# arthas's version
|
||||
DATE=$(date '+%Y%m%d%H%M%S')
|
||||
|
||||
ARTHAS_VERSION="${CUR_VERSION}.${DATE}"
|
||||
|
||||
echo ${ARTHAS_VERSION} > $DIR/core/src/main/resources/com/taobao/arthas/core/res/version
|
||||
|
||||
# define newset arthas lib home
|
||||
NEWEST_ARTHAS_LIB_HOME=${HOME}/.arthas/lib/${ARTHAS_VERSION}/arthas
|
||||
|
||||
|
||||
# exit shell with err_code
|
||||
# $1 : err_code
|
||||
# $2 : err_msg
|
||||
exit_on_err()
|
||||
{
|
||||
[[ ! -z "${2}" ]] && echo "${2}" 1>&2
|
||||
exit ${1}
|
||||
}
|
||||
|
||||
# maven package the arthas
|
||||
mvn clean package -Dmaven.test.skip=true -f $DIR/pom.xml \
|
||||
|| exit_on_err 1 "package arthas failed."
|
||||
|
||||
rm -r $DIR/core/src/main/resources/com/taobao/arthas/core/res/version
|
||||
|
||||
# reset the target dir
|
||||
mkdir -p ${ARTHAS_TARGET_DIR}
|
||||
|
||||
# copy jar to TARGET_DIR
|
||||
cp $DIR/spy/target/arthas-spy.jar ${ARTHAS_TARGET_DIR}/arthas-spy.jar
|
||||
cp $DIR/core/target/arthas-core-jar-with-dependencies.jar ${ARTHAS_TARGET_DIR}/arthas-core.jar
|
||||
cp $DIR/agent/target/arthas-agent-jar-with-dependencies.jar ${ARTHAS_TARGET_DIR}/arthas-agent.jar
|
||||
cp $DIR/client/target/arthas-client-jar-with-dependencies.jar ${ARTHAS_TARGET_DIR}/arthas-client.jar
|
||||
|
||||
# copy shell to TARGET_DIR
|
||||
cat $DIR/bin/install-local.sh|sed "s/ARTHAS_VERSION=0.0/ARTHAS_VERSION=${ARTHAS_VERSION}/g" > ${ARTHAS_TARGET_DIR}/install-local.sh
|
||||
chmod +x ${ARTHAS_TARGET_DIR}/install-local.sh
|
||||
cp $DIR/bin/as.sh ${ARTHAS_TARGET_DIR}/as.sh
|
||||
cp $DIR/bin/as.bat ${ARTHAS_TARGET_DIR}/as.bat
|
||||
|
||||
# zip the arthas
|
||||
cd $DIR/target/
|
||||
zip -r arthas-${ARTHAS_VERSION}-bin.zip arthas/
|
||||
cd -
|
||||
|
||||
# install to local
|
||||
mkdir -p ${NEWEST_ARTHAS_LIB_HOME}
|
||||
cp $DIR/target/arthas/* ${NEWEST_ARTHAS_LIB_HOME}/
|
||||
|
||||
if [ $# -gt 0 ] && [ "$1" = "-release" ]; then
|
||||
echo "creating git tag ${ARTHAS_VERSION}..."
|
||||
git tag -a ${ARTHAS_VERSION} -m "release ${ARTHAS_VERSION}"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "A local git tag ${ARTHAS_VERSION} has been created, please use 'git tag -l' to verify it."
|
||||
echo "To commit the tag to remote repo, please run 'git push --tags' manually. "
|
||||
fi
|
||||
fi
|
3
batch.as
Normal file
3
batch.as
Normal file
@ -0,0 +1,3 @@
|
||||
dashboard -n 1
|
||||
sysprop
|
||||
watch arthas.Test test "@com.alibaba.arthas.Test@n.entrySet().iterator.{? #this.key.name()=='STOP' }" -n 2
|
90
bin/as.bat
Normal file
90
bin/as.bat
Normal file
@ -0,0 +1,90 @@
|
||||
@echo off
|
||||
|
||||
REM ----------------------------------------------------------------------------
|
||||
REM program : Arthas
|
||||
REM author : Core Engine @ Taobao.com
|
||||
REM date : 2015-11-11
|
||||
REM version : 3.0
|
||||
REM ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
:init
|
||||
REM Decide how to startup depending on the version of windows
|
||||
|
||||
REM -- Win98ME
|
||||
if NOT "%OS%"=="Windows_NT" goto Win9xArg
|
||||
|
||||
REM set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
goto WinNTGetScriptDir
|
||||
|
||||
:Win9xArg
|
||||
REM Slurp the command line arguments. This loop allows for an unlimited number
|
||||
REM of arguments (up to the command line limit, anyway).
|
||||
set BASEDIR=%CD%
|
||||
goto repoSetup
|
||||
|
||||
:WinNTGetScriptDir
|
||||
set BASEDIR=%~dp0
|
||||
|
||||
:repoSetup
|
||||
set AGENT_JAR=%BASEDIR%\arthas-agent.jar
|
||||
set CORE_JAR=%BASEDIR%\arthas-core.jar
|
||||
|
||||
set PID=%1
|
||||
|
||||
REM Setup JAVA_HOME
|
||||
if "%JAVA_HOME%" == "" goto noJavaHome
|
||||
if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
|
||||
if not exist "%JAVA_HOME%\lib\tools.jar" goto noJavaHome
|
||||
set JAVACMD="%JAVA_HOME%\bin\java"
|
||||
set BOOT_CLASSPATH="-Xbootclasspath/a:%JAVA_HOME%\lib\tools.jar"
|
||||
goto okJava
|
||||
|
||||
:noJavaHome
|
||||
echo The JAVA_HOME environment variable is not defined correctly.
|
||||
echo It is needed to run this program.
|
||||
echo NB: JAVA_HOME should point to a JDK not a JRE.
|
||||
goto exit
|
||||
|
||||
:okJava
|
||||
set JAVACMD="%JAVA_HOME%"\bin\java
|
||||
|
||||
REM Reaching here means variables are defined and arguments have been captured
|
||||
:endInit
|
||||
|
||||
%JAVACMD% -Dfile.encoding=UTF-8 %BOOT_CLASSPATH% -jar "%CORE_JAR%" -pid "%PID%" -target-ip 127.0.0.1 -telnet-port 3658 -http-port 8563 -core "%CORE_JAR%" -agent "%AGENT_JAR%"
|
||||
if %ERRORLEVEL% NEQ 0 goto error
|
||||
goto attachSuccess
|
||||
|
||||
:error
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
set ERROR_CODE=%ERRORLEVEL%
|
||||
goto endNT
|
||||
|
||||
:attachSuccess
|
||||
REM %JAVACMD% -Dfile.encoding=UTF-8 -Djava.awt.headless=true -cp "%CORE_JAR%" com.taobao.arthas.core.ArthasConsole 127.0.0.1 3658
|
||||
telnet 127.0.0.1 3658
|
||||
|
||||
REM set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" goto endNT
|
||||
|
||||
REM For old DOS remove the set variables from ENV - we assume they were not set
|
||||
REM before we started - at least we don't leave any baggage around
|
||||
goto postExec
|
||||
|
||||
:endNT
|
||||
REM If error code is set to 1 then the endlocal was done already in :error.
|
||||
if %ERROR_CODE% EQU 0 endlocal
|
||||
|
||||
|
||||
:postExec
|
||||
|
||||
if "%FORCE_EXIT_ON_ERROR%" == "on" (
|
||||
if %ERROR_CODE% NEQ 0 exit %ERROR_CODE%
|
||||
)
|
||||
|
||||
exit /B %ERROR_CODE%
|
482
bin/as.sh
Executable file
482
bin/as.sh
Executable file
@ -0,0 +1,482 @@
|
||||
#!/bin/sh
|
||||
|
||||
# program : Arthas
|
||||
# author : Core Engine @ Taobao.com
|
||||
# date : 2017-3-27
|
||||
# version : 3.0.3
|
||||
|
||||
# define arthas's home
|
||||
ARTHAS_HOME=${HOME}/.arthas
|
||||
|
||||
# define arthas's lib
|
||||
ARTHAS_LIB_DIR=${ARTHAS_HOME}/lib
|
||||
|
||||
# define arthas's temp dir
|
||||
TMP_DIR=/tmp
|
||||
|
||||
# last update arthas version
|
||||
ARTHAS_VERSION=
|
||||
|
||||
# current arthas script version
|
||||
ARTHAS_SCRIPT_VERSION=3.0.2
|
||||
|
||||
# arthas remote url
|
||||
ARTHAS_REMOTE_VERSION_URL="http://arthas.io/api/arthas/newestVersion.do"
|
||||
ARTHAS_REMOTE_DOWNLOAD_URL="http://arthas.io/mdtool"
|
||||
|
||||
# update timeout(sec)
|
||||
SO_TIMEOUT=5
|
||||
|
||||
# define default target ip
|
||||
DEFAULT_TARGET_IP="127.0.0.1"
|
||||
|
||||
# define default target port
|
||||
DEFAULT_TELNET_PORT="3658"
|
||||
DEFAULT_HTTP_PORT="8563"
|
||||
|
||||
# define JVM's OPS
|
||||
JVM_OPTS=""
|
||||
|
||||
# define default batch mode
|
||||
BATCH_MODE=false
|
||||
|
||||
# if true, the script will only attach the agent to target jvm.
|
||||
ATTACH_ONLY=false
|
||||
|
||||
# define batch script location
|
||||
BATCH_SCRIPT=
|
||||
|
||||
ARTHAS_OPTS="-Djava.awt.headless=true"
|
||||
|
||||
# exit shell with err_code
|
||||
# $1 : err_code
|
||||
# $2 : err_msg
|
||||
exit_on_err()
|
||||
{
|
||||
[[ ! -z "${2}" ]] && echo "${2}" 1>&2
|
||||
exit ${1}
|
||||
}
|
||||
|
||||
|
||||
# get with default value
|
||||
# $1 : target value
|
||||
# $2 : default value
|
||||
default()
|
||||
{
|
||||
[[ ! -z "${1}" ]] && echo "${1}" || echo "${2}"
|
||||
}
|
||||
|
||||
|
||||
# check arthas permission
|
||||
check_permission()
|
||||
{
|
||||
[ ! -w ${HOME} ] \
|
||||
&& exit_on_err 1 "permission denied, ${HOME} is not writeable."
|
||||
}
|
||||
|
||||
|
||||
# reset arthas work environment
|
||||
# reset some options for env
|
||||
reset_for_env()
|
||||
{
|
||||
|
||||
# init ARTHAS' lib
|
||||
mkdir -p ${ARTHAS_LIB_DIR} \
|
||||
|| exit_on_err 1 "create ${ARTHAS_LIB_DIR} fail."
|
||||
|
||||
# if env define the JAVA_HOME, use it first
|
||||
# if is alibaba opts, use alibaba ops's default JAVA_HOME
|
||||
[ -z ${JAVA_HOME} ] && JAVA_HOME=/opt/taobao/java
|
||||
|
||||
# iterater throught candidates to find a proper JAVA_HOME at least contains tools.jar which is required by arthas.
|
||||
if [ ! -d ${JAVA_HOME} ]; then
|
||||
JAVA_HOME_CANDIDATES=($(ps aux | grep java | grep -v 'grep java' | awk '{print $11}' | sed -n 's/\/bin\/java$//p'))
|
||||
for JAVA_HOME_TEMP in ${JAVA_HOME_CANDIDATES[@]}; do
|
||||
if [ -f ${JAVA_HOME_TEMP}/lib/tools.jar ]; then
|
||||
JAVA_HOME=${JAVA_HOME_TEMP}
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# check the jvm version, we need 1.6+
|
||||
local JAVA_VERSION=$(${JAVA_HOME}/bin/java -version 2>&1|awk -F '"' '$2>"1.5"{print $2}')
|
||||
[[ ! -x ${JAVA_HOME} || -z ${JAVA_VERSION} ]] && exit_on_err 1 "illegal ENV, please set \$JAVA_HOME to JDK6+"
|
||||
|
||||
# when java version greater than 9, there is no tools.jar
|
||||
if [[ ! "$JAVA_VERSION" =~ ^9 ]];then
|
||||
# check tools.jar exists
|
||||
if [ ! -f ${JAVA_HOME}/lib/tools.jar ]; then
|
||||
exit_on_err 1 "${JAVA_HOME}/lib/tools.jar does not exist, arthas could not be launched!"
|
||||
else
|
||||
BOOT_CLASSPATH=-Xbootclasspath/a:${JAVA_HOME}/lib/tools.jar
|
||||
fi
|
||||
fi
|
||||
|
||||
# reset CHARSET for alibaba opts, we use GBK
|
||||
[[ -x /opt/taobao/java ]] && JVM_OPTS="-Dinput.encoding=GBK ${JVM_OPTS} "
|
||||
|
||||
}
|
||||
|
||||
# get latest version from local
|
||||
get_local_version()
|
||||
{
|
||||
ls ${ARTHAS_LIB_DIR} | sort | tail -1
|
||||
}
|
||||
|
||||
# get latest version from remote
|
||||
get_remote_version()
|
||||
{
|
||||
curl -sLk --connect-timeout ${SO_TIMEOUT} "${ARTHAS_REMOTE_VERSION_URL}"
|
||||
}
|
||||
|
||||
# make version format to comparable format like 000.000.(0){15}
|
||||
# $1 : version
|
||||
to_comparable_version()
|
||||
{
|
||||
echo ${1}|awk -F "." '{printf("%d.%d.%d\n",$1,$2,$3)}'
|
||||
}
|
||||
|
||||
# update arthas if necessary
|
||||
update_if_necessary()
|
||||
{
|
||||
local update_version=$1
|
||||
|
||||
if [ ! -d ${ARTHAS_LIB_DIR}/${update_version} ]; then
|
||||
echo "updating version ${update_version} ..."
|
||||
|
||||
local temp_target_lib_dir="$TMP_DIR/temp_${update_version}_$$"
|
||||
local temp_target_lib_zip="${temp_target_lib_dir}/arthas-${update_version}-bin.zip"
|
||||
local target_lib_dir="${ARTHAS_LIB_DIR}/${update_version}"
|
||||
|
||||
# clean
|
||||
rm -rf ${temp_target_lib_dir}
|
||||
rm -rf ${target_lib_dir}
|
||||
|
||||
mkdir -p "${temp_target_lib_dir}" \
|
||||
|| exit_on_err 1 "create ${temp_target_lib_dir} fail."
|
||||
|
||||
# download current arthas version
|
||||
curl \
|
||||
-#Lk \
|
||||
--connect-timeout ${SO_TIMEOUT} \
|
||||
-o ${temp_target_lib_zip} \
|
||||
"${ARTHAS_REMOTE_DOWNLOAD_URL}/${update_version}/arthas-${update_version}-bin.zip" \
|
||||
|| return 1
|
||||
|
||||
# unzip arthas lib
|
||||
unzip ${temp_target_lib_zip} -d ${temp_target_lib_dir} || (rm -rf ${temp_target_lib_dir} && return 1)
|
||||
|
||||
# rename
|
||||
mv ${temp_target_lib_dir} ${target_lib_dir} || return 1
|
||||
|
||||
# print success
|
||||
echo "update completed."
|
||||
fi
|
||||
}
|
||||
|
||||
# the usage
|
||||
usage()
|
||||
{
|
||||
echo "
|
||||
Usage:
|
||||
$0 [-b [-f SCRIPT_FILE]] [debug] [--use-version VERSION] [--attach-only] <PID>[@IP:TELNET_PORT:HTTP_PORT]
|
||||
[debug] : start the agent in debug mode
|
||||
<PID> : the target Java Process ID
|
||||
[IP] : the target's IP
|
||||
[TELNET_PORT] : the target's PORT for telnet
|
||||
[HTTP_PORT] : the target's PORT for http
|
||||
[-b] : batch mode, which will disable interactive process selection.
|
||||
[-f] : specify the path to batch script file.
|
||||
[--attach-only] : only attach the arthas agent to target jvm.
|
||||
[--use-version] : use the specified arthas version to attach.
|
||||
|
||||
Example:
|
||||
./as.sh <PID>
|
||||
./as.sh <PID>@[IP]
|
||||
./as.sh <PID>@[IP:PORT]
|
||||
./as.sh debug <PID>
|
||||
./as.sh -b <PID>
|
||||
./as.sh -b -f /path/to/script
|
||||
./as.sh --attach-only <PID>
|
||||
./as.sh --use-version 2.0.20161221142407 <PID>
|
||||
|
||||
Here is the list of possible java process(es) to attatch:
|
||||
|
||||
$(${JAVA_HOME}/bin/jps -l | grep -v sun.tools.jps.Jps)
|
||||
"
|
||||
}
|
||||
|
||||
# parse the argument
|
||||
parse_arguments()
|
||||
{
|
||||
if [ "$1" = "-h" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" = "-b" ]; then
|
||||
BATCH_MODE=true
|
||||
shift
|
||||
if [ "$1" = "-f" ]; then
|
||||
if [ "x$2" != "x" ] && [ -f $2 ]; then
|
||||
BATCH_SCRIPT=$2
|
||||
echo "Using script file for batch mode: $BATCH_SCRIPT"
|
||||
shift # -f
|
||||
shift # /path/to/script
|
||||
else
|
||||
echo "Invalid script file $2."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$1" = "debug" ] ; then
|
||||
if [ -z "$JPDA_TRANSPORT" ]; then
|
||||
JPDA_TRANSPORT="dt_socket"
|
||||
fi
|
||||
if [ -z "$JPDA_ADDRESS" ]; then
|
||||
JPDA_ADDRESS="8888"
|
||||
fi
|
||||
if [ -z "$JPDA_SUSPEND" ]; then
|
||||
JPDA_SUSPEND="n"
|
||||
fi
|
||||
if [ -z "$JPDA_OPTS" ]; then
|
||||
JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND"
|
||||
fi
|
||||
ARTHAS_OPTS="$JPDA_OPTS $ARTHAS_OPTS"
|
||||
shift
|
||||
fi
|
||||
|
||||
# use custom version
|
||||
if [ "$1" = "--use-version" ]; then
|
||||
shift
|
||||
ARTHAS_VERSION=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
# attach only mode
|
||||
if [ "$1" = "--attach-only" ]; then
|
||||
ATTACH_ONLY=true
|
||||
shift
|
||||
fi
|
||||
|
||||
TARGET_PID=$(echo ${1}|awk -F "@" '{print $1}');
|
||||
TARGET_IP=$(echo ${1}|awk -F "@|:" '{print $2}');
|
||||
TELNET_PORT=$(echo ${1}|awk -F ":" '{print $2}');
|
||||
HTTP_PORT=$(echo ${1}|awk -F ":" '{print $3}');
|
||||
|
||||
# check pid
|
||||
if [ -z ${TARGET_PID} ] && [ ${BATCH_MODE} = false ]; then
|
||||
# interactive mode
|
||||
IFS=$'\n'
|
||||
CANDIDATES=($(${JAVA_HOME}/bin/jps -l | grep -v sun.tools.jps.Jps | awk '{print $0}'))
|
||||
|
||||
if [ ${#CANDIDATES[@]} -eq 0 ]; then
|
||||
echo "Error: no available java process to attach."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Found existing java process, please choose one and hit RETURN."
|
||||
|
||||
index=0
|
||||
suggest=1
|
||||
# auto select tomcat/pandora-boot process
|
||||
for process in "${CANDIDATES[@]}"; do
|
||||
index=$(($index+1))
|
||||
if [ $(echo ${process} | grep -c org.apache.catalina.startup.Bootstrap) -eq 1 ] \
|
||||
|| [ $(echo ${process} | grep -c com.taobao.pandora.boot.loader.SarLauncher) -eq 1 ]
|
||||
then
|
||||
suggest=${index}
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
index=0
|
||||
for process in "${CANDIDATES[@]}"; do
|
||||
index=$(($index+1))
|
||||
if [ ${index} -eq ${suggest} ]; then
|
||||
echo "* [$index]: ${process}"
|
||||
else
|
||||
echo " [$index]: ${process}"
|
||||
fi
|
||||
done
|
||||
|
||||
read choice
|
||||
|
||||
if [ -z ${choice} ]; then
|
||||
choice=${suggest}
|
||||
fi
|
||||
|
||||
TARGET_PID=`echo ${CANDIDATES[$(($choice-1))]} | cut -d ' ' -f 1`
|
||||
|
||||
elif [ -z ${TARGET_PID} ]; then
|
||||
# batch mode is enabled, no interactive process selection.
|
||||
echo "Illegal arguments, the <PID> is required." 1>&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# reset ${ip} to default if empty
|
||||
[ -z ${TARGET_IP} ] && TARGET_IP=${DEFAULT_TARGET_IP}
|
||||
|
||||
# reset ${port} to default if empty
|
||||
[ -z ${TELNET_PORT} ] && TELNET_PORT=${DEFAULT_TELNET_PORT}
|
||||
[ -z ${HTTP_PORT} ] && HTTP_PORT=${DEFAULT_HTTP_PORT}
|
||||
|
||||
return 0
|
||||
|
||||
}
|
||||
|
||||
|
||||
# attach arthas to target jvm
|
||||
# $1 : arthas_local_version
|
||||
attach_jvm()
|
||||
{
|
||||
local arthas_version=$1
|
||||
local arthas_lib_dir=${ARTHAS_LIB_DIR}/${arthas_version}/arthas
|
||||
|
||||
echo "Attaching to ${TARGET_PID} using version ${1}..."
|
||||
|
||||
if [ ${TARGET_IP} = ${DEFAULT_TARGET_IP} ]; then
|
||||
if [[ "${arthas_version}" > "3.0" ]]; then
|
||||
${JAVA_HOME}/bin/java \
|
||||
${ARTHAS_OPTS} ${BOOT_CLASSPATH} ${JVM_OPTS} \
|
||||
-jar ${arthas_lib_dir}/arthas-core.jar \
|
||||
-pid ${TARGET_PID} \
|
||||
-target-ip ${TARGET_IP} \
|
||||
-telnet-port ${TELNET_PORT} \
|
||||
-http-port ${HTTP_PORT} \
|
||||
-core "${arthas_lib_dir}/arthas-core.jar" \
|
||||
-agent "${arthas_lib_dir}/arthas-agent.jar"
|
||||
else
|
||||
# for compatibility
|
||||
${JAVA_HOME}/bin/java \
|
||||
${ARTHAS_OPTS} ${BOOT_CLASSPATH} ${JVM_OPTS} \
|
||||
-jar ${arthas_lib_dir}/arthas-core.jar \
|
||||
-pid ${TARGET_PID} \
|
||||
-target ${TARGET_IP}":"${TELNET_PORT} \
|
||||
-core "${arthas_lib_dir}/arthas-core.jar" \
|
||||
-agent "${arthas_lib_dir}/arthas-agent.jar"
|
||||
|
||||
# verify_pid
|
||||
echo "help" > /tmp/command
|
||||
PID=`${JAVA_HOME}/bin/java -cp ${arthas_lib_dir}/arthas-core.jar ${ARTHAS_OPTS}\
|
||||
com.taobao.arthas.core.ArthasConsole ${TARGET_IP} ${TELNET_PORT} -b -f /tmp/command \
|
||||
| grep PID | awk '{print $2}'`
|
||||
rm /tmp/command
|
||||
if [ ! -z ${PID} ] && [ "${PID}" != "${TARGET_PID}" ]; then
|
||||
echo "WARNING: Arthas server is running on ${PID} instead of ${TARGET_PID}, exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
sanity_check() {
|
||||
# 0 check whether the pid exist
|
||||
local pid=$(ps -p ${TARGET_PID} -o pid=)
|
||||
if [ -z ${pid} ]; then
|
||||
exit_on_err 1 "The target pid (${TARGET_PID}) does not exist!"
|
||||
fi
|
||||
|
||||
# 1 check the current user matches the process owner
|
||||
local current_user=$(id -u -n)
|
||||
# the last '=' after 'user' eliminates the column header
|
||||
local target_user=$(ps -p "${TARGET_PID}" -o user=)
|
||||
if [ "$current_user" != "$target_user" ]; then
|
||||
echo "The current user ($current_user) does not match with the owner of process ${TARGET_PID} ($target_user)."
|
||||
echo "To solve this, choose one of the following command:"
|
||||
echo " 1) sudo su $target_user && ./as.sh"
|
||||
echo " 2) sudo -u $target_user -EH ./as.sh"
|
||||
exit_on_err 1
|
||||
fi
|
||||
}
|
||||
|
||||
# active console
|
||||
# $1 : arthas_local_version
|
||||
active_console()
|
||||
{
|
||||
local arthas_version=$1
|
||||
local arthas_lib_dir=${ARTHAS_LIB_DIR}/${arthas_version}/arthas
|
||||
|
||||
if [[ "${arthas_version}" > "3.0" ]]; then
|
||||
if [ "${BATCH_MODE}" = "true" ]; then
|
||||
${JAVA_HOME}/bin/java ${ARTHAS_OPTS} ${JVM_OPTS} \
|
||||
-jar ${arthas_lib_dir}/arthas-client.jar \
|
||||
${TARGET_IP} \
|
||||
-p ${TELNET_PORT} \
|
||||
-f ${BATCH_SCRIPT}
|
||||
elif type telnet 2>&1 >> /dev/null; then
|
||||
# use telnet
|
||||
telnet ${TARGET_IP} ${TELNET_PORT}
|
||||
else
|
||||
echo "'telnet' is required." 1>&2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# for compatibility
|
||||
# use default console
|
||||
ARGS="${TARGET_IP} ${TELNET_PORT}"
|
||||
if [ ${BATCH_MODE} = true ]; then
|
||||
ARGS="$ARGS -b"
|
||||
fi
|
||||
if [ ! -z ${BATCH_SCRIPT} ]; then
|
||||
ARGS="$ARGS -f $BATCH_SCRIPT"
|
||||
fi
|
||||
eval ${JAVA_HOME}/bin/java ${ARTHAS_OPTS} \
|
||||
-cp ${arthas_lib_dir}/arthas-core.jar \
|
||||
com.taobao.arthas.core.ArthasConsole \
|
||||
${ARGS}
|
||||
fi
|
||||
}
|
||||
|
||||
# the main
|
||||
main()
|
||||
{
|
||||
echo "Arthas script version: $ARTHAS_SCRIPT_VERSION"
|
||||
echo "Try out Arthas online: /arthas/web-console"
|
||||
|
||||
check_permission
|
||||
reset_for_env
|
||||
|
||||
parse_arguments "${@}" \
|
||||
|| exit_on_err 1 "$(usage)"
|
||||
|
||||
local remote_version=$(get_remote_version)
|
||||
|
||||
if [ -z ${ARTHAS_VERSION} ]; then
|
||||
update_if_necessary ${remote_version} || echo "update fail, ignore this update." 1>&2
|
||||
else
|
||||
update_if_necessary ${ARTHAS_VERSION} || echo "update fail, ignore this update." 1>&2
|
||||
fi
|
||||
|
||||
local arthas_local_version=$(get_local_version)
|
||||
|
||||
if [ ! -z ${ARTHAS_VERSION} ]; then
|
||||
arthas_local_version=${ARTHAS_VERSION}
|
||||
fi
|
||||
|
||||
if [ ! -d ${ARTHAS_LIB_DIR}/${arthas_local_version} ]; then
|
||||
exit_on_err 1 "arthas not found, please check your network."
|
||||
fi
|
||||
|
||||
sanity_check
|
||||
|
||||
echo "Calculating attach execution time..."
|
||||
time (attach_jvm ${arthas_local_version} || exit 1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit_on_err 1 "attach to target jvm (${TARGET_PID}) failed, check ${HOME}/logs/arthas/arthas.log or stderr of target jvm for any exceptions."
|
||||
fi
|
||||
|
||||
echo "Attach success."
|
||||
|
||||
if [ ${ATTACH_ONLY} = false ]; then
|
||||
echo "Connecting to arthas server... current timestamp is `date +%s`"
|
||||
active_console ${arthas_local_version}
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
main "${@}"
|
35
bin/install-local.sh
Normal file
35
bin/install-local.sh
Normal file
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# define newset arthas's version
|
||||
# need ../arthas-packages.sh replace the version number
|
||||
ARTHAS_VERSION=0.0
|
||||
|
||||
# define newset arthas lib home
|
||||
ARTHAS_LIB_HOME=${HOME}/.arthas/lib/${ARTHAS_VERSION}/arthas
|
||||
|
||||
# exit shell with err_code
|
||||
# $1 : err_code
|
||||
# $2 : err_msg
|
||||
exit_on_err()
|
||||
{
|
||||
[[ ! -z "${2}" ]] && echo "${2}" 1>&2
|
||||
exit ${1}
|
||||
}
|
||||
|
||||
# install to local if necessary
|
||||
if [[ ! -x ${ARTHAS_LIB_HOME} ]]; then
|
||||
|
||||
# install to local
|
||||
mkdir -p ${ARTHAS_LIB_HOME} \
|
||||
|| exit_on_err 1 "create target directory ${ARTHAS_LIB_HOME} failed."
|
||||
|
||||
# copy jar files
|
||||
cp *.jar ${ARTHAS_LIB_HOME}/
|
||||
|
||||
# make it -x
|
||||
chmod +x ./as.sh
|
||||
|
||||
fi
|
||||
|
||||
echo "install to local successed."
|
||||
|
48
bin/install.sh
Normal file
48
bin/install.sh
Normal file
@ -0,0 +1,48 @@
|
||||
#! /bin/bash
|
||||
|
||||
# temp file of as.sh
|
||||
TEMP_ARTHAS_FILE="./as.sh.$$"
|
||||
|
||||
# target file of as.sh
|
||||
TARGET_ARTHAS_FILE="./as.sh"
|
||||
|
||||
# update timeout(sec)
|
||||
SO_TIMEOUT=60
|
||||
|
||||
# default downloading url
|
||||
ARTHAS_FILE_URL="http://arthas.io/arthas/as.sh"
|
||||
|
||||
# exit shell with err_code
|
||||
# $1 : err_code
|
||||
# $2 : err_msg
|
||||
exit_on_err()
|
||||
{
|
||||
[[ ! -z "${2}" ]] && echo "${2}" 1>&2
|
||||
exit ${1}
|
||||
}
|
||||
|
||||
# check permission to download && install
|
||||
[ ! -w ./ ] && exit_on_err 1 "permission denied, target directory ./ was not writable."
|
||||
|
||||
if [ $# -gt 1 ] && [ $1 = "--url" ]; then
|
||||
shift
|
||||
ARTHAS_FILE_URL=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
# download from aliyunos
|
||||
echo "downloading... ${TEMP_ARTHAS_FILE}"
|
||||
curl \
|
||||
-sLk \
|
||||
--connect-timeout ${SO_TIMEOUT} \
|
||||
$ARTHAS_FILE_URL \
|
||||
-o ${TEMP_ARTHAS_FILE} \
|
||||
|| exit_on_err 1 "download failed!"
|
||||
|
||||
# wirte or overwrite local file
|
||||
rm -rf as.sh
|
||||
mv ${TEMP_ARTHAS_FILE} ${TARGET_ARTHAS_FILE}
|
||||
chmod +x ${TARGET_ARTHAS_FILE}
|
||||
|
||||
# done
|
||||
echo "Arthas install successed."
|
27
bin/jps.sh
Normal file
27
bin/jps.sh
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
# jps.sh version 1.0.2
|
||||
|
||||
# there might be multiple java processes, e.g. log-agent
|
||||
JPS_CMDS=($(ps aux | grep java | grep -v 'grep java' | awk '{print $11}' | sed -n 's/java$/jps/p'))
|
||||
|
||||
# find the first executable jps command
|
||||
JPS_CMD=""
|
||||
for jps in ${JPS_CMDS[@]}; do
|
||||
if [ -x $jps ]; then
|
||||
JPS_CMD=$jps
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$JPS_CMD" == "" ]; then
|
||||
echo "No Java Process Found on this Machine."
|
||||
exit 1
|
||||
else
|
||||
result=`$JPS_CMD -lmv | grep -v jps`
|
||||
if [ "$result" == "" ]; then
|
||||
ps aux | grep -E '^admin.*java.*' | grep -v grep | awk 'BEGIN{ORS=""}{print $2" ";for(j=NF;j>=12;j--){if(match($j, /^\-[a-zA-Z0-9]/)) {break;} } for(i=j+1;i<=NF;i++) {print $i" "} for(i=12;i<=j;i++) {print $i" "} print "\n" }'
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
fi
|
63
client/pom.xml
Normal file
63
client/pom.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>arthas-all</artifactId>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>arthas-client</artifactId>
|
||||
<name>arthas-client</name>
|
||||
|
||||
<build>
|
||||
<finalName>arthas-client</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>attached</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.taobao.arthas.client.TelnetConsole</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Created-By>core engine team, middleware group, alibaba inc.</Created-By>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.middleware</groupId>
|
||||
<artifactId>cli</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
213
client/src/main/java/com/taobao/arthas/client/TelnetConsole.java
Normal file
213
client/src/main/java/com/taobao/arthas/client/TelnetConsole.java
Normal file
@ -0,0 +1,213 @@
|
||||
package com.taobao.arthas.client;
|
||||
|
||||
import com.taobao.middleware.cli.Argument;
|
||||
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 org.apache.commons.net.telnet.TelnetClient;
|
||||
import org.apache.commons.net.telnet.WindowSizeOptionHandler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ralf0131 2016-12-29 11:55.
|
||||
*/
|
||||
public class TelnetConsole{
|
||||
|
||||
private static final String PROMPT = "$";
|
||||
private static final String DEFAULT_TELNET_PORT = "3658";
|
||||
private static final int DEFAULT_CONNECTION_TIMEOUT = 5000; // 5000 ms
|
||||
private static final String DEFAULT_WINDOW_WIDTH = "120";
|
||||
private static final String DEFAULT_WINDOW_HEIGHT = "40";
|
||||
private static final int DEFAULT_BUFFER_SIZE = 1024;
|
||||
|
||||
private TelnetClient telnet;
|
||||
private String address;
|
||||
private int port;
|
||||
private InputStream in;
|
||||
private PrintStream out;
|
||||
|
||||
public TelnetConsole(String address, int port, int width, int height) {
|
||||
this.telnet = new TelnetClient();
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
try {
|
||||
telnet.addOptionHandler(new WindowSizeOptionHandler(width, height, true, false, true, false));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
telnet.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
try {
|
||||
// Connect to the specified server
|
||||
telnet.connect(address, port);
|
||||
// Get input and output stream references
|
||||
in = telnet.getInputStream();
|
||||
out = new PrintStream(telnet.getOutputStream());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String readUntil(String prompt) {
|
||||
try {
|
||||
StringBuilder sBuffer = new StringBuilder();
|
||||
byte[] b = new byte[DEFAULT_BUFFER_SIZE];
|
||||
while(true) {
|
||||
int size = in.read(b);
|
||||
if(-1 != size) {
|
||||
sBuffer.append(new String(b,0,size));
|
||||
String data = sBuffer.toString();
|
||||
if(data.trim().endsWith(prompt)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sBuffer.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String readUntilPrompt() {
|
||||
return readUntil(PROMPT);
|
||||
}
|
||||
|
||||
public void write(String value) {
|
||||
try {
|
||||
out.println(value);
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommand(String command) {
|
||||
try {
|
||||
write(command);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
try {
|
||||
telnet.disconnect();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批处理模式
|
||||
*/
|
||||
public void batchModeRun(File batchFile) {
|
||||
if (batchFile == null || !batchFile.exists()) {
|
||||
return;
|
||||
}
|
||||
batchModeRun(readLines(batchFile));
|
||||
}
|
||||
|
||||
private void batchModeRun(List<String> commands) {
|
||||
for (String command: commands) {
|
||||
// send command to server
|
||||
sendCommand(command + " | plaintext");
|
||||
// read result from server and output
|
||||
String response = readUntilPrompt();
|
||||
System.out.print(response);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> readLines(File batchFile) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new FileReader(batchFile));
|
||||
String line = br.readLine();
|
||||
while (line != null) {
|
||||
list.add(line);
|
||||
line = br.readLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: TelnetConsole <target-ip> [-p PORT] [-c COMMAND] [-f BATCH_FILE] [-w WIDTH] [-h HEIGHT]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
CommandLine commandLine = parseArguments(args);
|
||||
|
||||
TelnetConsole console = new TelnetConsole(
|
||||
(String)commandLine.getArgumentValue("target-ip"),
|
||||
(Integer)commandLine.getOptionValue("p"),
|
||||
(Integer)commandLine.getOptionValue("w"),
|
||||
(Integer)commandLine.getOptionValue("h"));
|
||||
|
||||
console.connect();
|
||||
String logo = console.readUntilPrompt();
|
||||
System.out.print(logo);
|
||||
|
||||
String cmd = commandLine.getOptionValue("c");
|
||||
if (cmd != null) {
|
||||
List<String> cmds = new ArrayList<String>();
|
||||
for (String c: cmd.split(";")) {
|
||||
cmds.add(c.trim());
|
||||
}
|
||||
console.batchModeRun(cmds);
|
||||
}
|
||||
|
||||
String filePath = commandLine.getOptionValue("f");
|
||||
if (filePath != null) {
|
||||
File batchFile = new File(filePath);
|
||||
console.batchModeRun(batchFile);
|
||||
}
|
||||
|
||||
console.disconnect();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandLine parseArguments(String[] args) {
|
||||
Argument addr = new Argument().setArgName("target-ip").setIndex(0).setRequired(true);
|
||||
Option port = new TypedOption<Integer>().setType(Integer.class).setShortName("p")
|
||||
.setDefaultValue(DEFAULT_TELNET_PORT);
|
||||
Option command = new TypedOption<String>().setType(String.class).setShortName("c");
|
||||
Option batchFileOption = new TypedOption<String>().setType(String.class).setShortName("f");
|
||||
Option width = new TypedOption<Integer>().setType(Integer.class).setShortName("w")
|
||||
.setDefaultValue(DEFAULT_WINDOW_WIDTH);
|
||||
Option height = new TypedOption<Integer>().setType(Integer.class).setShortName("h")
|
||||
.setDefaultValue(DEFAULT_WINDOW_HEIGHT);
|
||||
CLI cli = CLIs.create("TelnetConsole").addArgument(addr).addOption(port)
|
||||
.addOption(command).addOption(batchFileOption).addOption(width).addOption(height);
|
||||
return cli.parse(Arrays.asList(args));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/***
|
||||
* The DatagramSocketClient provides the basic operations that are required
|
||||
* of client objects accessing datagram sockets. It is meant to be
|
||||
* subclassed to avoid having to rewrite the same code over and over again
|
||||
* to open a socket, close a socket, set timeouts, etc. Of special note
|
||||
* is the {@link #setDatagramSocketFactory setDatagramSocketFactory }
|
||||
* method, which allows you to control the type of DatagramSocket the
|
||||
* DatagramSocketClient creates for network communications. This is
|
||||
* especially useful for adding things like proxy support as well as better
|
||||
* support for applets. For
|
||||
* example, you could create a
|
||||
* {@link org.apache.commons.net.DatagramSocketFactory}
|
||||
* that
|
||||
* requests browser security capabilities before creating a socket.
|
||||
* All classes derived from DatagramSocketClient should use the
|
||||
* {@link #_socketFactory_ _socketFactory_ } member variable to
|
||||
* create DatagramSocket instances rather than instantiating
|
||||
* them by directly invoking a constructor. By honoring this contract
|
||||
* you guarantee that a user will always be able to provide his own
|
||||
* Socket implementations by substituting his own SocketFactory.
|
||||
*
|
||||
*
|
||||
* @see DatagramSocketFactory
|
||||
***/
|
||||
|
||||
public abstract class DatagramSocketClient
|
||||
{
|
||||
/***
|
||||
* The default DatagramSocketFactory shared by all DatagramSocketClient
|
||||
* instances.
|
||||
***/
|
||||
private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY =
|
||||
new DefaultDatagramSocketFactory();
|
||||
|
||||
/**
|
||||
* Charset to use for byte IO.
|
||||
*/
|
||||
private Charset charset = Charset.defaultCharset();
|
||||
|
||||
/*** The timeout to use after opening a socket. ***/
|
||||
protected int _timeout_;
|
||||
|
||||
/*** The datagram socket used for the connection. ***/
|
||||
protected DatagramSocket _socket_;
|
||||
|
||||
/***
|
||||
* A status variable indicating if the client's socket is currently open.
|
||||
***/
|
||||
protected boolean _isOpen_;
|
||||
|
||||
/*** The datagram socket's DatagramSocketFactory. ***/
|
||||
protected DatagramSocketFactory _socketFactory_;
|
||||
|
||||
/***
|
||||
* Default constructor for DatagramSocketClient. Initializes
|
||||
* _socket_ to null, _timeout_ to 0, and _isOpen_ to false.
|
||||
***/
|
||||
public DatagramSocketClient()
|
||||
{
|
||||
_socket_ = null;
|
||||
_timeout_ = 0;
|
||||
_isOpen_ = false;
|
||||
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Opens a DatagramSocket on the local host at the first available port.
|
||||
* Also sets the timeout on the socket to the default timeout set
|
||||
* by {@link #setDefaultTimeout setDefaultTimeout() }.
|
||||
* <p>
|
||||
* _isOpen_ is set to true after calling this method and _socket_
|
||||
* is set to the newly opened socket.
|
||||
*
|
||||
* @exception SocketException If the socket could not be opened or the
|
||||
* timeout could not be set.
|
||||
***/
|
||||
public void open() throws SocketException
|
||||
{
|
||||
_socket_ = _socketFactory_.createDatagramSocket();
|
||||
_socket_.setSoTimeout(_timeout_);
|
||||
_isOpen_ = true;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Opens a DatagramSocket on the local host at a specified port.
|
||||
* Also sets the timeout on the socket to the default timeout set
|
||||
* by {@link #setDefaultTimeout setDefaultTimeout() }.
|
||||
* <p>
|
||||
* _isOpen_ is set to true after calling this method and _socket_
|
||||
* is set to the newly opened socket.
|
||||
*
|
||||
* @param port The port to use for the socket.
|
||||
* @exception SocketException If the socket could not be opened or the
|
||||
* timeout could not be set.
|
||||
***/
|
||||
public void open(int port) throws SocketException
|
||||
{
|
||||
_socket_ = _socketFactory_.createDatagramSocket(port);
|
||||
_socket_.setSoTimeout(_timeout_);
|
||||
_isOpen_ = true;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Opens a DatagramSocket at the specified address on the local host
|
||||
* at a specified port.
|
||||
* Also sets the timeout on the socket to the default timeout set
|
||||
* by {@link #setDefaultTimeout setDefaultTimeout() }.
|
||||
* <p>
|
||||
* _isOpen_ is set to true after calling this method and _socket_
|
||||
* is set to the newly opened socket.
|
||||
*
|
||||
* @param port The port to use for the socket.
|
||||
* @param laddr The local address to use.
|
||||
* @exception SocketException If the socket could not be opened or the
|
||||
* timeout could not be set.
|
||||
***/
|
||||
public void open(int port, InetAddress laddr) throws SocketException
|
||||
{
|
||||
_socket_ = _socketFactory_.createDatagramSocket(port, laddr);
|
||||
_socket_.setSoTimeout(_timeout_);
|
||||
_isOpen_ = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***
|
||||
* Closes the DatagramSocket used for the connection.
|
||||
* You should call this method after you've finished using the class
|
||||
* instance and also before you call {@link #open open() }
|
||||
* again. _isOpen_ is set to false and _socket_ is set to null.
|
||||
* If you call this method when the client socket is not open,
|
||||
* a NullPointerException is thrown.
|
||||
***/
|
||||
public void close()
|
||||
{
|
||||
if (_socket_ != null) {
|
||||
_socket_.close();
|
||||
}
|
||||
_socket_ = null;
|
||||
_isOpen_ = false;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns true if the client has a currently open socket.
|
||||
*
|
||||
* @return True if the client has a curerntly open socket, false otherwise.
|
||||
***/
|
||||
public boolean isOpen()
|
||||
{
|
||||
return _isOpen_;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Set the default timeout in milliseconds to use when opening a socket.
|
||||
* After a call to open, the timeout for the socket is set using this value.
|
||||
* This method should be used prior to a call to {@link #open open()}
|
||||
* and should not be confused with {@link #setSoTimeout setSoTimeout()}
|
||||
* which operates on the currently open socket. _timeout_ contains
|
||||
* the new timeout value.
|
||||
*
|
||||
* @param timeout The timeout in milliseconds to use for the datagram socket
|
||||
* connection.
|
||||
***/
|
||||
public void setDefaultTimeout(int timeout)
|
||||
{
|
||||
_timeout_ = timeout;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the default timeout in milliseconds that is used when
|
||||
* opening a socket.
|
||||
*
|
||||
* @return The default timeout in milliseconds that is used when
|
||||
* opening a socket.
|
||||
***/
|
||||
public int getDefaultTimeout()
|
||||
{
|
||||
return _timeout_;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Set the timeout in milliseconds of a currently open connection.
|
||||
* Only call this method after a connection has been opened
|
||||
* by {@link #open open()}.
|
||||
*
|
||||
* @param timeout The timeout in milliseconds to use for the currently
|
||||
* open datagram socket connection.
|
||||
* @throws SocketException if an error setting the timeout
|
||||
***/
|
||||
public void setSoTimeout(int timeout) throws SocketException
|
||||
{
|
||||
_socket_.setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the timeout in milliseconds of the currently opened socket.
|
||||
* If you call this method when the client socket is not open,
|
||||
* a NullPointerException is thrown.
|
||||
*
|
||||
* @return The timeout in milliseconds of the currently opened socket.
|
||||
* @throws SocketException if an error getting the timeout
|
||||
***/
|
||||
public int getSoTimeout() throws SocketException
|
||||
{
|
||||
return _socket_.getSoTimeout();
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the port number of the open socket on the local host used
|
||||
* for the connection. If you call this method when the client socket
|
||||
* is not open, a NullPointerException is thrown.
|
||||
*
|
||||
* @return The port number of the open socket on the local host used
|
||||
* for the connection.
|
||||
***/
|
||||
public int getLocalPort()
|
||||
{
|
||||
return _socket_.getLocalPort();
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the local address to which the client's socket is bound.
|
||||
* If you call this method when the client socket is not open, a
|
||||
* NullPointerException is thrown.
|
||||
*
|
||||
* @return The local address to which the client's socket is bound.
|
||||
***/
|
||||
public InetAddress getLocalAddress()
|
||||
{
|
||||
return _socket_.getLocalAddress();
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Sets the DatagramSocketFactory used by the DatagramSocketClient
|
||||
* to open DatagramSockets. If the factory value is null, then a default
|
||||
* factory is used (only do this to reset the factory after having
|
||||
* previously altered it).
|
||||
*
|
||||
* @param factory The new DatagramSocketFactory the DatagramSocketClient
|
||||
* should use.
|
||||
***/
|
||||
public void setDatagramSocketFactory(DatagramSocketFactory factory)
|
||||
{
|
||||
if (factory == null) {
|
||||
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
|
||||
} else {
|
||||
_socketFactory_ = factory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charset name.
|
||||
*
|
||||
* @return the charset name.
|
||||
* @since 3.3
|
||||
* TODO Will be deprecated once the code requires Java 1.6 as a mininmum
|
||||
*/
|
||||
public String getCharsetName() {
|
||||
return charset.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charset.
|
||||
*
|
||||
* @return the charset.
|
||||
* @since 3.3
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charset.
|
||||
*
|
||||
* @param charset the charset.
|
||||
* @since 3.3
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/***
|
||||
* The DatagramSocketFactory interface provides a means for the
|
||||
* programmer to control the creation of datagram sockets and
|
||||
* provide his own DatagramSocket implementations for use by all
|
||||
* classes derived from
|
||||
* {@link org.apache.commons.net.DatagramSocketClient}
|
||||
* .
|
||||
* This allows you to provide your own DatagramSocket implementations and
|
||||
* to perform security checks or browser capability requests before
|
||||
* creating a DatagramSocket.
|
||||
*
|
||||
*
|
||||
***/
|
||||
|
||||
public interface DatagramSocketFactory
|
||||
{
|
||||
|
||||
/***
|
||||
* Creates a DatagramSocket on the local host at the first available port.
|
||||
* @return the socket
|
||||
*
|
||||
* @exception SocketException If the socket could not be created.
|
||||
***/
|
||||
public DatagramSocket createDatagramSocket() throws SocketException;
|
||||
|
||||
/***
|
||||
* Creates a DatagramSocket on the local host at a specified port.
|
||||
*
|
||||
* @param port The port to use for the socket.
|
||||
* @return the socket
|
||||
* @exception SocketException If the socket could not be created.
|
||||
***/
|
||||
public DatagramSocket createDatagramSocket(int port) throws SocketException;
|
||||
|
||||
/***
|
||||
* Creates a DatagramSocket at the specified address on the local host
|
||||
* at a specified port.
|
||||
*
|
||||
* @param port The port to use for the socket.
|
||||
* @param laddr The local address to use.
|
||||
* @return the socket
|
||||
* @exception SocketException If the socket could not be created.
|
||||
***/
|
||||
public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
|
||||
throws SocketException;
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/***
|
||||
* DefaultDatagramSocketFactory implements the DatagramSocketFactory
|
||||
* interface by simply wrapping the java.net.DatagramSocket
|
||||
* constructors. It is the default DatagramSocketFactory used by
|
||||
* {@link org.apache.commons.net.DatagramSocketClient}
|
||||
* implementations.
|
||||
*
|
||||
*
|
||||
* @see DatagramSocketFactory
|
||||
* @see DatagramSocketClient
|
||||
* @see DatagramSocketClient#setDatagramSocketFactory
|
||||
***/
|
||||
|
||||
public class DefaultDatagramSocketFactory implements DatagramSocketFactory
|
||||
{
|
||||
|
||||
/***
|
||||
* Creates a DatagramSocket on the local host at the first available port.
|
||||
* @return a new DatagramSocket
|
||||
* @exception SocketException If the socket could not be created.
|
||||
***/
|
||||
@Override
|
||||
public DatagramSocket createDatagramSocket() throws SocketException
|
||||
{
|
||||
return new DatagramSocket();
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a DatagramSocket on the local host at a specified port.
|
||||
*
|
||||
* @param port The port to use for the socket.
|
||||
* @return a new DatagramSocket
|
||||
* @exception SocketException If the socket could not be created.
|
||||
***/
|
||||
@Override
|
||||
public DatagramSocket createDatagramSocket(int port) throws SocketException
|
||||
{
|
||||
return new DatagramSocket(port);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a DatagramSocket at the specified address on the local host
|
||||
* at a specified port.
|
||||
*
|
||||
* @param port The port to use for the socket.
|
||||
* @param laddr The local address to use.
|
||||
* @return a new DatagramSocket
|
||||
* @exception SocketException If the socket could not be created.
|
||||
***/
|
||||
@Override
|
||||
public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
|
||||
throws SocketException
|
||||
{
|
||||
return new DatagramSocket(port, laddr);
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
/***
|
||||
* DefaultSocketFactory implements the SocketFactory interface by
|
||||
* simply wrapping the java.net.Socket and java.net.ServerSocket
|
||||
* constructors. It is the default SocketFactory used by
|
||||
* {@link org.apache.commons.net.SocketClient}
|
||||
* implementations.
|
||||
*
|
||||
*
|
||||
* @see SocketFactory
|
||||
* @see SocketClient
|
||||
* @see SocketClient#setSocketFactory
|
||||
***/
|
||||
|
||||
public class DefaultSocketFactory extends SocketFactory
|
||||
{
|
||||
/** The proxy to use when creating new sockets. */
|
||||
private final Proxy connProxy;
|
||||
|
||||
/**
|
||||
* The default constructor.
|
||||
*/
|
||||
public DefaultSocketFactory()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor for sockets with proxy support.
|
||||
*
|
||||
* @param proxy The Proxy to use when creating new Sockets.
|
||||
* @since 3.2
|
||||
*/
|
||||
public DefaultSocketFactory(Proxy proxy)
|
||||
{
|
||||
connProxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unconnected Socket.
|
||||
*
|
||||
* @return A new unconnected Socket.
|
||||
* @exception IOException If an I/O error occurs while creating the Socket.
|
||||
* @since 3.2
|
||||
*/
|
||||
@Override
|
||||
public Socket createSocket() throws IOException
|
||||
{
|
||||
if (connProxy != null)
|
||||
{
|
||||
return new Socket(connProxy);
|
||||
}
|
||||
return new Socket();
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a Socket connected to the given host and port.
|
||||
*
|
||||
* @param host The hostname to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @return A Socket connected to the given host and port.
|
||||
* @exception UnknownHostException If the hostname cannot be resolved.
|
||||
* @exception IOException If an I/O error occurs while creating the Socket.
|
||||
***/
|
||||
@Override
|
||||
public Socket createSocket(String host, int port)
|
||||
throws UnknownHostException, IOException
|
||||
{
|
||||
if (connProxy != null)
|
||||
{
|
||||
Socket s = new Socket(connProxy);
|
||||
s.connect(new InetSocketAddress(host, port));
|
||||
return s;
|
||||
}
|
||||
return new Socket(host, port);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a Socket connected to the given host and port.
|
||||
*
|
||||
* @param address The address of the host to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @return A Socket connected to the given host and port.
|
||||
* @exception IOException If an I/O error occurs while creating the Socket.
|
||||
***/
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port)
|
||||
throws IOException
|
||||
{
|
||||
if (connProxy != null)
|
||||
{
|
||||
Socket s = new Socket(connProxy);
|
||||
s.connect(new InetSocketAddress(address, port));
|
||||
return s;
|
||||
}
|
||||
return new Socket(address, port);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a Socket connected to the given host and port and
|
||||
* originating from the specified local address and port.
|
||||
*
|
||||
* @param host The hostname to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @param localAddr The local address to use.
|
||||
* @param localPort The local port to use.
|
||||
* @return A Socket connected to the given host and port.
|
||||
* @exception UnknownHostException If the hostname cannot be resolved.
|
||||
* @exception IOException If an I/O error occurs while creating the Socket.
|
||||
***/
|
||||
@Override
|
||||
public Socket createSocket(String host, int port,
|
||||
InetAddress localAddr, int localPort)
|
||||
throws UnknownHostException, IOException
|
||||
{
|
||||
if (connProxy != null)
|
||||
{
|
||||
Socket s = new Socket(connProxy);
|
||||
s.bind(new InetSocketAddress(localAddr, localPort));
|
||||
s.connect(new InetSocketAddress(host, port));
|
||||
return s;
|
||||
}
|
||||
return new Socket(host, port, localAddr, localPort);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a Socket connected to the given host and port and
|
||||
* originating from the specified local address and port.
|
||||
*
|
||||
* @param address The address of the host to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @param localAddr The local address to use.
|
||||
* @param localPort The local port to use.
|
||||
* @return A Socket connected to the given host and port.
|
||||
* @exception IOException If an I/O error occurs while creating the Socket.
|
||||
***/
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port,
|
||||
InetAddress localAddr, int localPort)
|
||||
throws IOException
|
||||
{
|
||||
if (connProxy != null)
|
||||
{
|
||||
Socket s = new Socket(connProxy);
|
||||
s.bind(new InetSocketAddress(localAddr, localPort));
|
||||
s.connect(new InetSocketAddress(address, port));
|
||||
return s;
|
||||
}
|
||||
return new Socket(address, port, localAddr, localPort);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a ServerSocket bound to a specified port. A port
|
||||
* of 0 will create the ServerSocket on a system-determined free port.
|
||||
*
|
||||
* @param port The port on which to listen, or 0 to use any free port.
|
||||
* @return A ServerSocket that will listen on a specified port.
|
||||
* @exception IOException If an I/O error occurs while creating
|
||||
* the ServerSocket.
|
||||
***/
|
||||
public ServerSocket createServerSocket(int port) throws IOException
|
||||
{
|
||||
return new ServerSocket(port);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a ServerSocket bound to a specified port with a given
|
||||
* maximum queue length for incoming connections. A port of 0 will
|
||||
* create the ServerSocket on a system-determined free port.
|
||||
*
|
||||
* @param port The port on which to listen, or 0 to use any free port.
|
||||
* @param backlog The maximum length of the queue for incoming connections.
|
||||
* @return A ServerSocket that will listen on a specified port.
|
||||
* @exception IOException If an I/O error occurs while creating
|
||||
* the ServerSocket.
|
||||
***/
|
||||
public ServerSocket createServerSocket(int port, int backlog)
|
||||
throws IOException
|
||||
{
|
||||
return new ServerSocket(port, backlog);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a ServerSocket bound to a specified port on a given local
|
||||
* address with a given maximum queue length for incoming connections.
|
||||
* A port of 0 will
|
||||
* create the ServerSocket on a system-determined free port.
|
||||
*
|
||||
* @param port The port on which to listen, or 0 to use any free port.
|
||||
* @param backlog The maximum length of the queue for incoming connections.
|
||||
* @param bindAddr The local address to which the ServerSocket should bind.
|
||||
* @return A ServerSocket that will listen on a specified port.
|
||||
* @exception IOException If an I/O error occurs while creating
|
||||
* the ServerSocket.
|
||||
***/
|
||||
public ServerSocket createServerSocket(int port, int backlog,
|
||||
InetAddress bindAddr)
|
||||
throws IOException
|
||||
{
|
||||
return new ServerSocket(port, backlog, bindAddr);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/***
|
||||
* This exception is used to indicate that the reply from a server
|
||||
* could not be interpreted. Most of the NetComponents classes attempt
|
||||
* to be as lenient as possible when receiving server replies. Many
|
||||
* server implementations deviate from IETF protocol specifications, making
|
||||
* it necessary to be as flexible as possible. However, there will be
|
||||
* certain situations where it is not possible to continue an operation
|
||||
* because the server reply could not be interpreted in a meaningful manner.
|
||||
* In these cases, a MalformedServerReplyException should be thrown.
|
||||
*
|
||||
*
|
||||
***/
|
||||
|
||||
public class MalformedServerReplyException extends IOException
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 6006765264250543945L;
|
||||
|
||||
/*** Constructs a MalformedServerReplyException with no message ***/
|
||||
public MalformedServerReplyException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructs a MalformedServerReplyException with a specified message.
|
||||
*
|
||||
* @param message The message explaining the reason for the exception.
|
||||
***/
|
||||
public MalformedServerReplyException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/***
|
||||
* This is a support class for some of the example programs. It is
|
||||
* a sample implementation of the ProtocolCommandListener interface
|
||||
* which just prints out to a specified stream all command/reply traffic.
|
||||
*
|
||||
* @since 2.0
|
||||
***/
|
||||
|
||||
public class PrintCommandListener implements ProtocolCommandListener
|
||||
{
|
||||
private final PrintWriter __writer;
|
||||
private final boolean __nologin;
|
||||
private final char __eolMarker;
|
||||
private final boolean __directionMarker;
|
||||
|
||||
/**
|
||||
* Create the default instance which prints everything.
|
||||
*
|
||||
* @param stream where to write the commands and responses
|
||||
* e.g. System.out
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintStream stream)
|
||||
{
|
||||
this(new PrintWriter(stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance which optionally suppresses login command text
|
||||
* and indicates where the EOL starts with the specified character.
|
||||
*
|
||||
* @param stream where to write the commands and responses
|
||||
* @param suppressLogin if {@code true}, only print command name for login
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintStream stream, boolean suppressLogin) {
|
||||
this(new PrintWriter(stream), suppressLogin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance which optionally suppresses login command text
|
||||
* and indicates where the EOL starts with the specified character.
|
||||
*
|
||||
* @param stream where to write the commands and responses
|
||||
* @param suppressLogin if {@code true}, only print command name for login
|
||||
* @param eolMarker if non-zero, add a marker just before the EOL.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker) {
|
||||
this(new PrintWriter(stream), suppressLogin, eolMarker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance which optionally suppresses login command text
|
||||
* and indicates where the EOL starts with the specified character.
|
||||
*
|
||||
* @param stream where to write the commands and responses
|
||||
* @param suppressLogin if {@code true}, only print command name for login
|
||||
* @param eolMarker if non-zero, add a marker just before the EOL.
|
||||
* @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker, boolean showDirection) {
|
||||
this(new PrintWriter(stream), suppressLogin, eolMarker, showDirection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default instance which prints everything.
|
||||
*
|
||||
* @param writer where to write the commands and responses
|
||||
*/
|
||||
public PrintCommandListener(PrintWriter writer)
|
||||
{
|
||||
this(writer, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance which optionally suppresses login command text.
|
||||
*
|
||||
* @param writer where to write the commands and responses
|
||||
* @param suppressLogin if {@code true}, only print command name for login
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintWriter writer, boolean suppressLogin)
|
||||
{
|
||||
this(writer, suppressLogin, (char) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance which optionally suppresses login command text
|
||||
* and indicates where the EOL starts with the specified character.
|
||||
*
|
||||
* @param writer where to write the commands and responses
|
||||
* @param suppressLogin if {@code true}, only print command name for login
|
||||
* @param eolMarker if non-zero, add a marker just before the EOL.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker)
|
||||
{
|
||||
this(writer, suppressLogin, eolMarker, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance which optionally suppresses login command text
|
||||
* and indicates where the EOL starts with the specified character.
|
||||
*
|
||||
* @param writer where to write the commands and responses
|
||||
* @param suppressLogin if {@code true}, only print command name for login
|
||||
* @param eolMarker if non-zero, add a marker just before the EOL.
|
||||
* @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker, boolean showDirection)
|
||||
{
|
||||
__writer = writer;
|
||||
__nologin = suppressLogin;
|
||||
__eolMarker = eolMarker;
|
||||
__directionMarker = showDirection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void protocolCommandSent(ProtocolCommandEvent event)
|
||||
{
|
||||
if (__directionMarker) {
|
||||
__writer.print("> ");
|
||||
}
|
||||
if (__nologin) {
|
||||
String cmd = event.getCommand();
|
||||
if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) {
|
||||
__writer.print(cmd);
|
||||
__writer.println(" *******"); // Don't bother with EOL marker for this!
|
||||
} else {
|
||||
final String IMAP_LOGIN = "LOGIN";
|
||||
if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP
|
||||
String msg = event.getMessage();
|
||||
msg=msg.substring(0, msg.indexOf(IMAP_LOGIN)+IMAP_LOGIN.length());
|
||||
__writer.print(msg);
|
||||
__writer.println(" *******"); // Don't bother with EOL marker for this!
|
||||
} else {
|
||||
__writer.print(getPrintableString(event.getMessage()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__writer.print(getPrintableString(event.getMessage()));
|
||||
}
|
||||
__writer.flush();
|
||||
}
|
||||
|
||||
private String getPrintableString(String msg){
|
||||
if (__eolMarker == 0) {
|
||||
return msg;
|
||||
}
|
||||
int pos = msg.indexOf(SocketClient.NETASCII_EOL);
|
||||
if (pos > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(msg.substring(0,pos));
|
||||
sb.append(__eolMarker);
|
||||
sb.append(msg.substring(pos));
|
||||
return sb.toString();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void protocolReplyReceived(ProtocolCommandEvent event)
|
||||
{
|
||||
if (__directionMarker) {
|
||||
__writer.print("< ");
|
||||
}
|
||||
__writer.print(event.getMessage());
|
||||
__writer.flush();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
import java.util.EventObject;
|
||||
|
||||
/***
|
||||
* There exists a large class of IETF protocols that work by sending an
|
||||
* ASCII text command and arguments to a server, and then receiving an
|
||||
* ASCII text reply. For debugging and other purposes, it is extremely
|
||||
* useful to log or keep track of the contents of the protocol messages.
|
||||
* The ProtocolCommandEvent class coupled with the
|
||||
* {@link org.apache.commons.net.ProtocolCommandListener}
|
||||
* interface facilitate this process.
|
||||
*
|
||||
*
|
||||
* @see ProtocolCommandListener
|
||||
* @see ProtocolCommandSupport
|
||||
***/
|
||||
|
||||
public class ProtocolCommandEvent extends EventObject
|
||||
{
|
||||
private static final long serialVersionUID = 403743538418947240L;
|
||||
|
||||
private final int __replyCode;
|
||||
private final boolean __isCommand;
|
||||
private final String __message, __command;
|
||||
|
||||
/***
|
||||
* Creates a ProtocolCommandEvent signalling a command was sent to
|
||||
* the server. ProtocolCommandEvents created with this constructor
|
||||
* should only be sent after a command has been sent, but before the
|
||||
* reply has been received.
|
||||
*
|
||||
* @param source The source of the event.
|
||||
* @param command The string representation of the command type sent, not
|
||||
* including the arguments (e.g., "STAT" or "GET").
|
||||
* @param message The entire command string verbatim as sent to the server,
|
||||
* including all arguments.
|
||||
***/
|
||||
public ProtocolCommandEvent(Object source, String command, String message)
|
||||
{
|
||||
super(source);
|
||||
__replyCode = 0;
|
||||
__message = message;
|
||||
__isCommand = true;
|
||||
__command = command;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Creates a ProtocolCommandEvent signalling a reply to a command was
|
||||
* received. ProtocolCommandEvents created with this constructor
|
||||
* should only be sent after a complete command reply has been received
|
||||
* fromt a server.
|
||||
*
|
||||
* @param source The source of the event.
|
||||
* @param replyCode The integer code indicating the natureof the reply.
|
||||
* This will be the protocol integer value for protocols
|
||||
* that use integer reply codes, or the reply class constant
|
||||
* corresponding to the reply for protocols like POP3 that use
|
||||
* strings like OK rather than integer codes (i.e., POP3Repy.OK).
|
||||
* @param message The entire reply as received from the server.
|
||||
***/
|
||||
public ProtocolCommandEvent(Object source, int replyCode, String message)
|
||||
{
|
||||
super(source);
|
||||
__replyCode = replyCode;
|
||||
__message = message;
|
||||
__isCommand = false;
|
||||
__command = null;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns the string representation of the command type sent (e.g., "STAT"
|
||||
* or "GET"). If the ProtocolCommandEvent is a reply event, then null
|
||||
* is returned.
|
||||
*
|
||||
* @return The string representation of the command type sent, or null
|
||||
* if this is a reply event.
|
||||
***/
|
||||
public String getCommand()
|
||||
{
|
||||
return __command;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the reply code of the received server reply. Undefined if
|
||||
* this is not a reply event.
|
||||
*
|
||||
* @return The reply code of the received server reply. Undefined if
|
||||
* not a reply event.
|
||||
***/
|
||||
public int getReplyCode()
|
||||
{
|
||||
return __replyCode;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns true if the ProtocolCommandEvent was generated as a result
|
||||
* of sending a command.
|
||||
*
|
||||
* @return true If the ProtocolCommandEvent was generated as a result
|
||||
* of sending a command. False otherwise.
|
||||
***/
|
||||
public boolean isCommand()
|
||||
{
|
||||
return __isCommand;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns true if the ProtocolCommandEvent was generated as a result
|
||||
* of receiving a reply.
|
||||
*
|
||||
* @return true If the ProtocolCommandEvent was generated as a result
|
||||
* of receiving a reply. False otherwise.
|
||||
***/
|
||||
public boolean isReply()
|
||||
{
|
||||
return !isCommand();
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns the entire message sent to or received from the server.
|
||||
* Includes the line terminator.
|
||||
*
|
||||
* @return The entire message sent to or received from the server.
|
||||
***/
|
||||
public String getMessage()
|
||||
{
|
||||
return __message;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
import java.util.EventListener;
|
||||
|
||||
/***
|
||||
* There exists a large class of IETF protocols that work by sending an
|
||||
* ASCII text command and arguments to a server, and then receiving an
|
||||
* ASCII text reply. For debugging and other purposes, it is extremely
|
||||
* useful to log or keep track of the contents of the protocol messages.
|
||||
* The ProtocolCommandListener interface coupled with the
|
||||
* {@link ProtocolCommandEvent} class facilitate this process.
|
||||
* <p>
|
||||
* To receive ProtocolCommandEvents, you merely implement the
|
||||
* ProtocolCommandListener interface and register the class as a listener
|
||||
* with a ProtocolCommandEvent source such as
|
||||
* {@link org.apache.commons.net.ftp.FTPClient}.
|
||||
*
|
||||
*
|
||||
* @see ProtocolCommandEvent
|
||||
* @see ProtocolCommandSupport
|
||||
***/
|
||||
|
||||
public interface ProtocolCommandListener extends EventListener
|
||||
{
|
||||
|
||||
/***
|
||||
* This method is invoked by a ProtocolCommandEvent source after
|
||||
* sending a protocol command to a server.
|
||||
*
|
||||
* @param event The ProtocolCommandEvent fired.
|
||||
***/
|
||||
public void protocolCommandSent(ProtocolCommandEvent event);
|
||||
|
||||
/***
|
||||
* This method is invoked by a ProtocolCommandEvent source after
|
||||
* receiving a reply from a server.
|
||||
*
|
||||
* @param event The ProtocolCommandEvent fired.
|
||||
***/
|
||||
public void protocolReplyReceived(ProtocolCommandEvent event);
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.EventListener;
|
||||
|
||||
import org.apache.commons.net.util.ListenerList;
|
||||
|
||||
/***
|
||||
* ProtocolCommandSupport is a convenience class for managing a list of
|
||||
* ProtocolCommandListeners and firing ProtocolCommandEvents. You can
|
||||
* simply delegate ProtocolCommandEvent firing and listener
|
||||
* registering/unregistering tasks to this class.
|
||||
*
|
||||
*
|
||||
* @see ProtocolCommandEvent
|
||||
* @see ProtocolCommandListener
|
||||
***/
|
||||
|
||||
public class ProtocolCommandSupport implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -8017692739988399978L;
|
||||
|
||||
private final Object __source;
|
||||
private final ListenerList __listeners;
|
||||
|
||||
/***
|
||||
* Creates a ProtocolCommandSupport instance using the indicated source
|
||||
* as the source of ProtocolCommandEvents.
|
||||
*
|
||||
* @param source The source to use for all generated ProtocolCommandEvents.
|
||||
***/
|
||||
public ProtocolCommandSupport(Object source)
|
||||
{
|
||||
__listeners = new ListenerList();
|
||||
__source = source;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Fires a ProtocolCommandEvent signalling the sending of a command to all
|
||||
* registered listeners, invoking their
|
||||
* {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() }
|
||||
* methods.
|
||||
*
|
||||
* @param command The string representation of the command type sent, not
|
||||
* including the arguments (e.g., "STAT" or "GET").
|
||||
* @param message The entire command string verbatim as sent to the server,
|
||||
* including all arguments.
|
||||
***/
|
||||
public void fireCommandSent(String command, String message)
|
||||
{
|
||||
ProtocolCommandEvent event;
|
||||
|
||||
event = new ProtocolCommandEvent(__source, command, message);
|
||||
|
||||
for (EventListener listener : __listeners)
|
||||
{
|
||||
((ProtocolCommandListener)listener).protocolCommandSent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Fires a ProtocolCommandEvent signalling the reception of a command reply
|
||||
* to all registered listeners, invoking their
|
||||
* {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() }
|
||||
* methods.
|
||||
*
|
||||
* @param replyCode The integer code indicating the natureof the reply.
|
||||
* This will be the protocol integer value for protocols
|
||||
* that use integer reply codes, or the reply class constant
|
||||
* corresponding to the reply for protocols like POP3 that use
|
||||
* strings like OK rather than integer codes (i.e., POP3Repy.OK).
|
||||
* @param message The entire reply as received from the server.
|
||||
***/
|
||||
public void fireReplyReceived(int replyCode, String message)
|
||||
{
|
||||
ProtocolCommandEvent event;
|
||||
event = new ProtocolCommandEvent(__source, replyCode, message);
|
||||
|
||||
for (EventListener listener : __listeners)
|
||||
{
|
||||
((ProtocolCommandListener)listener).protocolReplyReceived(event);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Adds a ProtocolCommandListener.
|
||||
*
|
||||
* @param listener The ProtocolCommandListener to add.
|
||||
***/
|
||||
public void addProtocolCommandListener(ProtocolCommandListener listener)
|
||||
{
|
||||
__listeners.addListener(listener);
|
||||
}
|
||||
|
||||
/***
|
||||
* Removes a ProtocolCommandListener.
|
||||
*
|
||||
* @param listener The ProtocolCommandListener to remove.
|
||||
***/
|
||||
public void removeProtocolCommandListener(ProtocolCommandListener listener)
|
||||
{
|
||||
__listeners.removeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the number of ProtocolCommandListeners currently registered.
|
||||
*
|
||||
* @return The number of ProtocolCommandListeners currently registered.
|
||||
***/
|
||||
public int getListenerCount()
|
||||
{
|
||||
return __listeners.getListenerCount();
|
||||
}
|
||||
|
||||
}
|
||||
|
888
client/src/main/java/org/apache/commons/net/SocketClient.java
Normal file
888
client/src/main/java/org/apache/commons/net/SocketClient.java
Normal file
@ -0,0 +1,888 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
|
||||
/**
|
||||
* The SocketClient provides the basic operations that are required of
|
||||
* client objects accessing sockets. It is meant to be
|
||||
* subclassed to avoid having to rewrite the same code over and over again
|
||||
* to open a socket, close a socket, set timeouts, etc. Of special note
|
||||
* is the {@link #setSocketFactory setSocketFactory }
|
||||
* method, which allows you to control the type of Socket the SocketClient
|
||||
* creates for initiating network connections. This is especially useful
|
||||
* for adding SSL or proxy support as well as better support for applets. For
|
||||
* example, you could create a
|
||||
* {@link javax.net.SocketFactory} that
|
||||
* requests browser security capabilities before creating a socket.
|
||||
* All classes derived from SocketClient should use the
|
||||
* {@link #_socketFactory_ _socketFactory_ } member variable to
|
||||
* create Socket and ServerSocket instances rather than instantiating
|
||||
* them by directly invoking a constructor. By honoring this contract
|
||||
* you guarantee that a user will always be able to provide his own
|
||||
* Socket implementations by substituting his own SocketFactory.
|
||||
* @see SocketFactory
|
||||
*/
|
||||
public abstract class SocketClient
|
||||
{
|
||||
/**
|
||||
* The end of line character sequence used by most IETF protocols. That
|
||||
* is a carriage return followed by a newline: "\r\n"
|
||||
*/
|
||||
public static final String NETASCII_EOL = "\r\n";
|
||||
|
||||
/** The default SocketFactory shared by all SocketClient instances. */
|
||||
private static final SocketFactory __DEFAULT_SOCKET_FACTORY =
|
||||
SocketFactory.getDefault();
|
||||
|
||||
/** The default {@link ServerSocketFactory} */
|
||||
private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY =
|
||||
ServerSocketFactory.getDefault();
|
||||
|
||||
/**
|
||||
* A ProtocolCommandSupport object used to manage the registering of
|
||||
* ProtocolCommandListeners and the firing of ProtocolCommandEvents.
|
||||
*/
|
||||
private ProtocolCommandSupport __commandSupport;
|
||||
|
||||
/** The timeout to use after opening a socket. */
|
||||
protected int _timeout_;
|
||||
|
||||
/** The socket used for the connection. */
|
||||
protected Socket _socket_;
|
||||
|
||||
/** The hostname used for the connection (null = no hostname supplied). */
|
||||
protected String _hostname_;
|
||||
|
||||
/** The default port the client should connect to. */
|
||||
protected int _defaultPort_;
|
||||
|
||||
/** The socket's InputStream. */
|
||||
protected InputStream _input_;
|
||||
|
||||
/** The socket's OutputStream. */
|
||||
protected OutputStream _output_;
|
||||
|
||||
/** The socket's SocketFactory. */
|
||||
protected SocketFactory _socketFactory_;
|
||||
|
||||
/** The socket's ServerSocket Factory. */
|
||||
protected ServerSocketFactory _serverSocketFactory_;
|
||||
|
||||
/** The socket's connect timeout (0 = infinite timeout) */
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT = 0;
|
||||
protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
|
||||
|
||||
/** Hint for SO_RCVBUF size */
|
||||
private int receiveBufferSize = -1;
|
||||
|
||||
/** Hint for SO_SNDBUF size */
|
||||
private int sendBufferSize = -1;
|
||||
|
||||
/** The proxy to use when connecting. */
|
||||
private Proxy connProxy;
|
||||
|
||||
/**
|
||||
* Charset to use for byte IO.
|
||||
*/
|
||||
private Charset charset = Charset.defaultCharset();
|
||||
|
||||
/**
|
||||
* Default constructor for SocketClient. Initializes
|
||||
* _socket_ to null, _timeout_ to 0, _defaultPort to 0,
|
||||
* _isConnected_ to false, charset to {@code Charset.defaultCharset()}
|
||||
* and _socketFactory_ to a shared instance of
|
||||
* {@link org.apache.commons.net.DefaultSocketFactory}.
|
||||
*/
|
||||
public SocketClient()
|
||||
{
|
||||
_socket_ = null;
|
||||
_hostname_ = null;
|
||||
_input_ = null;
|
||||
_output_ = null;
|
||||
_timeout_ = 0;
|
||||
_defaultPort_ = 0;
|
||||
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
|
||||
_serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Because there are so many connect() methods, the _connectAction_()
|
||||
* method is provided as a means of performing some action immediately
|
||||
* after establishing a connection, rather than reimplementing all
|
||||
* of the connect() methods. The last action performed by every
|
||||
* connect() method after opening a socket is to call this method.
|
||||
* <p>
|
||||
* This method sets the timeout on the just opened socket to the default
|
||||
* timeout set by {@link #setDefaultTimeout setDefaultTimeout() },
|
||||
* sets _input_ and _output_ to the socket's InputStream and OutputStream
|
||||
* respectively, and sets _isConnected_ to true.
|
||||
* <p>
|
||||
* Subclasses overriding this method should start by calling
|
||||
* <code> super._connectAction_() </code> first to ensure the
|
||||
* initialization of the aforementioned protected variables.
|
||||
* @throws IOException (SocketException) if a problem occurs with the socket
|
||||
*/
|
||||
protected void _connectAction_() throws IOException
|
||||
{
|
||||
_socket_.setSoTimeout(_timeout_);
|
||||
_input_ = _socket_.getInputStream();
|
||||
_output_ = _socket_.getOutputStream();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a Socket connected to a remote host at the specified port and
|
||||
* originating from the current host at a system assigned port.
|
||||
* Before returning, {@link #_connectAction_ _connectAction_() }
|
||||
* is called to perform connection initialization actions.
|
||||
* <p>
|
||||
* @param host The remote host.
|
||||
* @param port The port to connect to on the remote host.
|
||||
* @exception SocketException If the socket timeout could not be set.
|
||||
* @exception IOException If the socket could not be opened. In most
|
||||
* cases you will only want to catch IOException since SocketException is
|
||||
* derived from it.
|
||||
*/
|
||||
public void connect(InetAddress host, int port)
|
||||
throws SocketException, IOException
|
||||
{
|
||||
_hostname_ = null;
|
||||
_socket_ = _socketFactory_.createSocket();
|
||||
if (receiveBufferSize != -1) {
|
||||
_socket_.setReceiveBufferSize(receiveBufferSize);
|
||||
}
|
||||
if (sendBufferSize != -1) {
|
||||
_socket_.setSendBufferSize(sendBufferSize);
|
||||
}
|
||||
_socket_.connect(new InetSocketAddress(host, port), connectTimeout);
|
||||
_connectAction_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a Socket connected to a remote host at the specified port and
|
||||
* originating from the current host at a system assigned port.
|
||||
* Before returning, {@link #_connectAction_ _connectAction_() }
|
||||
* is called to perform connection initialization actions.
|
||||
* <p>
|
||||
* @param hostname The name of the remote host.
|
||||
* @param port The port to connect to on the remote host.
|
||||
* @exception SocketException If the socket timeout could not be set.
|
||||
* @exception IOException If the socket could not be opened. In most
|
||||
* cases you will only want to catch IOException since SocketException is
|
||||
* derived from it.
|
||||
* @exception java.net.UnknownHostException If the hostname cannot be resolved.
|
||||
*/
|
||||
public void connect(String hostname, int port)
|
||||
throws SocketException, IOException
|
||||
{
|
||||
connect(InetAddress.getByName(hostname), port);
|
||||
_hostname_ = hostname;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a Socket connected to a remote host at the specified port and
|
||||
* originating from the specified local address and port.
|
||||
* Before returning, {@link #_connectAction_ _connectAction_() }
|
||||
* is called to perform connection initialization actions.
|
||||
* <p>
|
||||
* @param host The remote host.
|
||||
* @param port The port to connect to on the remote host.
|
||||
* @param localAddr The local address to use.
|
||||
* @param localPort The local port to use.
|
||||
* @exception SocketException If the socket timeout could not be set.
|
||||
* @exception IOException If the socket could not be opened. In most
|
||||
* cases you will only want to catch IOException since SocketException is
|
||||
* derived from it.
|
||||
*/
|
||||
public void connect(InetAddress host, int port,
|
||||
InetAddress localAddr, int localPort)
|
||||
throws SocketException, IOException
|
||||
{
|
||||
_hostname_ = null;
|
||||
_socket_ = _socketFactory_.createSocket();
|
||||
if (receiveBufferSize != -1) {
|
||||
_socket_.setReceiveBufferSize(receiveBufferSize);
|
||||
}
|
||||
if (sendBufferSize != -1) {
|
||||
_socket_.setSendBufferSize(sendBufferSize);
|
||||
}
|
||||
_socket_.bind(new InetSocketAddress(localAddr, localPort));
|
||||
_socket_.connect(new InetSocketAddress(host, port), connectTimeout);
|
||||
_connectAction_();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a Socket connected to a remote host at the specified port and
|
||||
* originating from the specified local address and port.
|
||||
* Before returning, {@link #_connectAction_ _connectAction_() }
|
||||
* is called to perform connection initialization actions.
|
||||
* <p>
|
||||
* @param hostname The name of the remote host.
|
||||
* @param port The port to connect to on the remote host.
|
||||
* @param localAddr The local address to use.
|
||||
* @param localPort The local port to use.
|
||||
* @exception SocketException If the socket timeout could not be set.
|
||||
* @exception IOException If the socket could not be opened. In most
|
||||
* cases you will only want to catch IOException since SocketException is
|
||||
* derived from it.
|
||||
* @exception java.net.UnknownHostException If the hostname cannot be resolved.
|
||||
*/
|
||||
public void connect(String hostname, int port,
|
||||
InetAddress localAddr, int localPort)
|
||||
throws SocketException, IOException
|
||||
{
|
||||
connect(InetAddress.getByName(hostname), port, localAddr, localPort);
|
||||
_hostname_ = hostname;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a Socket connected to a remote host at the current default port
|
||||
* and originating from the current host at a system assigned port.
|
||||
* Before returning, {@link #_connectAction_ _connectAction_() }
|
||||
* is called to perform connection initialization actions.
|
||||
* <p>
|
||||
* @param host The remote host.
|
||||
* @exception SocketException If the socket timeout could not be set.
|
||||
* @exception IOException If the socket could not be opened. In most
|
||||
* cases you will only want to catch IOException since SocketException is
|
||||
* derived from it.
|
||||
*/
|
||||
public void connect(InetAddress host) throws SocketException, IOException
|
||||
{
|
||||
_hostname_ = null;
|
||||
connect(host, _defaultPort_);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a Socket connected to a remote host at the current default
|
||||
* port and originating from the current host at a system assigned port.
|
||||
* Before returning, {@link #_connectAction_ _connectAction_() }
|
||||
* is called to perform connection initialization actions.
|
||||
* <p>
|
||||
* @param hostname The name of the remote host.
|
||||
* @exception SocketException If the socket timeout could not be set.
|
||||
* @exception IOException If the socket could not be opened. In most
|
||||
* cases you will only want to catch IOException since SocketException is
|
||||
* derived from it.
|
||||
* @exception java.net.UnknownHostException If the hostname cannot be resolved.
|
||||
*/
|
||||
public void connect(String hostname) throws SocketException, IOException
|
||||
{
|
||||
connect(hostname, _defaultPort_);
|
||||
_hostname_ = hostname;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the socket connection.
|
||||
* You should call this method after you've finished using the class
|
||||
* instance and also before you call
|
||||
* {@link #connect connect() }
|
||||
* again. _isConnected_ is set to false, _socket_ is set to null,
|
||||
* _input_ is set to null, and _output_ is set to null.
|
||||
* <p>
|
||||
* @exception IOException If there is an error closing the socket.
|
||||
*/
|
||||
public void disconnect() throws IOException
|
||||
{
|
||||
closeQuietly(_socket_);
|
||||
closeQuietly(_input_);
|
||||
closeQuietly(_output_);
|
||||
_socket_ = null;
|
||||
_hostname_ = null;
|
||||
_input_ = null;
|
||||
_output_ = null;
|
||||
}
|
||||
|
||||
private void closeQuietly(Socket socket) {
|
||||
if (socket != null){
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closeQuietly(Closeable close){
|
||||
if (close != null){
|
||||
try {
|
||||
close.close();
|
||||
} catch (IOException e) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if the client is currently connected to a server.
|
||||
* <p>
|
||||
* Delegates to {@link Socket#isConnected()}
|
||||
* @return True if the client is currently connected to a server,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean isConnected()
|
||||
{
|
||||
if (_socket_ == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _socket_.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make various checks on the socket to test if it is available for use.
|
||||
* Note that the only sure test is to use it, but these checks may help
|
||||
* in some cases.
|
||||
* @see <a href="https://issues.apache.org/jira/browse/NET-350">NET-350</a>
|
||||
* @return {@code true} if the socket appears to be available for use
|
||||
* @since 3.0
|
||||
*/
|
||||
public boolean isAvailable(){
|
||||
if (isConnected()) {
|
||||
try
|
||||
{
|
||||
if (_socket_.getInetAddress() == null) {
|
||||
return false;
|
||||
}
|
||||
if (_socket_.getPort() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (_socket_.getRemoteSocketAddress() == null) {
|
||||
return false;
|
||||
}
|
||||
if (_socket_.isClosed()) {
|
||||
return false;
|
||||
}
|
||||
/* these aren't exact checks (a Socket can be half-open),
|
||||
but since we usually require two-way data transfer,
|
||||
we check these here too: */
|
||||
if (_socket_.isInputShutdown()) {
|
||||
return false;
|
||||
}
|
||||
if (_socket_.isOutputShutdown()) {
|
||||
return false;
|
||||
}
|
||||
/* ignore the result, catch exceptions: */
|
||||
_socket_.getInputStream();
|
||||
_socket_.getOutputStream();
|
||||
}
|
||||
catch (IOException ioex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default port the SocketClient should connect to when a port
|
||||
* is not specified. The {@link #_defaultPort_ _defaultPort_ }
|
||||
* variable stores this value. If never set, the default port is equal
|
||||
* to zero.
|
||||
* <p>
|
||||
* @param port The default port to set.
|
||||
*/
|
||||
public void setDefaultPort(int port)
|
||||
{
|
||||
_defaultPort_ = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the default port (stored in
|
||||
* {@link #_defaultPort_ _defaultPort_ }).
|
||||
* <p>
|
||||
* @return The current value of the default port.
|
||||
*/
|
||||
public int getDefaultPort()
|
||||
{
|
||||
return _defaultPort_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the default timeout in milliseconds to use when opening a socket.
|
||||
* This value is only used previous to a call to
|
||||
* {@link #connect connect()}
|
||||
* and should not be confused with {@link #setSoTimeout setSoTimeout()}
|
||||
* which operates on an the currently opened socket. _timeout_ contains
|
||||
* the new timeout value.
|
||||
* <p>
|
||||
* @param timeout The timeout in milliseconds to use for the socket
|
||||
* connection.
|
||||
*/
|
||||
public void setDefaultTimeout(int timeout)
|
||||
{
|
||||
_timeout_ = timeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default timeout in milliseconds that is used when
|
||||
* opening a socket.
|
||||
* <p>
|
||||
* @return The default timeout in milliseconds that is used when
|
||||
* opening a socket.
|
||||
*/
|
||||
public int getDefaultTimeout()
|
||||
{
|
||||
return _timeout_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the timeout in milliseconds of a currently open connection.
|
||||
* Only call this method after a connection has been opened
|
||||
* by {@link #connect connect()}.
|
||||
* <p>
|
||||
* To set the initial timeout, use {@link #setDefaultTimeout(int)} instead.
|
||||
*
|
||||
* @param timeout The timeout in milliseconds to use for the currently
|
||||
* open socket connection.
|
||||
* @exception SocketException If the operation fails.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public void setSoTimeout(int timeout) throws SocketException
|
||||
{
|
||||
_socket_.setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the underlying socket send buffer size.
|
||||
* <p>
|
||||
* @param size The size of the buffer in bytes.
|
||||
* @throws SocketException never thrown, but subclasses might want to do so
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setSendBufferSize(int size) throws SocketException {
|
||||
sendBufferSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current sendBuffer size
|
||||
* @return the size, or -1 if not initialised
|
||||
* @since 3.0
|
||||
*/
|
||||
protected int getSendBufferSize(){
|
||||
return sendBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the underlying socket receive buffer size.
|
||||
* <p>
|
||||
* @param size The size of the buffer in bytes.
|
||||
* @throws SocketException never (but subclasses may wish to do so)
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setReceiveBufferSize(int size) throws SocketException {
|
||||
receiveBufferSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current receivedBuffer size
|
||||
* @return the size, or -1 if not initialised
|
||||
* @since 3.0
|
||||
*/
|
||||
protected int getReceiveBufferSize(){
|
||||
return receiveBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timeout in milliseconds of the currently opened socket.
|
||||
* <p>
|
||||
* @return The timeout in milliseconds of the currently opened socket.
|
||||
* @exception SocketException If the operation fails.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public int getSoTimeout() throws SocketException
|
||||
{
|
||||
return _socket_.getSoTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the Nagle's algorithm (TCP_NODELAY) on the
|
||||
* currently opened socket.
|
||||
* <p>
|
||||
* @param on True if Nagle's algorithm is to be enabled, false if not.
|
||||
* @exception SocketException If the operation fails.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public void setTcpNoDelay(boolean on) throws SocketException
|
||||
{
|
||||
_socket_.setTcpNoDelay(on);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if Nagle's algorithm is enabled on the currently opened
|
||||
* socket.
|
||||
* <p>
|
||||
* @return True if Nagle's algorithm is enabled on the currently opened
|
||||
* socket, false otherwise.
|
||||
* @exception SocketException If the operation fails.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public boolean getTcpNoDelay() throws SocketException
|
||||
{
|
||||
return _socket_.getTcpNoDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SO_KEEPALIVE flag on the currently opened socket.
|
||||
*
|
||||
* From the Javadocs, the default keepalive time is 2 hours (although this is
|
||||
* implementation dependent). It looks as though the Windows WSA sockets implementation
|
||||
* allows a specific keepalive value to be set, although this seems not to be the case on
|
||||
* other systems.
|
||||
* @param keepAlive If true, keepAlive is turned on
|
||||
* @throws SocketException if there is a problem with the socket
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
* @since 2.2
|
||||
*/
|
||||
public void setKeepAlive(boolean keepAlive) throws SocketException {
|
||||
_socket_.setKeepAlive(keepAlive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the SO_KEEPALIVE flag on the currently opened socket.
|
||||
* Delegates to {@link Socket#getKeepAlive()}
|
||||
* @return True if SO_KEEPALIVE is enabled.
|
||||
* @throws SocketException if there is a problem with the socket
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
* @since 2.2
|
||||
*/
|
||||
public boolean getKeepAlive() throws SocketException {
|
||||
return _socket_.getKeepAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SO_LINGER timeout on the currently opened socket.
|
||||
* <p>
|
||||
* @param on True if linger is to be enabled, false if not.
|
||||
* @param val The linger timeout (in hundredths of a second?)
|
||||
* @exception SocketException If the operation fails.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public void setSoLinger(boolean on, int val) throws SocketException
|
||||
{
|
||||
_socket_.setSoLinger(on, val);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current SO_LINGER timeout of the currently opened socket.
|
||||
* <p>
|
||||
* @return The current SO_LINGER timeout. If SO_LINGER is disabled returns
|
||||
* -1.
|
||||
* @exception SocketException If the operation fails.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public int getSoLinger() throws SocketException
|
||||
{
|
||||
return _socket_.getSoLinger();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the port number of the open socket on the local host used
|
||||
* for the connection.
|
||||
* Delegates to {@link Socket#getLocalPort()}
|
||||
* <p>
|
||||
* @return The port number of the open socket on the local host used
|
||||
* for the connection.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public int getLocalPort()
|
||||
{
|
||||
return _socket_.getLocalPort();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the local address to which the client's socket is bound.
|
||||
* Delegates to {@link Socket#getLocalAddress()}
|
||||
* <p>
|
||||
* @return The local address to which the client's socket is bound.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public InetAddress getLocalAddress()
|
||||
{
|
||||
return _socket_.getLocalAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port number of the remote host to which the client is
|
||||
* connected.
|
||||
* Delegates to {@link Socket#getPort()}
|
||||
* <p>
|
||||
* @return The port number of the remote host to which the client is
|
||||
* connected.
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public int getRemotePort()
|
||||
{
|
||||
return _socket_.getPort();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return The remote address to which the client is connected.
|
||||
* Delegates to {@link Socket#getInetAddress()}
|
||||
* @throws NullPointerException if the socket is not currently open
|
||||
*/
|
||||
public InetAddress getRemoteAddress()
|
||||
{
|
||||
return _socket_.getInetAddress();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the remote end of the given socket is connected to the
|
||||
* the same host that the SocketClient is currently connected to. This
|
||||
* is useful for doing a quick security check when a client needs to
|
||||
* accept a connection from a server, such as an FTP data connection or
|
||||
* a BSD R command standard error stream.
|
||||
* <p>
|
||||
* @param socket the item to check against
|
||||
* @return True if the remote hosts are the same, false if not.
|
||||
*/
|
||||
public boolean verifyRemote(Socket socket)
|
||||
{
|
||||
InetAddress host1, host2;
|
||||
|
||||
host1 = socket.getInetAddress();
|
||||
host2 = getRemoteAddress();
|
||||
|
||||
return host1.equals(host2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the SocketFactory used by the SocketClient to open socket
|
||||
* connections. If the factory value is null, then a default
|
||||
* factory is used (only do this to reset the factory after having
|
||||
* previously altered it).
|
||||
* Any proxy setting is discarded.
|
||||
* <p>
|
||||
* @param factory The new SocketFactory the SocketClient should use.
|
||||
*/
|
||||
public void setSocketFactory(SocketFactory factory)
|
||||
{
|
||||
if (factory == null) {
|
||||
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
|
||||
} else {
|
||||
_socketFactory_ = factory;
|
||||
}
|
||||
// re-setting the socket factory makes the proxy setting useless,
|
||||
// so set the field to null so that getProxy() doesn't return a
|
||||
// Proxy that we're actually not using.
|
||||
connProxy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ServerSocketFactory used by the SocketClient to open ServerSocket
|
||||
* connections. If the factory value is null, then a default
|
||||
* factory is used (only do this to reset the factory after having
|
||||
* previously altered it).
|
||||
* <p>
|
||||
* @param factory The new ServerSocketFactory the SocketClient should use.
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setServerSocketFactory(ServerSocketFactory factory) {
|
||||
if (factory == null) {
|
||||
_serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
|
||||
} else {
|
||||
_serverSocketFactory_ = factory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's
|
||||
* connect() method.
|
||||
* @param connectTimeout The connection timeout to use (in ms)
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying socket connection timeout.
|
||||
* @return timeout (in ms)
|
||||
* @since 2.0
|
||||
*/
|
||||
public int getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying {@link ServerSocketFactory}
|
||||
* @return The server socket factory
|
||||
* @since 2.2
|
||||
*/
|
||||
public ServerSocketFactory getServerSocketFactory() {
|
||||
return _serverSocketFactory_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a ProtocolCommandListener.
|
||||
*
|
||||
* @param listener The ProtocolCommandListener to add.
|
||||
* @since 3.0
|
||||
*/
|
||||
public void addProtocolCommandListener(ProtocolCommandListener listener) {
|
||||
getCommandSupport().addProtocolCommandListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a ProtocolCommandListener.
|
||||
*
|
||||
* @param listener The ProtocolCommandListener to remove.
|
||||
* @since 3.0
|
||||
*/
|
||||
public void removeProtocolCommandListener(ProtocolCommandListener listener) {
|
||||
getCommandSupport().removeProtocolCommandListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are any listeners, send them the reply details.
|
||||
*
|
||||
* @param replyCode the code extracted from the reply
|
||||
* @param reply the full reply text
|
||||
* @since 3.0
|
||||
*/
|
||||
protected void fireReplyReceived(int replyCode, String reply) {
|
||||
if (getCommandSupport().getListenerCount() > 0) {
|
||||
getCommandSupport().fireReplyReceived(replyCode, reply);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are any listeners, send them the command details.
|
||||
*
|
||||
* @param command the command name
|
||||
* @param message the complete message, including command name
|
||||
* @since 3.0
|
||||
*/
|
||||
protected void fireCommandSent(String command, String message) {
|
||||
if (getCommandSupport().getListenerCount() > 0) {
|
||||
getCommandSupport().fireCommandSent(command, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the CommandSupport instance if required
|
||||
*/
|
||||
protected void createCommandSupport(){
|
||||
__commandSupport = new ProtocolCommandSupport(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can override this if they need to provide their own
|
||||
* instance field for backwards compatibilty.
|
||||
*
|
||||
* @return the CommandSupport instance, may be {@code null}
|
||||
* @since 3.0
|
||||
*/
|
||||
protected ProtocolCommandSupport getCommandSupport() {
|
||||
return __commandSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the proxy for use with all the connections.
|
||||
* The proxy is used for connections established after the
|
||||
* call to this method.
|
||||
*
|
||||
* @param proxy the new proxy for connections.
|
||||
* @since 3.2
|
||||
*/
|
||||
public void setProxy(Proxy proxy) {
|
||||
setSocketFactory(new DefaultSocketFactory(proxy));
|
||||
connProxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the proxy for use with all the connections.
|
||||
* @return the current proxy for connections.
|
||||
*/
|
||||
public Proxy getProxy() {
|
||||
return connProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charset name.
|
||||
*
|
||||
* @return the charset.
|
||||
* @since 3.3
|
||||
* @deprecated Since the code now requires Java 1.6 as a mininmum
|
||||
*/
|
||||
@Deprecated
|
||||
public String getCharsetName() {
|
||||
return charset.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charset.
|
||||
*
|
||||
* @return the charset.
|
||||
* @since 3.3
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charset.
|
||||
*
|
||||
* @param charset the charset.
|
||||
* @since 3.3
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/*
|
||||
* N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility,
|
||||
* so the abstract method is needed to pass the instance to the methods which were moved here.
|
||||
*/
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* Implements the telnet echo option RFC 857.
|
||||
***/
|
||||
public class EchoOptionHandler extends TelnetOptionHandler
|
||||
{
|
||||
/***
|
||||
* Constructor for the EchoOptionHandler. Allows defining desired
|
||||
* initial setting for local/remote activation of this option and
|
||||
* behaviour in case a local/remote activation request for this
|
||||
* option is received.
|
||||
* <p>
|
||||
* @param initlocal - if set to true, a WILL is sent upon connection.
|
||||
* @param initremote - if set to true, a DO is sent upon connection.
|
||||
* @param acceptlocal - if set to true, any DO request is accepted.
|
||||
* @param acceptremote - if set to true, any WILL request is accepted.
|
||||
***/
|
||||
public EchoOptionHandler(boolean initlocal, boolean initremote,
|
||||
boolean acceptlocal, boolean acceptremote)
|
||||
{
|
||||
super(TelnetOption.ECHO, initlocal, initremote,
|
||||
acceptlocal, acceptremote);
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructor for the EchoOptionHandler. Initial and accept
|
||||
* behaviour flags are set to false
|
||||
***/
|
||||
public EchoOptionHandler()
|
||||
{
|
||||
super(TelnetOption.ECHO, false, false, false, false);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* The InvalidTelnetOptionException is the exception that is
|
||||
* thrown whenever a TelnetOptionHandler with an invlaid
|
||||
* option code is registered in TelnetClient with addOptionHandler.
|
||||
***/
|
||||
public class InvalidTelnetOptionException extends Exception
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = -2516777155928793597L;
|
||||
|
||||
/***
|
||||
* Option code
|
||||
***/
|
||||
private final int optionCode;
|
||||
|
||||
/***
|
||||
* Error message
|
||||
***/
|
||||
private final String msg;
|
||||
|
||||
/***
|
||||
* Constructor for the exception.
|
||||
* <p>
|
||||
* @param message - Error message.
|
||||
* @param optcode - Option code.
|
||||
***/
|
||||
public InvalidTelnetOptionException(String message, int optcode)
|
||||
{
|
||||
optionCode = optcode;
|
||||
msg = message;
|
||||
}
|
||||
|
||||
/***
|
||||
* Gets the error message of ths exception.
|
||||
* <p>
|
||||
* @return the error message.
|
||||
***/
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
return (msg + ": " + optionCode);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* Simple option handler that can be used for options
|
||||
* that don't require subnegotiation.
|
||||
***/
|
||||
public class SimpleOptionHandler extends TelnetOptionHandler
|
||||
{
|
||||
/***
|
||||
* Constructor for the SimpleOptionHandler. Allows defining desired
|
||||
* initial setting for local/remote activation of this option and
|
||||
* behaviour in case a local/remote activation request for this
|
||||
* option is received.
|
||||
* <p>
|
||||
* @param optcode - option code.
|
||||
* @param initlocal - if set to true, a WILL is sent upon connection.
|
||||
* @param initremote - if set to true, a DO is sent upon connection.
|
||||
* @param acceptlocal - if set to true, any DO request is accepted.
|
||||
* @param acceptremote - if set to true, any WILL request is accepted.
|
||||
***/
|
||||
public SimpleOptionHandler(int optcode,
|
||||
boolean initlocal,
|
||||
boolean initremote,
|
||||
boolean acceptlocal,
|
||||
boolean acceptremote)
|
||||
{
|
||||
super(optcode, initlocal, initremote,
|
||||
acceptlocal, acceptremote);
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructor for the SimpleOptionHandler. Initial and accept
|
||||
* behaviour flags are set to false
|
||||
* <p>
|
||||
* @param optcode - option code.
|
||||
***/
|
||||
public SimpleOptionHandler(int optcode)
|
||||
{
|
||||
super(optcode, false, false, false, false);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* Implements the telnet suppress go ahead option RFC 858.
|
||||
***/
|
||||
public class SuppressGAOptionHandler extends TelnetOptionHandler
|
||||
{
|
||||
/***
|
||||
* Constructor for the SuppressGAOptionHandler. Allows defining desired
|
||||
* initial setting for local/remote activation of this option and
|
||||
* behaviour in case a local/remote activation request for this
|
||||
* option is received.
|
||||
* <p>
|
||||
* @param initlocal - if set to true, a WILL is sent upon connection.
|
||||
* @param initremote - if set to true, a DO is sent upon connection.
|
||||
* @param acceptlocal - if set to true, any DO request is accepted.
|
||||
* @param acceptremote - if set to true, any WILL request is accepted.
|
||||
***/
|
||||
public SuppressGAOptionHandler(boolean initlocal, boolean initremote,
|
||||
boolean acceptlocal, boolean acceptremote)
|
||||
{
|
||||
super(TelnetOption.SUPPRESS_GO_AHEAD, initlocal, initremote,
|
||||
acceptlocal, acceptremote);
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructor for the SuppressGAOptionHandler. Initial and accept
|
||||
* behaviour flags are set to false
|
||||
***/
|
||||
public SuppressGAOptionHandler()
|
||||
{
|
||||
super(TelnetOption.SUPPRESS_GO_AHEAD, false, false, false, false);
|
||||
}
|
||||
|
||||
}
|
1265
client/src/main/java/org/apache/commons/net/telnet/Telnet.java
Normal file
1265
client/src/main/java/org/apache/commons/net/telnet/Telnet.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/***
|
||||
* The TelnetClient class implements the simple network virtual
|
||||
* terminal (NVT) for the Telnet protocol according to RFC 854. It
|
||||
* does not implement any of the extra Telnet options because it
|
||||
* is meant to be used within a Java program providing automated
|
||||
* access to Telnet accessible resources.
|
||||
* <p>
|
||||
* The class can be used by first connecting to a server using the
|
||||
* SocketClient
|
||||
* {@link org.apache.commons.net.SocketClient#connect connect}
|
||||
* method. Then an InputStream and OutputStream for sending and
|
||||
* receiving data over the Telnet connection can be obtained by
|
||||
* using the {@link #getInputStream getInputStream() } and
|
||||
* {@link #getOutputStream getOutputStream() } methods.
|
||||
* When you finish using the streams, you must call
|
||||
* {@link #disconnect disconnect } rather than simply
|
||||
* closing the streams.
|
||||
***/
|
||||
|
||||
public class TelnetClient extends Telnet
|
||||
{
|
||||
private InputStream __input;
|
||||
private OutputStream __output;
|
||||
protected boolean readerThread = true;
|
||||
private TelnetInputListener inputListener;
|
||||
|
||||
/***
|
||||
* Default TelnetClient constructor, sets terminal-type {@code VT100}.
|
||||
***/
|
||||
public TelnetClient()
|
||||
{
|
||||
/* TERMINAL-TYPE option (start)*/
|
||||
super ("VT100");
|
||||
/* TERMINAL-TYPE option (end)*/
|
||||
__input = null;
|
||||
__output = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an instance with the specified terminal type.
|
||||
*
|
||||
* @param termtype the terminal type to use, e.g. {@code VT100}
|
||||
*/
|
||||
/* TERMINAL-TYPE option (start)*/
|
||||
public TelnetClient(String termtype)
|
||||
{
|
||||
super (termtype);
|
||||
__input = null;
|
||||
__output = null;
|
||||
}
|
||||
/* TERMINAL-TYPE option (end)*/
|
||||
|
||||
void _flushOutputStream() throws IOException
|
||||
{
|
||||
_output_.flush();
|
||||
}
|
||||
void _closeOutputStream() throws IOException
|
||||
{
|
||||
_output_.close();
|
||||
}
|
||||
|
||||
/***
|
||||
* Handles special connection requirements.
|
||||
*
|
||||
* @exception IOException If an error occurs during connection setup.
|
||||
***/
|
||||
@Override
|
||||
protected void _connectAction_() throws IOException
|
||||
{
|
||||
super._connectAction_();
|
||||
TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread);
|
||||
if(readerThread)
|
||||
{
|
||||
tmp._start();
|
||||
}
|
||||
// __input CANNOT refer to the TelnetInputStream. We run into
|
||||
// blocking problems when some classes use TelnetInputStream, so
|
||||
// we wrap it with a BufferedInputStream which we know is safe.
|
||||
// This blocking behavior requires further investigation, but right
|
||||
// now it looks like classes like InputStreamReader are not implemented
|
||||
// in a safe manner.
|
||||
__input = new BufferedInputStream(tmp);
|
||||
__output = new TelnetOutputStream(this);
|
||||
}
|
||||
|
||||
/***
|
||||
* Disconnects the telnet session, closing the input and output streams
|
||||
* as well as the socket. If you have references to the
|
||||
* input and output streams of the telnet connection, you should not
|
||||
* close them yourself, but rather call disconnect to properly close
|
||||
* the connection.
|
||||
***/
|
||||
@Override
|
||||
public void disconnect() throws IOException
|
||||
{
|
||||
if (__input != null) {
|
||||
__input.close();
|
||||
}
|
||||
if (__output != null) {
|
||||
__output.close();
|
||||
}
|
||||
super.disconnect();
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns the telnet connection output stream. You should not close the
|
||||
* stream when you finish with it. Rather, you should call
|
||||
* {@link #disconnect disconnect }.
|
||||
*
|
||||
* @return The telnet connection output stream.
|
||||
***/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return __output;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns the telnet connection input stream. You should not close the
|
||||
* stream when you finish with it. Rather, you should call
|
||||
* {@link #disconnect disconnect }.
|
||||
*
|
||||
* @return The telnet connection input stream.
|
||||
***/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return __input;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns the state of the option on the local side.
|
||||
*
|
||||
* @param option - Option to be checked.
|
||||
*
|
||||
* @return The state of the option on the local side.
|
||||
***/
|
||||
public boolean getLocalOptionState(int option)
|
||||
{
|
||||
/* BUG (option active when not already acknowledged) (start)*/
|
||||
return (_stateIsWill(option) && _requestedWill(option));
|
||||
/* BUG (option active when not already acknowledged) (end)*/
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns the state of the option on the remote side.
|
||||
*
|
||||
* @param option - Option to be checked.
|
||||
*
|
||||
* @return The state of the option on the remote side.
|
||||
***/
|
||||
public boolean getRemoteOptionState(int option)
|
||||
{
|
||||
/* BUG (option active when not already acknowledged) (start)*/
|
||||
return (_stateIsDo(option) && _requestedDo(option));
|
||||
/* BUG (option active when not already acknowledged) (end)*/
|
||||
}
|
||||
/* open TelnetOptionHandler functionality (end)*/
|
||||
|
||||
/* Code Section added for supporting AYT (start)*/
|
||||
|
||||
/***
|
||||
* Sends an Are You There sequence and waits for the result.
|
||||
*
|
||||
* @param timeout - Time to wait for a response (millis.)
|
||||
*
|
||||
* @return true if AYT received a response, false otherwise
|
||||
*
|
||||
* @throws InterruptedException on error
|
||||
* @throws IllegalArgumentException on error
|
||||
* @throws IOException on error
|
||||
***/
|
||||
public boolean sendAYT(long timeout)
|
||||
throws IOException, IllegalArgumentException, InterruptedException
|
||||
{
|
||||
return (_sendAYT(timeout));
|
||||
}
|
||||
/* Code Section added for supporting AYT (start)*/
|
||||
|
||||
/***
|
||||
* Sends a protocol-specific subnegotiation message to the remote peer.
|
||||
* {@link TelnetClient} will add the IAC SB & IAC SE framing bytes;
|
||||
* the first byte in {@code message} should be the appropriate telnet
|
||||
* option code.
|
||||
*
|
||||
* <p>
|
||||
* This method does not wait for any response. Subnegotiation messages
|
||||
* sent by the remote end can be handled by registering an approrpriate
|
||||
* {@link TelnetOptionHandler}.
|
||||
* </p>
|
||||
*
|
||||
* @param message option code followed by subnegotiation payload
|
||||
* @throws IllegalArgumentException if {@code message} has length zero
|
||||
* @throws IOException if an I/O error occurs while writing the message
|
||||
* @since 3.0
|
||||
***/
|
||||
public void sendSubnegotiation(int[] message)
|
||||
throws IOException, IllegalArgumentException
|
||||
{
|
||||
if (message.length < 1) {
|
||||
throw new IllegalArgumentException("zero length message");
|
||||
}
|
||||
_sendSubnegotiation(message);
|
||||
}
|
||||
|
||||
/***
|
||||
* Sends a command byte to the remote peer, adding the IAC prefix.
|
||||
*
|
||||
* <p>
|
||||
* This method does not wait for any response. Messages
|
||||
* sent by the remote end can be handled by registering an approrpriate
|
||||
* {@link TelnetOptionHandler}.
|
||||
* </p>
|
||||
*
|
||||
* @param command the code for the command
|
||||
* @throws IOException if an I/O error occurs while writing the message
|
||||
* @throws IllegalArgumentException on error
|
||||
* @since 3.0
|
||||
***/
|
||||
public void sendCommand(byte command)
|
||||
throws IOException, IllegalArgumentException
|
||||
{
|
||||
_sendCommand(command);
|
||||
}
|
||||
|
||||
/* open TelnetOptionHandler functionality (start)*/
|
||||
|
||||
/***
|
||||
* Registers a new TelnetOptionHandler for this telnet client to use.
|
||||
*
|
||||
* @param opthand - option handler to be registered.
|
||||
*
|
||||
* @throws InvalidTelnetOptionException on error
|
||||
* @throws IOException on error
|
||||
***/
|
||||
@Override
|
||||
public void addOptionHandler(TelnetOptionHandler opthand)
|
||||
throws InvalidTelnetOptionException, IOException
|
||||
{
|
||||
super.addOptionHandler(opthand);
|
||||
}
|
||||
/* open TelnetOptionHandler functionality (end)*/
|
||||
|
||||
/***
|
||||
* Unregisters a TelnetOptionHandler.
|
||||
*
|
||||
* @param optcode - Code of the option to be unregistered.
|
||||
*
|
||||
* @throws InvalidTelnetOptionException on error
|
||||
* @throws IOException on error
|
||||
***/
|
||||
@Override
|
||||
public void deleteOptionHandler(int optcode)
|
||||
throws InvalidTelnetOptionException, IOException
|
||||
{
|
||||
super.deleteOptionHandler(optcode);
|
||||
}
|
||||
|
||||
/* Code Section added for supporting spystreams (start)*/
|
||||
/***
|
||||
* Registers an OutputStream for spying what's going on in
|
||||
* the TelnetClient session.
|
||||
*
|
||||
* @param spystream - OutputStream on which session activity
|
||||
* will be echoed.
|
||||
***/
|
||||
public void registerSpyStream(OutputStream spystream)
|
||||
{
|
||||
super._registerSpyStream(spystream);
|
||||
}
|
||||
|
||||
/***
|
||||
* Stops spying this TelnetClient.
|
||||
*
|
||||
***/
|
||||
public void stopSpyStream()
|
||||
{
|
||||
super._stopSpyStream();
|
||||
}
|
||||
/* Code Section added for supporting spystreams (end)*/
|
||||
|
||||
/***
|
||||
* Registers a notification handler to which will be sent
|
||||
* notifications of received telnet option negotiation commands.
|
||||
*
|
||||
* @param notifhand - TelnetNotificationHandler to be registered
|
||||
***/
|
||||
@Override
|
||||
public void registerNotifHandler(TelnetNotificationHandler notifhand)
|
||||
{
|
||||
super.registerNotifHandler(notifhand);
|
||||
}
|
||||
|
||||
/***
|
||||
* Unregisters the current notification handler.
|
||||
*
|
||||
***/
|
||||
@Override
|
||||
public void unregisterNotifHandler()
|
||||
{
|
||||
super.unregisterNotifHandler();
|
||||
}
|
||||
|
||||
/***
|
||||
* Sets the status of the reader thread.
|
||||
*
|
||||
* <p>
|
||||
* When enabled, a seaparate internal reader thread is created for new
|
||||
* connections to read incoming data as it arrives. This results in
|
||||
* immediate handling of option negotiation, notifications, etc.
|
||||
* (at least until the fixed-size internal buffer fills up).
|
||||
* Otherwise, no thread is created an all negotiation and option
|
||||
* handling is deferred until a read() is performed on the
|
||||
* {@link #getInputStream input stream}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The reader thread must be enabled for {@link TelnetInputListener}
|
||||
* support.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When this method is invoked, the reader thread status will apply to all
|
||||
* subsequent connections; the current connection (if any) is not affected.
|
||||
* </p>
|
||||
*
|
||||
* @param flag true to enable the reader thread, false to disable
|
||||
* @see #registerInputListener
|
||||
***/
|
||||
public void setReaderThread(boolean flag)
|
||||
{
|
||||
readerThread = flag;
|
||||
}
|
||||
|
||||
/***
|
||||
* Gets the status of the reader thread.
|
||||
*
|
||||
* @return true if the reader thread is enabled, false otherwise
|
||||
***/
|
||||
public boolean getReaderThread()
|
||||
{
|
||||
return (readerThread);
|
||||
}
|
||||
|
||||
/***
|
||||
* Register a listener to be notified when new incoming data is
|
||||
* available to be read on the {@link #getInputStream input stream}.
|
||||
* Only one listener is supported at a time.
|
||||
*
|
||||
* <p>
|
||||
* More precisely, notifications are issued whenever the number of
|
||||
* bytes available for immediate reading (i.e., the value returned
|
||||
* by {@link InputStream#available}) transitions from zero to non-zero.
|
||||
* Note that (in general) multiple reads may be required to empty the
|
||||
* buffer and reset this notification, because incoming bytes are being
|
||||
* added to the internal buffer asynchronously.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Notifications are only supported when a {@link #setReaderThread
|
||||
* reader thread} is enabled for the connection.
|
||||
* </p>
|
||||
*
|
||||
* @param listener listener to be registered; replaces any previous
|
||||
* @since 3.0
|
||||
***/
|
||||
public synchronized void registerInputListener(TelnetInputListener listener)
|
||||
{
|
||||
this.inputListener = listener;
|
||||
}
|
||||
|
||||
/***
|
||||
* Unregisters the current {@link TelnetInputListener}, if any.
|
||||
*
|
||||
* @since 3.0
|
||||
***/
|
||||
public synchronized void unregisterInputListener()
|
||||
{
|
||||
this.inputListener = null;
|
||||
}
|
||||
|
||||
// Notify input listener
|
||||
void notifyInputListener() {
|
||||
TelnetInputListener listener;
|
||||
synchronized (this) {
|
||||
listener = this.inputListener;
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.telnetInputAvailable();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/**
|
||||
* The TelnetCommand class cannot be instantiated and only serves as a
|
||||
* storehouse for telnet command constants.
|
||||
* @see org.apache.commons.net.telnet.Telnet
|
||||
* @see org.apache.commons.net.telnet.TelnetClient
|
||||
*/
|
||||
|
||||
public final class TelnetCommand
|
||||
{
|
||||
/*** The maximum value a command code can have. This value is 255. ***/
|
||||
public static final int MAX_COMMAND_VALUE = 255;
|
||||
|
||||
/*** Interpret As Command code. Value is 255 according to RFC 854. ***/
|
||||
public static final int IAC = 255;
|
||||
|
||||
/*** Don't use option code. Value is 254 according to RFC 854. ***/
|
||||
public static final int DONT = 254;
|
||||
|
||||
/*** Request to use option code. Value is 253 according to RFC 854. ***/
|
||||
public static final int DO = 253;
|
||||
|
||||
/*** Refuse to use option code. Value is 252 according to RFC 854. ***/
|
||||
public static final int WONT = 252;
|
||||
|
||||
/*** Agree to use option code. Value is 251 according to RFC 854. ***/
|
||||
public static final int WILL = 251;
|
||||
|
||||
/*** Start subnegotiation code. Value is 250 according to RFC 854. ***/
|
||||
public static final int SB = 250;
|
||||
|
||||
/*** Go Ahead code. Value is 249 according to RFC 854. ***/
|
||||
public static final int GA = 249;
|
||||
|
||||
/*** Erase Line code. Value is 248 according to RFC 854. ***/
|
||||
public static final int EL = 248;
|
||||
|
||||
/*** Erase Character code. Value is 247 according to RFC 854. ***/
|
||||
public static final int EC = 247;
|
||||
|
||||
/*** Are You There code. Value is 246 according to RFC 854. ***/
|
||||
public static final int AYT = 246;
|
||||
|
||||
/*** Abort Output code. Value is 245 according to RFC 854. ***/
|
||||
public static final int AO = 245;
|
||||
|
||||
/*** Interrupt Process code. Value is 244 according to RFC 854. ***/
|
||||
public static final int IP = 244;
|
||||
|
||||
/*** Break code. Value is 243 according to RFC 854. ***/
|
||||
public static final int BREAK = 243;
|
||||
|
||||
/*** Data mark code. Value is 242 according to RFC 854. ***/
|
||||
public static final int DM = 242;
|
||||
|
||||
/*** No Operation code. Value is 241 according to RFC 854. ***/
|
||||
public static final int NOP = 241;
|
||||
|
||||
/*** End subnegotiation code. Value is 240 according to RFC 854. ***/
|
||||
public static final int SE = 240;
|
||||
|
||||
/*** End of record code. Value is 239. ***/
|
||||
public static final int EOR = 239;
|
||||
|
||||
/*** Abort code. Value is 238. ***/
|
||||
public static final int ABORT = 238;
|
||||
|
||||
/*** Suspend process code. Value is 237. ***/
|
||||
public static final int SUSP = 237;
|
||||
|
||||
/*** End of file code. Value is 236. ***/
|
||||
public static final int EOF = 236;
|
||||
|
||||
/*** Synchronize code. Value is 242. ***/
|
||||
public static final int SYNCH = 242;
|
||||
|
||||
/*** String representations of commands. ***/
|
||||
private static final String __commandString[] = {
|
||||
"IAC", "DONT", "DO", "WONT", "WILL", "SB", "GA", "EL", "EC", "AYT",
|
||||
"AO", "IP", "BRK", "DMARK", "NOP", "SE", "EOR", "ABORT", "SUSP", "EOF"
|
||||
};
|
||||
|
||||
private static final int __FIRST_COMMAND = IAC;
|
||||
private static final int __LAST_COMMAND = EOF;
|
||||
|
||||
/***
|
||||
* Returns the string representation of the telnet protocol command
|
||||
* corresponding to the given command code.
|
||||
* <p>
|
||||
* @param code The command code of the telnet protocol command.
|
||||
* @return The string representation of the telnet protocol command.
|
||||
***/
|
||||
public static final String getCommand(int code)
|
||||
{
|
||||
return __commandString[__FIRST_COMMAND - code];
|
||||
}
|
||||
|
||||
/***
|
||||
* Determines if a given command code is valid. Returns true if valid,
|
||||
* false if not.
|
||||
* <p>
|
||||
* @param code The command code to test.
|
||||
* @return True if the command code is valid, false if not.
|
||||
**/
|
||||
public static final boolean isValidCommand(int code)
|
||||
{
|
||||
return (code <= __FIRST_COMMAND && code >= __LAST_COMMAND);
|
||||
}
|
||||
|
||||
// Cannot be instantiated
|
||||
private TelnetCommand()
|
||||
{ }
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* Listener interface used for notification that incoming data is
|
||||
* available to be read.
|
||||
*
|
||||
* @see TelnetClient
|
||||
* @since 3.0
|
||||
***/
|
||||
public interface TelnetInputListener
|
||||
{
|
||||
|
||||
/***
|
||||
* Callback method invoked when new incoming data is available on a
|
||||
* {@link TelnetClient}'s {@link TelnetClient#getInputStream input stream}.
|
||||
*
|
||||
* @see TelnetClient#registerInputListener
|
||||
***/
|
||||
public void telnetInputAvailable();
|
||||
}
|
@ -0,0 +1,680 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
|
||||
final class TelnetInputStream extends BufferedInputStream implements Runnable
|
||||
{
|
||||
/** End of file has been reached */
|
||||
private static final int EOF = -1;
|
||||
|
||||
/** Read would block */
|
||||
private static final int WOULD_BLOCK = -2;
|
||||
|
||||
// TODO should these be private enums?
|
||||
static final int _STATE_DATA = 0, _STATE_IAC = 1, _STATE_WILL = 2,
|
||||
_STATE_WONT = 3, _STATE_DO = 4, _STATE_DONT = 5,
|
||||
_STATE_SB = 6, _STATE_SE = 7, _STATE_CR = 8, _STATE_IAC_SB = 9;
|
||||
|
||||
private boolean __hasReachedEOF; // @GuardedBy("__queue")
|
||||
private volatile boolean __isClosed;
|
||||
private boolean __readIsWaiting;
|
||||
private int __receiveState, __queueHead, __queueTail, __bytesAvailable;
|
||||
private final int[] __queue;
|
||||
private final TelnetClient __client;
|
||||
private final Thread __thread;
|
||||
private IOException __ioException;
|
||||
|
||||
/* TERMINAL-TYPE option (start)*/
|
||||
private final int __suboption[] = new int[512];
|
||||
private int __suboption_count = 0;
|
||||
/* TERMINAL-TYPE option (end)*/
|
||||
|
||||
private volatile boolean __threaded;
|
||||
|
||||
TelnetInputStream(InputStream input, TelnetClient client,
|
||||
boolean readerThread)
|
||||
{
|
||||
super(input);
|
||||
__client = client;
|
||||
__receiveState = _STATE_DATA;
|
||||
__isClosed = true;
|
||||
__hasReachedEOF = false;
|
||||
// Make it 2049, because when full, one slot will go unused, and we
|
||||
// want a 2048 byte buffer just to have a round number (base 2 that is)
|
||||
__queue = new int[2049];
|
||||
__queueHead = 0;
|
||||
__queueTail = 0;
|
||||
__bytesAvailable = 0;
|
||||
__ioException = null;
|
||||
__readIsWaiting = false;
|
||||
__threaded = false;
|
||||
if(readerThread) {
|
||||
__thread = new Thread(this);
|
||||
} else {
|
||||
__thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
TelnetInputStream(InputStream input, TelnetClient client) {
|
||||
this(input, client, true);
|
||||
}
|
||||
|
||||
void _start()
|
||||
{
|
||||
if(__thread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int priority;
|
||||
__isClosed = false;
|
||||
// TODO remove this
|
||||
// Need to set a higher priority in case JVM does not use pre-emptive
|
||||
// threads. This should prevent scheduler induced deadlock (rather than
|
||||
// deadlock caused by a bug in this code).
|
||||
priority = Thread.currentThread().getPriority() + 1;
|
||||
if (priority > Thread.MAX_PRIORITY) {
|
||||
priority = Thread.MAX_PRIORITY;
|
||||
}
|
||||
__thread.setPriority(priority);
|
||||
__thread.setDaemon(true);
|
||||
__thread.start();
|
||||
__threaded = true; // tell _processChar that we are running threaded
|
||||
}
|
||||
|
||||
|
||||
// synchronized(__client) critical sections are to protect against
|
||||
// TelnetOutputStream writing through the telnet client at same time
|
||||
// as a processDo/Will/etc. command invoked from TelnetInputStream
|
||||
// tries to write.
|
||||
/**
|
||||
* Get the next byte of data.
|
||||
* IAC commands are processed internally and do not return data.
|
||||
*
|
||||
* @param mayBlock true if method is allowed to block
|
||||
* @return the next byte of data,
|
||||
* or -1 (EOF) if end of stread reached,
|
||||
* or -2 (WOULD_BLOCK) if mayBlock is false and there is no data available
|
||||
*/
|
||||
private int __read(boolean mayBlock) throws IOException
|
||||
{
|
||||
int ch;
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
||||
// If there is no more data AND we were told not to block,
|
||||
// just return WOULD_BLOCK (-2). (More efficient than exception.)
|
||||
if(!mayBlock && super.available() == 0) {
|
||||
return WOULD_BLOCK;
|
||||
}
|
||||
|
||||
// Otherwise, exit only when we reach end of stream.
|
||||
if ((ch = super.read()) < 0) {
|
||||
return EOF;
|
||||
}
|
||||
|
||||
ch = (ch & 0xff);
|
||||
|
||||
/* Code Section added for supporting AYT (start)*/
|
||||
synchronized (__client)
|
||||
{
|
||||
__client._processAYTResponse();
|
||||
}
|
||||
/* Code Section added for supporting AYT (end)*/
|
||||
|
||||
/* Code Section added for supporting spystreams (start)*/
|
||||
__client._spyRead(ch);
|
||||
/* Code Section added for supporting spystreams (end)*/
|
||||
|
||||
switch (__receiveState)
|
||||
{
|
||||
|
||||
case _STATE_CR:
|
||||
if (ch == '\0')
|
||||
{
|
||||
// Strip null
|
||||
continue;
|
||||
}
|
||||
// How do we handle newline after cr?
|
||||
// else if (ch == '\n' && _requestedDont(TelnetOption.ECHO) &&
|
||||
|
||||
// Handle as normal data by falling through to _STATE_DATA case
|
||||
|
||||
//$FALL-THROUGH$
|
||||
case _STATE_DATA:
|
||||
if (ch == TelnetCommand.IAC)
|
||||
{
|
||||
__receiveState = _STATE_IAC;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (ch == '\r')
|
||||
{
|
||||
synchronized (__client)
|
||||
{
|
||||
if (__client._requestedDont(TelnetOption.BINARY)) {
|
||||
__receiveState = _STATE_CR;
|
||||
} else {
|
||||
__receiveState = _STATE_DATA;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__receiveState = _STATE_DATA;
|
||||
}
|
||||
break;
|
||||
|
||||
case _STATE_IAC:
|
||||
switch (ch)
|
||||
{
|
||||
case TelnetCommand.WILL:
|
||||
__receiveState = _STATE_WILL;
|
||||
continue;
|
||||
case TelnetCommand.WONT:
|
||||
__receiveState = _STATE_WONT;
|
||||
continue;
|
||||
case TelnetCommand.DO:
|
||||
__receiveState = _STATE_DO;
|
||||
continue;
|
||||
case TelnetCommand.DONT:
|
||||
__receiveState = _STATE_DONT;
|
||||
continue;
|
||||
/* TERMINAL-TYPE option (start)*/
|
||||
case TelnetCommand.SB:
|
||||
__suboption_count = 0;
|
||||
__receiveState = _STATE_SB;
|
||||
continue;
|
||||
/* TERMINAL-TYPE option (end)*/
|
||||
case TelnetCommand.IAC:
|
||||
__receiveState = _STATE_DATA;
|
||||
break; // exit to enclosing switch to return IAC from read
|
||||
case TelnetCommand.SE: // unexpected byte! ignore it (don't send it as a command)
|
||||
__receiveState = _STATE_DATA;
|
||||
continue;
|
||||
default:
|
||||
__receiveState = _STATE_DATA;
|
||||
__client._processCommand(ch); // Notify the user
|
||||
continue; // move on the next char
|
||||
}
|
||||
break; // exit and return from read
|
||||
case _STATE_WILL:
|
||||
synchronized (__client)
|
||||
{
|
||||
__client._processWill(ch);
|
||||
__client._flushOutputStream();
|
||||
}
|
||||
__receiveState = _STATE_DATA;
|
||||
continue;
|
||||
case _STATE_WONT:
|
||||
synchronized (__client)
|
||||
{
|
||||
__client._processWont(ch);
|
||||
__client._flushOutputStream();
|
||||
}
|
||||
__receiveState = _STATE_DATA;
|
||||
continue;
|
||||
case _STATE_DO:
|
||||
synchronized (__client)
|
||||
{
|
||||
__client._processDo(ch);
|
||||
__client._flushOutputStream();
|
||||
}
|
||||
__receiveState = _STATE_DATA;
|
||||
continue;
|
||||
case _STATE_DONT:
|
||||
synchronized (__client)
|
||||
{
|
||||
__client._processDont(ch);
|
||||
__client._flushOutputStream();
|
||||
}
|
||||
__receiveState = _STATE_DATA;
|
||||
continue;
|
||||
/* TERMINAL-TYPE option (start)*/
|
||||
case _STATE_SB:
|
||||
switch (ch)
|
||||
{
|
||||
case TelnetCommand.IAC:
|
||||
__receiveState = _STATE_IAC_SB;
|
||||
continue;
|
||||
default:
|
||||
// store suboption char
|
||||
if (__suboption_count < __suboption.length) {
|
||||
__suboption[__suboption_count++] = ch;
|
||||
}
|
||||
break;
|
||||
}
|
||||
__receiveState = _STATE_SB;
|
||||
continue;
|
||||
case _STATE_IAC_SB: // IAC received during SB phase
|
||||
switch (ch)
|
||||
{
|
||||
case TelnetCommand.SE:
|
||||
synchronized (__client)
|
||||
{
|
||||
__client._processSuboption(__suboption, __suboption_count);
|
||||
__client._flushOutputStream();
|
||||
}
|
||||
__receiveState = _STATE_DATA;
|
||||
continue;
|
||||
case TelnetCommand.IAC: // De-dup the duplicated IAC
|
||||
if (__suboption_count < __suboption.length) {
|
||||
__suboption[__suboption_count++] = ch;
|
||||
}
|
||||
break;
|
||||
default: // unexpected byte! ignore it
|
||||
break;
|
||||
}
|
||||
__receiveState = _STATE_SB;
|
||||
continue;
|
||||
/* TERMINAL-TYPE option (end)*/
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
// synchronized(__client) critical sections are to protect against
|
||||
// TelnetOutputStream writing through the telnet client at same time
|
||||
// as a processDo/Will/etc. command invoked from TelnetInputStream
|
||||
// tries to write. Returns true if buffer was previously empty.
|
||||
private boolean __processChar(int ch) throws InterruptedException
|
||||
{
|
||||
// Critical section because we're altering __bytesAvailable,
|
||||
// __queueTail, and the contents of _queue.
|
||||
boolean bufferWasEmpty;
|
||||
synchronized (__queue)
|
||||
{
|
||||
bufferWasEmpty = (__bytesAvailable == 0);
|
||||
while (__bytesAvailable >= __queue.length - 1)
|
||||
{
|
||||
// The queue is full. We need to wait before adding any more data to it. Hopefully the stream owner
|
||||
// will consume some data soon!
|
||||
if(__threaded)
|
||||
{
|
||||
__queue.notify();
|
||||
try
|
||||
{
|
||||
__queue.wait();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've been asked to add another character to the queue, but it is already full and there's
|
||||
// no other thread to drain it. This should not have happened!
|
||||
throw new IllegalStateException("Queue is full! Cannot process another character.");
|
||||
}
|
||||
}
|
||||
|
||||
// Need to do this in case we're not full, but block on a read
|
||||
if (__readIsWaiting && __threaded)
|
||||
{
|
||||
__queue.notify();
|
||||
}
|
||||
|
||||
__queue[__queueTail] = ch;
|
||||
++__bytesAvailable;
|
||||
|
||||
if (++__queueTail >= __queue.length) {
|
||||
__queueTail = 0;
|
||||
}
|
||||
}
|
||||
return bufferWasEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException
|
||||
{
|
||||
// Critical section because we're altering __bytesAvailable,
|
||||
// __queueHead, and the contents of _queue in addition to
|
||||
// testing value of __hasReachedEOF.
|
||||
synchronized (__queue)
|
||||
{
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (__ioException != null)
|
||||
{
|
||||
IOException e;
|
||||
e = __ioException;
|
||||
__ioException = null;
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (__bytesAvailable == 0)
|
||||
{
|
||||
// Return EOF if at end of file
|
||||
if (__hasReachedEOF) {
|
||||
return EOF;
|
||||
}
|
||||
|
||||
// Otherwise, we have to wait for queue to get something
|
||||
if(__threaded)
|
||||
{
|
||||
__queue.notify();
|
||||
try
|
||||
{
|
||||
__readIsWaiting = true;
|
||||
__queue.wait();
|
||||
__readIsWaiting = false;
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new InterruptedIOException("Fatal thread interruption during read.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//__alreadyread = false;
|
||||
__readIsWaiting = true;
|
||||
int ch;
|
||||
boolean mayBlock = true; // block on the first read only
|
||||
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((ch = __read(mayBlock)) < 0) { // must be EOF
|
||||
if(ch != WOULD_BLOCK) {
|
||||
return (ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedIOException e)
|
||||
{
|
||||
synchronized (__queue)
|
||||
{
|
||||
__ioException = e;
|
||||
__queue.notifyAll();
|
||||
try
|
||||
{
|
||||
__queue.wait(100);
|
||||
}
|
||||
catch (InterruptedException interrupted)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
return EOF;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if(ch != WOULD_BLOCK)
|
||||
{
|
||||
__processChar(ch);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
if (__isClosed) {
|
||||
return EOF;
|
||||
}
|
||||
}
|
||||
|
||||
// Reads should not block on subsequent iterations. Potentially, this could happen if the
|
||||
// remaining buffered socket data consists entirely of Telnet command sequence and no "user" data.
|
||||
mayBlock = false;
|
||||
|
||||
}
|
||||
// Continue reading as long as there is data available and the queue is not full.
|
||||
while (super.available() > 0 && __bytesAvailable < __queue.length - 1);
|
||||
|
||||
__readIsWaiting = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ch;
|
||||
|
||||
ch = __queue[__queueHead];
|
||||
|
||||
if (++__queueHead >= __queue.length) {
|
||||
__queueHead = 0;
|
||||
}
|
||||
|
||||
--__bytesAvailable;
|
||||
|
||||
// Need to explicitly notify() so available() works properly
|
||||
if(__bytesAvailable == 0 && __threaded) {
|
||||
__queue.notify();
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Reads the next number of bytes from the stream into an array and
|
||||
* returns the number of bytes read. Returns -1 if the end of the
|
||||
* stream has been reached.
|
||||
* <p>
|
||||
* @param buffer The byte array in which to store the data.
|
||||
* @return The number of bytes read. Returns -1 if the
|
||||
* end of the message has been reached.
|
||||
* @exception IOException If an error occurs in reading the underlying
|
||||
* stream.
|
||||
***/
|
||||
@Override
|
||||
public int read(byte buffer[]) throws IOException
|
||||
{
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Reads the next number of bytes from the stream into an array and returns
|
||||
* the number of bytes read. Returns -1 if the end of the
|
||||
* message has been reached. The characters are stored in the array
|
||||
* starting from the given offset and up to the length specified.
|
||||
* <p>
|
||||
* @param buffer The byte array in which to store the data.
|
||||
* @param offset The offset into the array at which to start storing data.
|
||||
* @param length The number of bytes to read.
|
||||
* @return The number of bytes read. Returns -1 if the
|
||||
* end of the stream has been reached.
|
||||
* @exception IOException If an error occurs while reading the underlying
|
||||
* stream.
|
||||
***/
|
||||
@Override
|
||||
public int read(byte buffer[], int offset, int length) throws IOException
|
||||
{
|
||||
int ch, off;
|
||||
|
||||
if (length < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Critical section because run() may change __bytesAvailable
|
||||
synchronized (__queue)
|
||||
{
|
||||
if (length > __bytesAvailable) {
|
||||
length = __bytesAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ch = read()) == EOF) {
|
||||
return EOF;
|
||||
}
|
||||
|
||||
off = offset;
|
||||
|
||||
do
|
||||
{
|
||||
buffer[offset++] = (byte)ch;
|
||||
}
|
||||
while (--length > 0 && (ch = read()) != EOF);
|
||||
|
||||
//__client._spyRead(buffer, off, offset - off);
|
||||
return (offset - off);
|
||||
}
|
||||
|
||||
|
||||
/*** Returns false. Mark is not supported. ***/
|
||||
@Override
|
||||
public boolean markSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException
|
||||
{
|
||||
// Critical section because run() may change __bytesAvailable
|
||||
synchronized (__queue)
|
||||
{
|
||||
if (__threaded) { // Must not call super.available when running threaded: NET-466
|
||||
return __bytesAvailable;
|
||||
} else {
|
||||
return __bytesAvailable + super.available();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Cannot be synchronized. Will cause deadlock if run() is blocked
|
||||
// in read because BufferedInputStream read() is synchronized.
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
// Completely disregard the fact thread may still be running.
|
||||
// We can't afford to block on this close by waiting for
|
||||
// thread to terminate because few if any JVM's will actually
|
||||
// interrupt a system read() from the interrupt() method.
|
||||
super.close();
|
||||
|
||||
synchronized (__queue)
|
||||
{
|
||||
__hasReachedEOF = true;
|
||||
__isClosed = true;
|
||||
|
||||
if (__thread != null && __thread.isAlive())
|
||||
{
|
||||
__thread.interrupt();
|
||||
}
|
||||
|
||||
__queue.notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
int ch;
|
||||
|
||||
try
|
||||
{
|
||||
_outerLoop:
|
||||
while (!__isClosed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((ch = __read(true)) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (InterruptedIOException e)
|
||||
{
|
||||
synchronized (__queue)
|
||||
{
|
||||
__ioException = e;
|
||||
__queue.notifyAll();
|
||||
try
|
||||
{
|
||||
__queue.wait(100);
|
||||
}
|
||||
catch (InterruptedException interrupted)
|
||||
{
|
||||
if (__isClosed) {
|
||||
break _outerLoop;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} catch(RuntimeException re) {
|
||||
// We treat any runtime exceptions as though the
|
||||
// stream has been closed. We close the
|
||||
// underlying stream just to be sure.
|
||||
super.close();
|
||||
// Breaking the loop has the effect of setting
|
||||
// the state to closed at the end of the method.
|
||||
break _outerLoop;
|
||||
}
|
||||
|
||||
// Process new character
|
||||
boolean notify = false;
|
||||
try
|
||||
{
|
||||
notify = __processChar(ch);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
if (__isClosed) {
|
||||
break _outerLoop;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify input listener if buffer was previously empty
|
||||
if (notify) {
|
||||
__client.notifyInputListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
synchronized (__queue)
|
||||
{
|
||||
__ioException = ioe;
|
||||
}
|
||||
__client.notifyInputListener();
|
||||
}
|
||||
|
||||
synchronized (__queue)
|
||||
{
|
||||
__isClosed = true; // Possibly redundant
|
||||
__hasReachedEOF = true;
|
||||
__queue.notify();
|
||||
}
|
||||
|
||||
__threaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emacs configuration
|
||||
* Local variables: **
|
||||
* mode: java **
|
||||
* c-basic-offset: 4 **
|
||||
* indent-tabs-mode: nil **
|
||||
* End: **
|
||||
*/
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* The TelnetNotificationHandler interface can be used to handle
|
||||
* notification of options negotiation commands received on a telnet
|
||||
* session.
|
||||
* <p>
|
||||
* The user can implement this interface and register a
|
||||
* TelnetNotificationHandler by using the registerNotificationHandler()
|
||||
* of TelnetClient to be notified of option negotiation commands.
|
||||
***/
|
||||
|
||||
public interface TelnetNotificationHandler
|
||||
{
|
||||
/***
|
||||
* The remote party sent a DO command.
|
||||
***/
|
||||
public static final int RECEIVED_DO = 1;
|
||||
|
||||
/***
|
||||
* The remote party sent a DONT command.
|
||||
***/
|
||||
public static final int RECEIVED_DONT = 2;
|
||||
|
||||
/***
|
||||
* The remote party sent a WILL command.
|
||||
***/
|
||||
public static final int RECEIVED_WILL = 3;
|
||||
|
||||
/***
|
||||
* The remote party sent a WONT command.
|
||||
***/
|
||||
public static final int RECEIVED_WONT = 4;
|
||||
|
||||
/***
|
||||
* The remote party sent a COMMAND.
|
||||
* @since 2.2
|
||||
***/
|
||||
public static final int RECEIVED_COMMAND = 5;
|
||||
|
||||
/***
|
||||
* Callback method called when TelnetClient receives an
|
||||
* command or option negotiation command
|
||||
*
|
||||
* @param negotiation_code - type of (negotiation) command received
|
||||
* (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT, RECEIVED_COMMAND)
|
||||
*
|
||||
* @param option_code - code of the option negotiated, or the command code itself (e.g. NOP).
|
||||
***/
|
||||
public void receivedNegotiation(int negotiation_code, int option_code);
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* The TelnetOption class cannot be instantiated and only serves as a
|
||||
* storehouse for telnet option constants.
|
||||
* <p>
|
||||
* Details regarding Telnet option specification can be found in RFC 855.
|
||||
*
|
||||
*
|
||||
* @see org.apache.commons.net.telnet.Telnet
|
||||
* @see org.apache.commons.net.telnet.TelnetClient
|
||||
***/
|
||||
|
||||
public class TelnetOption
|
||||
{
|
||||
/*** The maximum value an option code can have. This value is 255. ***/
|
||||
public static final int MAX_OPTION_VALUE = 255;
|
||||
|
||||
public static final int BINARY = 0;
|
||||
|
||||
public static final int ECHO = 1;
|
||||
|
||||
public static final int PREPARE_TO_RECONNECT = 2;
|
||||
|
||||
public static final int SUPPRESS_GO_AHEAD = 3;
|
||||
|
||||
public static final int APPROXIMATE_MESSAGE_SIZE = 4;
|
||||
|
||||
public static final int STATUS = 5;
|
||||
|
||||
public static final int TIMING_MARK = 6;
|
||||
|
||||
public static final int REMOTE_CONTROLLED_TRANSMISSION = 7;
|
||||
|
||||
public static final int NEGOTIATE_OUTPUT_LINE_WIDTH = 8;
|
||||
|
||||
public static final int NEGOTIATE_OUTPUT_PAGE_SIZE = 9;
|
||||
|
||||
public static final int NEGOTIATE_CARRIAGE_RETURN = 10;
|
||||
|
||||
public static final int NEGOTIATE_HORIZONTAL_TAB_STOP = 11;
|
||||
|
||||
public static final int NEGOTIATE_HORIZONTAL_TAB = 12;
|
||||
|
||||
public static final int NEGOTIATE_FORMFEED = 13;
|
||||
|
||||
public static final int NEGOTIATE_VERTICAL_TAB_STOP = 14;
|
||||
|
||||
public static final int NEGOTIATE_VERTICAL_TAB = 15;
|
||||
|
||||
public static final int NEGOTIATE_LINEFEED = 16;
|
||||
|
||||
public static final int EXTENDED_ASCII = 17;
|
||||
|
||||
public static final int FORCE_LOGOUT = 18;
|
||||
|
||||
public static final int BYTE_MACRO = 19;
|
||||
|
||||
public static final int DATA_ENTRY_TERMINAL = 20;
|
||||
|
||||
public static final int SUPDUP = 21;
|
||||
|
||||
public static final int SUPDUP_OUTPUT = 22;
|
||||
|
||||
public static final int SEND_LOCATION = 23;
|
||||
|
||||
public static final int TERMINAL_TYPE = 24;
|
||||
|
||||
public static final int END_OF_RECORD = 25;
|
||||
|
||||
public static final int TACACS_USER_IDENTIFICATION = 26;
|
||||
|
||||
public static final int OUTPUT_MARKING = 27;
|
||||
|
||||
public static final int TERMINAL_LOCATION_NUMBER = 28;
|
||||
|
||||
public static final int REGIME_3270 = 29;
|
||||
|
||||
public static final int X3_PAD = 30;
|
||||
|
||||
public static final int WINDOW_SIZE = 31;
|
||||
|
||||
public static final int TERMINAL_SPEED = 32;
|
||||
|
||||
public static final int REMOTE_FLOW_CONTROL = 33;
|
||||
|
||||
public static final int LINEMODE = 34;
|
||||
|
||||
public static final int X_DISPLAY_LOCATION = 35;
|
||||
|
||||
public static final int OLD_ENVIRONMENT_VARIABLES = 36;
|
||||
|
||||
public static final int AUTHENTICATION = 37;
|
||||
|
||||
public static final int ENCRYPTION = 38;
|
||||
|
||||
public static final int NEW_ENVIRONMENT_VARIABLES = 39;
|
||||
|
||||
public static final int EXTENDED_OPTIONS_LIST = 255;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final int __FIRST_OPTION = BINARY;
|
||||
private static final int __LAST_OPTION = EXTENDED_OPTIONS_LIST;
|
||||
|
||||
private static final String __optionString[] = {
|
||||
"BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", "NAME", "STATUS",
|
||||
"TIMING MARK", "RCTE", "NAOL", "NAOP", "NAOCRD", "NAOHTS", "NAOHTD",
|
||||
"NAOFFD", "NAOVTS", "NAOVTD", "NAOLFD", "EXTEND ASCII", "LOGOUT",
|
||||
"BYTE MACRO", "DATA ENTRY TERMINAL", "SUPDUP", "SUPDUP OUTPUT",
|
||||
"SEND LOCATION", "TERMINAL TYPE", "END OF RECORD", "TACACS UID",
|
||||
"OUTPUT MARKING", "TTYLOC", "3270 REGIME", "X.3 PAD", "NAWS", "TSPEED",
|
||||
"LFLOW", "LINEMODE", "XDISPLOC", "OLD-ENVIRON", "AUTHENTICATION",
|
||||
"ENCRYPT", "NEW-ENVIRON", "TN3270E", "XAUTH", "CHARSET", "RSP",
|
||||
"Com Port Control", "Suppress Local Echo", "Start TLS",
|
||||
"KERMIT", "SEND-URL", "FORWARD_X", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "TELOPT PRAGMA LOGON", "TELOPT SSPI LOGON",
|
||||
"TELOPT PRAGMA HEARTBEAT", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "", "", "",
|
||||
"Extended-Options-List"
|
||||
};
|
||||
|
||||
|
||||
/***
|
||||
* Returns the string representation of the telnet protocol option
|
||||
* corresponding to the given option code.
|
||||
*
|
||||
* @param code The option code of the telnet protocol option
|
||||
* @return The string representation of the telnet protocol option.
|
||||
***/
|
||||
public static final String getOption(int code)
|
||||
{
|
||||
if(__optionString[code].length() == 0)
|
||||
{
|
||||
return "UNASSIGNED";
|
||||
}
|
||||
else
|
||||
{
|
||||
return __optionString[code];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Determines if a given option code is valid. Returns true if valid,
|
||||
* false if not.
|
||||
*
|
||||
* @param code The option code to test.
|
||||
* @return True if the option code is valid, false if not.
|
||||
**/
|
||||
public static final boolean isValidOption(int code)
|
||||
{
|
||||
return (code <= __LAST_OPTION);
|
||||
}
|
||||
|
||||
// Cannot be instantiated
|
||||
private TelnetOption()
|
||||
{ }
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* The TelnetOptionHandler class is the base class to be used
|
||||
* for implementing handlers for telnet options.
|
||||
* <p>
|
||||
* TelnetOptionHandler implements basic option handling
|
||||
* functionality and defines abstract methods that must be
|
||||
* implemented to define subnegotiation behaviour.
|
||||
***/
|
||||
public abstract class TelnetOptionHandler
|
||||
{
|
||||
/***
|
||||
* Option code
|
||||
***/
|
||||
private int optionCode = -1;
|
||||
|
||||
/***
|
||||
* true if the option should be activated on the local side
|
||||
***/
|
||||
private boolean initialLocal = false;
|
||||
|
||||
/***
|
||||
* true if the option should be activated on the remote side
|
||||
***/
|
||||
private boolean initialRemote = false;
|
||||
|
||||
/***
|
||||
* true if the option should be accepted on the local side
|
||||
***/
|
||||
private boolean acceptLocal = false;
|
||||
|
||||
/***
|
||||
* true if the option should be accepted on the remote side
|
||||
***/
|
||||
private boolean acceptRemote = false;
|
||||
|
||||
/***
|
||||
* true if the option is active on the local side
|
||||
***/
|
||||
private boolean doFlag = false;
|
||||
|
||||
/***
|
||||
* true if the option is active on the remote side
|
||||
***/
|
||||
private boolean willFlag = false;
|
||||
|
||||
/***
|
||||
* Constructor for the TelnetOptionHandler. Allows defining desired
|
||||
* initial setting for local/remote activation of this option and
|
||||
* behaviour in case a local/remote activation request for this
|
||||
* option is received.
|
||||
* <p>
|
||||
* @param optcode - Option code.
|
||||
* @param initlocal - if set to true, a WILL is sent upon connection.
|
||||
* @param initremote - if set to true, a DO is sent upon connection.
|
||||
* @param acceptlocal - if set to true, any DO request is accepted.
|
||||
* @param acceptremote - if set to true, any WILL request is accepted.
|
||||
***/
|
||||
public TelnetOptionHandler(int optcode,
|
||||
boolean initlocal,
|
||||
boolean initremote,
|
||||
boolean acceptlocal,
|
||||
boolean acceptremote)
|
||||
{
|
||||
optionCode = optcode;
|
||||
initialLocal = initlocal;
|
||||
initialRemote = initremote;
|
||||
acceptLocal = acceptlocal;
|
||||
acceptRemote = acceptremote;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Returns the option code for this option.
|
||||
* <p>
|
||||
* @return Option code.
|
||||
***/
|
||||
public int getOptionCode()
|
||||
{
|
||||
return (optionCode);
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns a boolean indicating whether to accept a DO
|
||||
* request coming from the other end.
|
||||
* <p>
|
||||
* @return true if a DO request shall be accepted.
|
||||
***/
|
||||
public boolean getAcceptLocal()
|
||||
{
|
||||
return (acceptLocal);
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns a boolean indicating whether to accept a WILL
|
||||
* request coming from the other end.
|
||||
* <p>
|
||||
* @return true if a WILL request shall be accepted.
|
||||
***/
|
||||
public boolean getAcceptRemote()
|
||||
{
|
||||
return (acceptRemote);
|
||||
}
|
||||
|
||||
/***
|
||||
* Set behaviour of the option for DO requests coming from
|
||||
* the other end.
|
||||
* <p>
|
||||
* @param accept - if true, subsequent DO requests will be accepted.
|
||||
***/
|
||||
public void setAcceptLocal(boolean accept)
|
||||
{
|
||||
acceptLocal = accept;
|
||||
}
|
||||
|
||||
/***
|
||||
* Set behaviour of the option for WILL requests coming from
|
||||
* the other end.
|
||||
* <p>
|
||||
* @param accept - if true, subsequent WILL requests will be accepted.
|
||||
***/
|
||||
public void setAcceptRemote(boolean accept)
|
||||
{
|
||||
acceptRemote = accept;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns a boolean indicating whether to send a WILL request
|
||||
* to the other end upon connection.
|
||||
* <p>
|
||||
* @return true if a WILL request shall be sent upon connection.
|
||||
***/
|
||||
public boolean getInitLocal()
|
||||
{
|
||||
return (initialLocal);
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns a boolean indicating whether to send a DO request
|
||||
* to the other end upon connection.
|
||||
* <p>
|
||||
* @return true if a DO request shall be sent upon connection.
|
||||
***/
|
||||
public boolean getInitRemote()
|
||||
{
|
||||
return (initialRemote);
|
||||
}
|
||||
|
||||
/***
|
||||
* Tells this option whether to send a WILL request upon connection.
|
||||
* <p>
|
||||
* @param init - if true, a WILL request will be sent upon subsequent
|
||||
* connections.
|
||||
***/
|
||||
public void setInitLocal(boolean init)
|
||||
{
|
||||
initialLocal = init;
|
||||
}
|
||||
|
||||
/***
|
||||
* Tells this option whether to send a DO request upon connection.
|
||||
* <p>
|
||||
* @param init - if true, a DO request will be sent upon subsequent
|
||||
* connections.
|
||||
***/
|
||||
public void setInitRemote(boolean init)
|
||||
{
|
||||
initialRemote = init;
|
||||
}
|
||||
|
||||
/***
|
||||
* Method called upon reception of a subnegotiation for this option
|
||||
* coming from the other end.
|
||||
* <p>
|
||||
* This implementation returns null, and
|
||||
* must be overridden by the actual TelnetOptionHandler to specify
|
||||
* which response must be sent for the subnegotiation request.
|
||||
* <p>
|
||||
* @param suboptionData - the sequence received, without IAC SB & IAC SE
|
||||
* @param suboptionLength - the length of data in suboption_data
|
||||
* <p>
|
||||
* @return response to be sent to the subnegotiation sequence. TelnetClient
|
||||
* will add IAC SB & IAC SE. null means no response
|
||||
***/
|
||||
public int[] answerSubnegotiation(int suboptionData[], int suboptionLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* This method is invoked whenever this option is acknowledged active on
|
||||
* the local end (TelnetClient sent a WILL, remote side sent a DO).
|
||||
* The method is used to specify a subnegotiation sequence that will be
|
||||
* sent by TelnetClient when the option is activated.
|
||||
* <p>
|
||||
* This implementation returns null, and must be overriden by
|
||||
* the actual TelnetOptionHandler to specify
|
||||
* which response must be sent for the subnegotiation request.
|
||||
* @return subnegotiation sequence to be sent by TelnetClient. TelnetClient
|
||||
* will add IAC SB & IAC SE. null means no subnegotiation.
|
||||
***/
|
||||
public int[] startSubnegotiationLocal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* This method is invoked whenever this option is acknowledged active on
|
||||
* the remote end (TelnetClient sent a DO, remote side sent a WILL).
|
||||
* The method is used to specify a subnegotiation sequence that will be
|
||||
* sent by TelnetClient when the option is activated.
|
||||
* <p>
|
||||
* This implementation returns null, and must be overriden by
|
||||
* the actual TelnetOptionHandler to specify
|
||||
* which response must be sent for the subnegotiation request.
|
||||
* @return subnegotiation sequence to be sent by TelnetClient. TelnetClient
|
||||
* will add IAC SB & IAC SE. null means no subnegotiation.
|
||||
***/
|
||||
public int[] startSubnegotiationRemote() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns a boolean indicating whether a WILL request sent to the other
|
||||
* side has been acknowledged.
|
||||
* <p>
|
||||
* @return true if a WILL sent to the other side has been acknowledged.
|
||||
***/
|
||||
boolean getWill()
|
||||
{
|
||||
return willFlag;
|
||||
}
|
||||
|
||||
/***
|
||||
* Tells this option whether a WILL request sent to the other
|
||||
* side has been acknowledged (invoked by TelnetClient).
|
||||
* <p>
|
||||
* @param state - if true, a WILL request has been acknowledged.
|
||||
***/
|
||||
void setWill(boolean state)
|
||||
{
|
||||
willFlag = state;
|
||||
}
|
||||
|
||||
/***
|
||||
* Returns a boolean indicating whether a DO request sent to the other
|
||||
* side has been acknowledged.
|
||||
* <p>
|
||||
* @return true if a DO sent to the other side has been acknowledged.
|
||||
***/
|
||||
boolean getDo()
|
||||
{
|
||||
return doFlag;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Tells this option whether a DO request sent to the other
|
||||
* side has been acknowledged (invoked by TelnetClient).
|
||||
* <p>
|
||||
* @param state - if true, a DO request has been acknowledged.
|
||||
***/
|
||||
void setDo(boolean state)
|
||||
{
|
||||
doFlag = state;
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Wraps an output stream.
|
||||
* <p>
|
||||
* In binary mode, the only conversion is to double IAC.
|
||||
* <p>
|
||||
* In ASCII mode, if convertCRtoCRLF is true (currently always true), any CR is converted to CRLF.
|
||||
* IACs are doubled.
|
||||
* Also a bare LF is converted to CRLF and a bare CR is converted to CR\0
|
||||
* <p>
|
||||
***/
|
||||
|
||||
|
||||
final class TelnetOutputStream extends OutputStream
|
||||
{
|
||||
private final TelnetClient __client;
|
||||
// TODO there does not appear to be any way to change this value - should it be a ctor parameter?
|
||||
private final boolean __convertCRtoCRLF = true;
|
||||
private boolean __lastWasCR = false;
|
||||
|
||||
TelnetOutputStream(TelnetClient client)
|
||||
{
|
||||
__client = client;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Writes a byte to the stream.
|
||||
* <p>
|
||||
* @param ch The byte to write.
|
||||
* @exception IOException If an error occurs while writing to the underlying
|
||||
* stream.
|
||||
***/
|
||||
@Override
|
||||
public void write(int ch) throws IOException
|
||||
{
|
||||
|
||||
synchronized (__client)
|
||||
{
|
||||
ch &= 0xff;
|
||||
|
||||
if (__client._requestedWont(TelnetOption.BINARY)) // i.e. ASCII
|
||||
{
|
||||
if (__lastWasCR)
|
||||
{
|
||||
if (__convertCRtoCRLF)
|
||||
{
|
||||
__client._sendByte('\n');
|
||||
if (ch == '\n') // i.e. was CRLF anyway
|
||||
{
|
||||
__lastWasCR = false;
|
||||
return ;
|
||||
}
|
||||
} // __convertCRtoCRLF
|
||||
else if (ch != '\n')
|
||||
{
|
||||
__client._sendByte('\0'); // RFC854 requires CR NUL for bare CR
|
||||
}
|
||||
}
|
||||
|
||||
switch (ch)
|
||||
{
|
||||
case '\r':
|
||||
__client._sendByte('\r');
|
||||
__lastWasCR = true;
|
||||
break;
|
||||
case '\n':
|
||||
if (!__lastWasCR) { // convert LF to CRLF
|
||||
__client._sendByte('\r');
|
||||
}
|
||||
__client._sendByte(ch);
|
||||
__lastWasCR = false;
|
||||
break;
|
||||
case TelnetCommand.IAC:
|
||||
__client._sendByte(TelnetCommand.IAC);
|
||||
__client._sendByte(TelnetCommand.IAC);
|
||||
__lastWasCR = false;
|
||||
break;
|
||||
default:
|
||||
__client._sendByte(ch);
|
||||
__lastWasCR = false;
|
||||
break;
|
||||
}
|
||||
} // end ASCII
|
||||
else if (ch == TelnetCommand.IAC)
|
||||
{
|
||||
__client._sendByte(ch);
|
||||
__client._sendByte(TelnetCommand.IAC);
|
||||
} else {
|
||||
__client._sendByte(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Writes a byte array to the stream.
|
||||
* <p>
|
||||
* @param buffer The byte array to write.
|
||||
* @exception IOException If an error occurs while writing to the underlying
|
||||
* stream.
|
||||
***/
|
||||
@Override
|
||||
public void write(byte buffer[]) throws IOException
|
||||
{
|
||||
write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Writes a number of bytes from a byte array to the stream starting from
|
||||
* a given offset.
|
||||
* <p>
|
||||
* @param buffer The byte array to write.
|
||||
* @param offset The offset into the array at which to start copying data.
|
||||
* @param length The number of bytes to write.
|
||||
* @exception IOException If an error occurs while writing to the underlying
|
||||
* stream.
|
||||
***/
|
||||
@Override
|
||||
public void write(byte buffer[], int offset, int length) throws IOException
|
||||
{
|
||||
synchronized (__client)
|
||||
{
|
||||
while (length-- > 0) {
|
||||
write(buffer[offset++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*** Flushes the stream. ***/
|
||||
@Override
|
||||
public void flush() throws IOException
|
||||
{
|
||||
__client._flushOutputStream();
|
||||
}
|
||||
|
||||
/*** Closes the stream. ***/
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
__client._closeOutputStream();
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* Implements the telnet terminal type option RFC 1091.
|
||||
***/
|
||||
public class TerminalTypeOptionHandler extends TelnetOptionHandler
|
||||
{
|
||||
/***
|
||||
* Terminal type
|
||||
***/
|
||||
private final String termType;
|
||||
|
||||
/***
|
||||
* Terminal type option
|
||||
***/
|
||||
protected static final int TERMINAL_TYPE = 24;
|
||||
|
||||
/***
|
||||
* Send (for subnegotiation)
|
||||
***/
|
||||
protected static final int TERMINAL_TYPE_SEND = 1;
|
||||
|
||||
/***
|
||||
* Is (for subnegotiation)
|
||||
***/
|
||||
protected static final int TERMINAL_TYPE_IS = 0;
|
||||
|
||||
/***
|
||||
* Constructor for the TerminalTypeOptionHandler. Allows defining desired
|
||||
* initial setting for local/remote activation of this option and
|
||||
* behaviour in case a local/remote activation request for this
|
||||
* option is received.
|
||||
* <p>
|
||||
* @param termtype - terminal type that will be negotiated.
|
||||
* @param initlocal - if set to true, a WILL is sent upon connection.
|
||||
* @param initremote - if set to true, a DO is sent upon connection.
|
||||
* @param acceptlocal - if set to true, any DO request is accepted.
|
||||
* @param acceptremote - if set to true, any WILL request is accepted.
|
||||
***/
|
||||
public TerminalTypeOptionHandler(String termtype,
|
||||
boolean initlocal,
|
||||
boolean initremote,
|
||||
boolean acceptlocal,
|
||||
boolean acceptremote)
|
||||
{
|
||||
super(TelnetOption.TERMINAL_TYPE, initlocal, initremote,
|
||||
acceptlocal, acceptremote);
|
||||
termType = termtype;
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructor for the TerminalTypeOptionHandler. Initial and accept
|
||||
* behaviour flags are set to false
|
||||
* <p>
|
||||
* @param termtype - terminal type that will be negotiated.
|
||||
***/
|
||||
public TerminalTypeOptionHandler(String termtype)
|
||||
{
|
||||
super(TelnetOption.TERMINAL_TYPE, false, false, false, false);
|
||||
termType = termtype;
|
||||
}
|
||||
|
||||
/***
|
||||
* Implements the abstract method of TelnetOptionHandler.
|
||||
* <p>
|
||||
* @param suboptionData - the sequence received, without IAC SB & IAC SE
|
||||
* @param suboptionLength - the length of data in suboption_data
|
||||
* <p>
|
||||
* @return terminal type information
|
||||
***/
|
||||
@Override
|
||||
public int[] answerSubnegotiation(int suboptionData[], int suboptionLength)
|
||||
{
|
||||
if ((suboptionData != null) && (suboptionLength > 1)
|
||||
&& (termType != null))
|
||||
{
|
||||
if ((suboptionData[0] == TERMINAL_TYPE)
|
||||
&& (suboptionData[1] == TERMINAL_TYPE_SEND))
|
||||
{
|
||||
int response[] = new int[termType.length() + 2];
|
||||
|
||||
response[0] = TERMINAL_TYPE;
|
||||
response[1] = TERMINAL_TYPE_IS;
|
||||
|
||||
for (int ii = 0; ii < termType.length(); ii++)
|
||||
{
|
||||
response[ii + 2] = termType.charAt(ii);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.telnet;
|
||||
|
||||
/***
|
||||
* Implements the telnet window size option RFC 1073.
|
||||
* @version $Id: WindowSizeOptionHandler.java 1697293 2015-08-24 01:01:00Z sebb $
|
||||
* @since 2.0
|
||||
***/
|
||||
public class WindowSizeOptionHandler extends TelnetOptionHandler
|
||||
{
|
||||
/***
|
||||
* Horizontal Size
|
||||
***/
|
||||
private int m_nWidth = 80;
|
||||
|
||||
/***
|
||||
* Vertical Size
|
||||
***/
|
||||
private int m_nHeight = 24;
|
||||
|
||||
/***
|
||||
* Window size option
|
||||
***/
|
||||
protected static final int WINDOW_SIZE = 31;
|
||||
|
||||
/***
|
||||
* Constructor for the WindowSizeOptionHandler. Allows defining desired
|
||||
* initial setting for local/remote activation of this option and
|
||||
* behaviour in case a local/remote activation request for this
|
||||
* option is received.
|
||||
* <p>
|
||||
* @param nWidth - Window width.
|
||||
* @param nHeight - Window Height
|
||||
* @param initlocal - if set to true, a WILL is sent upon connection.
|
||||
* @param initremote - if set to true, a DO is sent upon connection.
|
||||
* @param acceptlocal - if set to true, any DO request is accepted.
|
||||
* @param acceptremote - if set to true, any WILL request is accepted.
|
||||
***/
|
||||
public WindowSizeOptionHandler(
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
boolean initlocal,
|
||||
boolean initremote,
|
||||
boolean acceptlocal,
|
||||
boolean acceptremote
|
||||
) {
|
||||
super (
|
||||
TelnetOption.WINDOW_SIZE,
|
||||
initlocal,
|
||||
initremote,
|
||||
acceptlocal,
|
||||
acceptremote
|
||||
);
|
||||
|
||||
m_nWidth = nWidth;
|
||||
m_nHeight = nHeight;
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructor for the WindowSizeOptionHandler. Initial and accept
|
||||
* behaviour flags are set to false
|
||||
* <p>
|
||||
* @param nWidth - Window width.
|
||||
* @param nHeight - Window Height
|
||||
***/
|
||||
public WindowSizeOptionHandler(
|
||||
int nWidth,
|
||||
int nHeight
|
||||
) {
|
||||
super (
|
||||
TelnetOption.WINDOW_SIZE,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
m_nWidth = nWidth;
|
||||
m_nHeight = nHeight;
|
||||
}
|
||||
|
||||
/***
|
||||
* Implements the abstract method of TelnetOptionHandler.
|
||||
* This will send the client Height and Width to the server.
|
||||
* <p>
|
||||
* @return array to send to remote system
|
||||
***/
|
||||
@Override
|
||||
public int[] startSubnegotiationLocal()
|
||||
{
|
||||
int nCompoundWindowSize = m_nWidth * 0x10000 + m_nHeight;
|
||||
int nResponseSize = 5;
|
||||
int nIndex;
|
||||
int nShift;
|
||||
int nTurnedOnBits;
|
||||
|
||||
if ((m_nWidth % 0x100) == 0xFF) {
|
||||
nResponseSize += 1;
|
||||
}
|
||||
|
||||
if ((m_nWidth / 0x100) == 0xFF) {
|
||||
nResponseSize += 1;
|
||||
}
|
||||
|
||||
if ((m_nHeight % 0x100) == 0xFF) {
|
||||
nResponseSize += 1;
|
||||
}
|
||||
|
||||
if ((m_nHeight / 0x100) == 0xFF) {
|
||||
nResponseSize += 1;
|
||||
}
|
||||
|
||||
//
|
||||
// allocate response array
|
||||
//
|
||||
int response[] = new int[nResponseSize];
|
||||
|
||||
//
|
||||
// Build response array.
|
||||
// ---------------------
|
||||
// 1. put option name.
|
||||
// 2. loop through Window size and fill the values,
|
||||
// 3. duplicate 'ff' if needed.
|
||||
//
|
||||
|
||||
response[0] = WINDOW_SIZE; // 1 //
|
||||
|
||||
for ( // 2 //
|
||||
nIndex=1, nShift = 24;
|
||||
nIndex < nResponseSize;
|
||||
nIndex++, nShift -=8
|
||||
) {
|
||||
nTurnedOnBits = 0xFF;
|
||||
nTurnedOnBits <<= nShift;
|
||||
response[nIndex] = (nCompoundWindowSize & nTurnedOnBits) >>> nShift;
|
||||
|
||||
if (response[nIndex] == 0xff) { // 3 //
|
||||
nIndex++;
|
||||
response[nIndex] = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.commons.net.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.EventListener;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
public class ListenerList implements Serializable, Iterable<EventListener>
|
||||
{
|
||||
private static final long serialVersionUID = -1934227607974228213L;
|
||||
|
||||
private final CopyOnWriteArrayList<EventListener> __listeners;
|
||||
|
||||
public ListenerList()
|
||||
{
|
||||
__listeners = new CopyOnWriteArrayList<EventListener>();
|
||||
}
|
||||
|
||||
public void addListener(EventListener listener)
|
||||
{
|
||||
__listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(EventListener listener)
|
||||
{
|
||||
__listeners.remove(listener);
|
||||
}
|
||||
|
||||
public int getListenerCount()
|
||||
{
|
||||
return __listeners.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link Iterator} for the {@link EventListener} instances.
|
||||
*
|
||||
* @return an {@link Iterator} for the {@link EventListener} instances
|
||||
* @since 2.0
|
||||
* TODO Check that this is a good defensive strategy
|
||||
*/
|
||||
@Override
|
||||
public Iterator<EventListener> iterator() {
|
||||
return __listeners.iterator();
|
||||
}
|
||||
|
||||
}
|
125
core/pom.xml
Normal file
125
core/pom.xml
Normal file
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-all</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>arthas-core</artifactId>
|
||||
<name>arthas-core</name>
|
||||
|
||||
<build>
|
||||
<finalName>arthas-core</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>attached</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.taobao.arthas.core.Arthas</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Created-By>core engine team, middleware group, alibaba inc.</Created-By>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
<!--<exclusions>-->
|
||||
<!--<exclusion>-->
|
||||
<!--<groupId>org.ow2.asm</groupId>-->
|
||||
<!--<artifactId>asm-tree</artifactId>-->
|
||||
<!--</exclusion>-->
|
||||
<!--</exclusions>-->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.middleware</groupId>
|
||||
<artifactId>termd-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.middleware</groupId>
|
||||
<artifactId>cli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taobao.text</groupId>
|
||||
<artifactId>text.ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fifesoft</groupId>
|
||||
<artifactId>rsyntaxtextarea</artifactId>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>org.codehaus.groovy</groupId>-->
|
||||
<!--<artifactId>groovy-all</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taobao.middleware</groupId>
|
||||
<artifactId>logger.api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ognl</groupId>
|
||||
<artifactId>ognl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.benf</groupId>
|
||||
<artifactId>cfr</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
93
core/src/main/java/com/taobao/arthas/core/Arthas.java
Normal file
93
core/src/main/java/com/taobao/arthas/core/Arthas.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
102
core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
Normal file
102
core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
Normal file
@ -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";
|
||||
|
||||
}
|
35
core/src/main/java/com/taobao/arthas/core/Option.java
Normal file
35
core/src/main/java/com/taobao/arthas/core/Option.java
Normal file
@ -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();
|
||||
|
||||
}
|
150
core/src/main/java/com/taobao/arthas/core/advisor/Advice.java
Normal file
150
core/src/main/java/com/taobao/arthas/core/advisor/Advice.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
399
core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java
Normal file
399
core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java
Normal file
@ -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时停止timer,resume时重启timer
|
||||
*/
|
||||
Handler<Void> stopHandler = new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
Handler<Void> restartHandler = new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
restart(process);
|
||||
}
|
||||
};
|
||||
process.suspendHandler(stopHandler);
|
||||
process.resumeHandler(restartHandler);
|
||||
process.endHandler(stopHandler);
|
||||
|
||||
// start the timer
|
||||
timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
|
||||
running = true;
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void restart(CommandProcess process) {
|
||||
if (timer == null) {
|
||||
Session session = process.session();
|
||||
timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);
|
||||
timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumOfExecutions() {
|
||||
return numOfExecutions;
|
||||
}
|
||||
|
||||
public boolean isBatchMode() {
|
||||
return batchMode;
|
||||
}
|
||||
|
||||
public long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
private static String beautifyName(String name) {
|
||||
return name.replace(' ', '_').toLowerCase();
|
||||
}
|
||||
|
||||
private static void addBufferPoolMemoryInfo(TableElement table) {
|
||||
try {
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class bufferPoolMXBeanClass = Class.forName("java.lang.management.BufferPoolMXBean");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanClass);
|
||||
for (BufferPoolMXBean mbean : bufferPoolMXBeans) {
|
||||
long used = mbean.getMemoryUsed();
|
||||
long total = mbean.getTotalCapacity();
|
||||
new MemoryEntry(mbean.getName(), used, total, Long.MIN_VALUE).addTableRow(table);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private static void addRuntimeInfo(TableElement table) {
|
||||
table.row("os.name", System.getProperty("os.name"));
|
||||
table.row("os.version", System.getProperty("os.version"));
|
||||
table.row("java.version", System.getProperty("java.version"));
|
||||
table.row("java.home", System.getProperty("java.home"));
|
||||
table.row("systemload.average",
|
||||
String.format("%.2f", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage()));
|
||||
table.row("processors", "" + Runtime.getRuntime().availableProcessors());
|
||||
table.row("uptime", "" + ManagementFactory.getRuntimeMXBean().getUptime() / 1000 + "s");
|
||||
}
|
||||
|
||||
private static void addMemoryInfo(TableElement table) {
|
||||
MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
|
||||
MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
|
||||
|
||||
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
|
||||
|
||||
new MemoryEntry("heap", heapMemoryUsage).addTableRow(table, Decoration.bold.bold());
|
||||
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
|
||||
if (MemoryType.HEAP.equals(poolMXBean.getType())) {
|
||||
MemoryUsage usage = poolMXBean.getUsage();
|
||||
String poolName = beautifyName(poolMXBean.getName());
|
||||
new MemoryEntry(poolName, usage).addTableRow(table);
|
||||
}
|
||||
}
|
||||
|
||||
new MemoryEntry("nonheap", nonHeapMemoryUsage).addTableRow(table, Decoration.bold.bold());
|
||||
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
|
||||
if (MemoryType.NON_HEAP.equals(poolMXBean.getType())) {
|
||||
MemoryUsage usage = poolMXBean.getUsage();
|
||||
String poolName = beautifyName(poolMXBean.getName());
|
||||
new MemoryEntry(poolName, usage).addTableRow(table);
|
||||
}
|
||||
}
|
||||
|
||||
addBufferPoolMemoryInfo(table);
|
||||
}
|
||||
|
||||
private static void addGcInfo(TableElement table) {
|
||||
List<GarbageCollectorMXBean> garbageCollectorMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
|
||||
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) {
|
||||
String name = garbageCollectorMXBean.getName();
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("gc." + beautifyName(name) + ".count",
|
||||
"" + garbageCollectorMXBean.getCollectionCount()));
|
||||
table.row("gc." + beautifyName(name) + ".time(ms)", "" + garbageCollectorMXBean.getCollectionTime());
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatBytes(long size) {
|
||||
int unit = 1;
|
||||
String unitStr = "B";
|
||||
if (size / 1024 > 0) {
|
||||
unit = 1024;
|
||||
unitStr = "K";
|
||||
} else if (size / 1024 / 1024 > 0) {
|
||||
unit = 1024 * 1024;
|
||||
unitStr = "M";
|
||||
}
|
||||
|
||||
return String.format("%d%s", size / unit, unitStr);
|
||||
}
|
||||
|
||||
private void addTomcatInfo(TableElement table) {
|
||||
|
||||
String threadPoolPath = "http://localhost:8006/connector/threadpool";
|
||||
String connectorStatPath = "http://localhost:8006/connector/stats";
|
||||
Response connectorStatResponse = NetUtils.request(connectorStatPath);
|
||||
if (connectorStatResponse.isSuccess()) {
|
||||
List<JSONObject> connectorStats = JSON.parseArray(connectorStatResponse.getContent(), JSONObject.class);
|
||||
for (JSONObject stat : connectorStats) {
|
||||
String name = stat.getString("name").replace("\"", "");
|
||||
long bytesReceived = stat.getLongValue("bytesReceived");
|
||||
long bytesSent = stat.getLongValue("bytesSent");
|
||||
long processingTime = stat.getLongValue("processingTime");
|
||||
long requestCount = stat.getLongValue("requestCount");
|
||||
long errorCount = stat.getLongValue("errorCount");
|
||||
|
||||
tomcatRequestCounter.update(requestCount);
|
||||
tomcatErrorCounter.update(errorCount);
|
||||
tomcatReceivedBytesCounter.update(bytesReceived);
|
||||
tomcatSentBytesCounter.update(bytesSent);
|
||||
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("connector", name));
|
||||
table.row("QPS", String.format("%.2f", tomcatRequestCounter.rate()));
|
||||
table.row("RT(ms)", String.format("%.2f", processingTime / (double) requestCount));
|
||||
table.row("error/s", String.format("%.2f", tomcatErrorCounter.rate()));
|
||||
table.row("received/s", formatBytes((long) tomcatReceivedBytesCounter.rate()));
|
||||
table.row("sent/s", formatBytes((long) tomcatSentBytesCounter.rate()));
|
||||
}
|
||||
}
|
||||
|
||||
Response threadPoolResponse = NetUtils.request(threadPoolPath);
|
||||
if (threadPoolResponse.isSuccess()) {
|
||||
List<JSONObject> threadPoolInfos = JSON.parseArray(threadPoolResponse.getContent(), JSONObject.class);
|
||||
for (JSONObject info : threadPoolInfos) {
|
||||
String name = info.getString("name").replace("\"", "");
|
||||
long busy = info.getLongValue("threadBusy");
|
||||
long total = info.getLongValue("threadCount");
|
||||
table.add(new RowElement().style(Decoration.bold.bold()).add("threadpool", name));
|
||||
table.row("busy", "" + busy);
|
||||
table.row("total", "" + total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String drawThreadInfo(int width, int height) {
|
||||
Map<String, Thread> threads = ThreadUtil.getThreads();
|
||||
return RenderUtil.render(threads.values().iterator(), new ThreadRenderer(), width, height);
|
||||
}
|
||||
|
||||
static String drawMemoryInfoAndGcInfo(int width, int height) {
|
||||
TableElement table = new TableElement(1, 1);
|
||||
|
||||
TableElement memoryInfoTable = new TableElement(3, 1, 1, 1, 1).rightCellPadding(1);
|
||||
memoryInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Memory",
|
||||
"used", "total", "max", "usage"));
|
||||
|
||||
addMemoryInfo(memoryInfoTable);
|
||||
|
||||
TableElement gcInfoTable = new TableElement(1, 1).rightCellPadding(1);
|
||||
gcInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("GC", ""));
|
||||
addGcInfo(gcInfoTable);
|
||||
|
||||
table.row(memoryInfoTable, gcInfoTable);
|
||||
return RenderUtil.render(table, width, height);
|
||||
}
|
||||
|
||||
String drawRuntineInfoAndTomcatInfo(int width, int height) {
|
||||
TableElement table = new TableElement(1, 1);
|
||||
|
||||
TableElement runtimeInfoTable = new TableElement(1, 1).rightCellPadding(1);
|
||||
runtimeInfoTable
|
||||
.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Runtime", ""));
|
||||
|
||||
addRuntimeInfo(runtimeInfoTable);
|
||||
|
||||
TableElement tomcatInfoTable = new TableElement(1, 1).rightCellPadding(1);
|
||||
|
||||
try {
|
||||
// 如果请求tomcat信息失败,则不显示tomcat信息
|
||||
if (NetUtils.request("http://localhost:8006").isSuccess()) {
|
||||
tomcatInfoTable
|
||||
.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Tomcat", ""));
|
||||
addTomcatInfo(tomcatInfoTable);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error(null, "get Tomcat Info error!", t);
|
||||
}
|
||||
|
||||
table.row(runtimeInfoTable, tomcatInfoTable);
|
||||
return RenderUtil.render(table, width, height);
|
||||
}
|
||||
|
||||
static class MemoryEntry {
|
||||
String name;
|
||||
long used;
|
||||
long total;
|
||||
long max;
|
||||
|
||||
int unit;
|
||||
String unitStr;
|
||||
|
||||
public MemoryEntry(String name, long used, long total, long max) {
|
||||
this.name = name;
|
||||
this.used = used;
|
||||
this.total = total;
|
||||
this.max = max;
|
||||
|
||||
unitStr = "K";
|
||||
unit = 1024;
|
||||
if (used / 1024 / 1024 > 0) {
|
||||
unitStr = "M";
|
||||
unit = 1024 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryEntry(String name, MemoryUsage usage) {
|
||||
this(name, usage.getUsed(), usage.getCommitted(), usage.getMax());
|
||||
}
|
||||
|
||||
private String format(long value) {
|
||||
String valueStr = "-";
|
||||
if (value == -1) {
|
||||
return "-1";
|
||||
}
|
||||
if (value != Long.MIN_VALUE) {
|
||||
valueStr = value / unit + unitStr;
|
||||
}
|
||||
return valueStr;
|
||||
}
|
||||
|
||||
public void addTableRow(TableElement table) {
|
||||
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
|
||||
|
||||
table.row(name, format(used), format(total), format(max), String.format("%.2f%%", usage));
|
||||
}
|
||||
|
||||
public void addTableRow(TableElement table, Style.Composite style) {
|
||||
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
|
||||
|
||||
table.add(new RowElement().style(style).add(name, format(used), format(total), format(max),
|
||||
String.format("%.2f%%", usage)));
|
||||
}
|
||||
}
|
||||
|
||||
private class DashboardTimerTask extends TimerTask {
|
||||
private CommandProcess process;
|
||||
|
||||
public DashboardTimerTask(CommandProcess process) {
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (count >= getNumOfExecutions()) {
|
||||
// stop the timer
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
process.write("Process ends after " + getNumOfExecutions() + " time(s).\n");
|
||||
process.end();
|
||||
return;
|
||||
}
|
||||
|
||||
int width = process.width();
|
||||
int height = process.height();
|
||||
|
||||
// 上半部分放thread top。下半部分再切分为田字格,其中上面两格放memory, gc的信息。下面两格放tomcat,
|
||||
// runtime的信息
|
||||
int totalHeight = height - 1;
|
||||
int threadTopHeight = totalHeight / 2;
|
||||
int lowerHalf = totalHeight - threadTopHeight;
|
||||
|
||||
int runtimeInfoHeight = lowerHalf / 2;
|
||||
int heapInfoHeight = lowerHalf - runtimeInfoHeight;
|
||||
|
||||
String threadInfo = drawThreadInfo(width, threadTopHeight);
|
||||
String memoryAndGc = drawMemoryInfoAndGcInfo(width, runtimeInfoHeight);
|
||||
String runTimeAndTomcat = drawRuntineInfoAndTomcatInfo(width, heapInfoHeight);
|
||||
|
||||
process.write(threadInfo + memoryAndGc + runTimeAndTomcat);
|
||||
|
||||
count++;
|
||||
process.times().incrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
@ -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时停止timer,resume时重启timer
|
||||
*/
|
||||
process.suspendHandler(new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
listener.destroy();
|
||||
}
|
||||
});
|
||||
process.resumeHandler(new Handler<Void>() {
|
||||
@Override
|
||||
public void handle(Void event) {
|
||||
listener.create();
|
||||
}
|
||||
});
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean completeExpress(Completion completion) {
|
||||
completion.complete(EMPTY);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user