This commit is contained in:
hengyunabc 2018-08-31 11:49:48 +08:00
parent ebfb0c6f96
commit 3fbfab4288
235 changed files with 30452 additions and 0 deletions

2
README.md Normal file
View File

@ -0,0 +1,2 @@
在线诊断利器Arthas

23
TODO.md Normal file
View 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
View 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>

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

View File

@ -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;
}
// 优先从parentSystemClassLoader里加载系统类避免抛出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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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.
*/
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &amp; 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 &amp; 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 &amp; 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 &amp; 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;
}
}

View File

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

View File

@ -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 &amp; 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;
}
}

View File

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

View File

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

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

View 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";
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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("/", ".");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,414 @@
package com.taobao.arthas.core.command.monitor200;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.handlers.Handler;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.util.LogUtil;
import com.taobao.arthas.core.util.NetUtils;
import com.taobao.arthas.core.util.NetUtils.Response;
import com.taobao.arthas.core.util.ThreadUtil;
import com.taobao.arthas.core.util.metrics.SumRateCounter;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
import com.taobao.middleware.logger.Logger;
import com.taobao.text.Color;
import com.taobao.text.Decoration;
import com.taobao.text.Style;
import com.taobao.text.renderers.ThreadRenderer;
import com.taobao.text.ui.RowElement;
import com.taobao.text.ui.TableElement;
import com.taobao.text.util.RenderUtil;
import java.lang.management.BufferPoolMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.lang.management.MemoryUsage;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author hengyunabc 2015年11月19日 上午11:57:21
*/
@Name("dashboard")
@Summary("Overview of target jvm's thread, memory, gc, vm, tomcat info.")
@Description(Constants.EXAMPLE +
" dashboard\n" +
" dashboard -n 10\n" +
" dashboard -i 2000\n" +
Constants.WIKI + Constants.WIKI_HOME + "cmds/dashboard")
public class DashboardCommand extends AnnotatedCommand {
private static final Logger logger = LogUtil.getArthasLogger();
private SumRateCounter tomcatRequestCounter = new SumRateCounter();
private SumRateCounter tomcatErrorCounter = new SumRateCounter();
private SumRateCounter tomcatReceivedBytesCounter = new SumRateCounter();
private SumRateCounter tomcatSentBytesCounter = new SumRateCounter();
private int numOfExecutions = Integer.MAX_VALUE;
private boolean batchMode;
private long interval = 5000;
private volatile long count = 0;
private volatile Timer timer;
private Boolean running = false;
@Option(shortName = "n", longName = "number-of-execution")
@Description("The number of times this command will be executed.")
public void setNumOfExecutions(int numOfExecutions) {
this.numOfExecutions = numOfExecutions;
}
@Option(shortName = "b", longName = "batch")
@Description("Execute this command in batch mode.")
public void setBatchMode(boolean batchMode) {
this.batchMode = batchMode;
}
@Option(shortName = "i", longName = "interval")
@Description("The interval (in ms) between two executions, default is 5000 ms.")
public void setInterval(long interval) {
this.interval = interval;
}
@Override
public void process(final CommandProcess process) {
Session session = process.session();
timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);
// ctrl-C support
process.interruptHandler(new DashboardInterruptHandler(process, timer));
/*
* 通过handle回调在suspend和end时停止timerresume时重启timer
*/
Handler<Void> stopHandler = new Handler<Void>() {
@Override
public void handle(Void event) {
stop();
}
};
Handler<Void> restartHandler = new Handler<Void>() {
@Override
public void handle(Void event) {
restart(process);
}
};
process.suspendHandler(stopHandler);
process.resumeHandler(restartHandler);
process.endHandler(stopHandler);
// start the timer
timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
running = true;
}
public synchronized void stop() {
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
}
}
public synchronized void restart(CommandProcess process) {
if (timer == null) {
Session session = process.session();
timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);
timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
}
}
public int getNumOfExecutions() {
return numOfExecutions;
}
public boolean isBatchMode() {
return batchMode;
}
public long getInterval() {
return interval;
}
private static String beautifyName(String name) {
return name.replace(' ', '_').toLowerCase();
}
private static void addBufferPoolMemoryInfo(TableElement table) {
try {
@SuppressWarnings("rawtypes")
Class bufferPoolMXBeanClass = Class.forName("java.lang.management.BufferPoolMXBean");
@SuppressWarnings("unchecked")
List<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanClass);
for (BufferPoolMXBean mbean : bufferPoolMXBeans) {
long used = mbean.getMemoryUsed();
long total = mbean.getTotalCapacity();
new MemoryEntry(mbean.getName(), used, total, Long.MIN_VALUE).addTableRow(table);
}
} catch (ClassNotFoundException e) {
// ignore
}
}
private static void addRuntimeInfo(TableElement table) {
table.row("os.name", System.getProperty("os.name"));
table.row("os.version", System.getProperty("os.version"));
table.row("java.version", System.getProperty("java.version"));
table.row("java.home", System.getProperty("java.home"));
table.row("systemload.average",
String.format("%.2f", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage()));
table.row("processors", "" + Runtime.getRuntime().availableProcessors());
table.row("uptime", "" + ManagementFactory.getRuntimeMXBean().getUptime() / 1000 + "s");
}
private static void addMemoryInfo(TableElement table) {
MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
new MemoryEntry("heap", heapMemoryUsage).addTableRow(table, Decoration.bold.bold());
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
if (MemoryType.HEAP.equals(poolMXBean.getType())) {
MemoryUsage usage = poolMXBean.getUsage();
String poolName = beautifyName(poolMXBean.getName());
new MemoryEntry(poolName, usage).addTableRow(table);
}
}
new MemoryEntry("nonheap", nonHeapMemoryUsage).addTableRow(table, Decoration.bold.bold());
for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) {
if (MemoryType.NON_HEAP.equals(poolMXBean.getType())) {
MemoryUsage usage = poolMXBean.getUsage();
String poolName = beautifyName(poolMXBean.getName());
new MemoryEntry(poolName, usage).addTableRow(table);
}
}
addBufferPoolMemoryInfo(table);
}
private static void addGcInfo(TableElement table) {
List<GarbageCollectorMXBean> garbageCollectorMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) {
String name = garbageCollectorMXBean.getName();
table.add(new RowElement().style(Decoration.bold.bold()).add("gc." + beautifyName(name) + ".count",
"" + garbageCollectorMXBean.getCollectionCount()));
table.row("gc." + beautifyName(name) + ".time(ms)", "" + garbageCollectorMXBean.getCollectionTime());
}
}
private static String formatBytes(long size) {
int unit = 1;
String unitStr = "B";
if (size / 1024 > 0) {
unit = 1024;
unitStr = "K";
} else if (size / 1024 / 1024 > 0) {
unit = 1024 * 1024;
unitStr = "M";
}
return String.format("%d%s", size / unit, unitStr);
}
private void addTomcatInfo(TableElement table) {
String threadPoolPath = "http://localhost:8006/connector/threadpool";
String connectorStatPath = "http://localhost:8006/connector/stats";
Response connectorStatResponse = NetUtils.request(connectorStatPath);
if (connectorStatResponse.isSuccess()) {
List<JSONObject> connectorStats = JSON.parseArray(connectorStatResponse.getContent(), JSONObject.class);
for (JSONObject stat : connectorStats) {
String name = stat.getString("name").replace("\"", "");
long bytesReceived = stat.getLongValue("bytesReceived");
long bytesSent = stat.getLongValue("bytesSent");
long processingTime = stat.getLongValue("processingTime");
long requestCount = stat.getLongValue("requestCount");
long errorCount = stat.getLongValue("errorCount");
tomcatRequestCounter.update(requestCount);
tomcatErrorCounter.update(errorCount);
tomcatReceivedBytesCounter.update(bytesReceived);
tomcatSentBytesCounter.update(bytesSent);
table.add(new RowElement().style(Decoration.bold.bold()).add("connector", name));
table.row("QPS", String.format("%.2f", tomcatRequestCounter.rate()));
table.row("RT(ms)", String.format("%.2f", processingTime / (double) requestCount));
table.row("error/s", String.format("%.2f", tomcatErrorCounter.rate()));
table.row("received/s", formatBytes((long) tomcatReceivedBytesCounter.rate()));
table.row("sent/s", formatBytes((long) tomcatSentBytesCounter.rate()));
}
}
Response threadPoolResponse = NetUtils.request(threadPoolPath);
if (threadPoolResponse.isSuccess()) {
List<JSONObject> threadPoolInfos = JSON.parseArray(threadPoolResponse.getContent(), JSONObject.class);
for (JSONObject info : threadPoolInfos) {
String name = info.getString("name").replace("\"", "");
long busy = info.getLongValue("threadBusy");
long total = info.getLongValue("threadCount");
table.add(new RowElement().style(Decoration.bold.bold()).add("threadpool", name));
table.row("busy", "" + busy);
table.row("total", "" + total);
}
}
}
static String drawThreadInfo(int width, int height) {
Map<String, Thread> threads = ThreadUtil.getThreads();
return RenderUtil.render(threads.values().iterator(), new ThreadRenderer(), width, height);
}
static String drawMemoryInfoAndGcInfo(int width, int height) {
TableElement table = new TableElement(1, 1);
TableElement memoryInfoTable = new TableElement(3, 1, 1, 1, 1).rightCellPadding(1);
memoryInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Memory",
"used", "total", "max", "usage"));
addMemoryInfo(memoryInfoTable);
TableElement gcInfoTable = new TableElement(1, 1).rightCellPadding(1);
gcInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("GC", ""));
addGcInfo(gcInfoTable);
table.row(memoryInfoTable, gcInfoTable);
return RenderUtil.render(table, width, height);
}
String drawRuntineInfoAndTomcatInfo(int width, int height) {
TableElement table = new TableElement(1, 1);
TableElement runtimeInfoTable = new TableElement(1, 1).rightCellPadding(1);
runtimeInfoTable
.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Runtime", ""));
addRuntimeInfo(runtimeInfoTable);
TableElement tomcatInfoTable = new TableElement(1, 1).rightCellPadding(1);
try {
// 如果请求tomcat信息失败则不显示tomcat信息
if (NetUtils.request("http://localhost:8006").isSuccess()) {
tomcatInfoTable
.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Tomcat", ""));
addTomcatInfo(tomcatInfoTable);
}
} catch (Throwable t) {
logger.error(null, "get Tomcat Info error!", t);
}
table.row(runtimeInfoTable, tomcatInfoTable);
return RenderUtil.render(table, width, height);
}
static class MemoryEntry {
String name;
long used;
long total;
long max;
int unit;
String unitStr;
public MemoryEntry(String name, long used, long total, long max) {
this.name = name;
this.used = used;
this.total = total;
this.max = max;
unitStr = "K";
unit = 1024;
if (used / 1024 / 1024 > 0) {
unitStr = "M";
unit = 1024 * 1024;
}
}
public MemoryEntry(String name, MemoryUsage usage) {
this(name, usage.getUsed(), usage.getCommitted(), usage.getMax());
}
private String format(long value) {
String valueStr = "-";
if (value == -1) {
return "-1";
}
if (value != Long.MIN_VALUE) {
valueStr = value / unit + unitStr;
}
return valueStr;
}
public void addTableRow(TableElement table) {
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
table.row(name, format(used), format(total), format(max), String.format("%.2f%%", usage));
}
public void addTableRow(TableElement table, Style.Composite style) {
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
table.add(new RowElement().style(style).add(name, format(used), format(total), format(max),
String.format("%.2f%%", usage)));
}
}
private class DashboardTimerTask extends TimerTask {
private CommandProcess process;
public DashboardTimerTask(CommandProcess process) {
this.process = process;
}
@Override
public void run() {
if (count >= getNumOfExecutions()) {
// stop the timer
timer.cancel();
timer.purge();
process.write("Process ends after " + getNumOfExecutions() + " time(s).\n");
process.end();
return;
}
int width = process.width();
int height = process.height();
// 上半部分放thread top下半部分再切分为田字格其中上面两格放memory, gc的信息下面两格放tomcat,
// runtime的信息
int totalHeight = height - 1;
int threadTopHeight = totalHeight / 2;
int lowerHalf = totalHeight - threadTopHeight;
int runtimeInfoHeight = lowerHalf / 2;
int heapInfoHeight = lowerHalf - runtimeInfoHeight;
String threadInfo = drawThreadInfo(width, threadTopHeight);
String memoryAndGc = drawMemoryInfoAndGcInfo(width, runtimeInfoHeight);
String runTimeAndTomcat = drawRuntineInfoAndTomcatInfo(width, heapInfoHeight);
process.write(threadInfo + memoryAndGc + runTimeAndTomcat);
count++;
process.times().incrementAndGet();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,128 @@
package com.taobao.arthas.core.command.monitor200;
import com.taobao.arthas.core.advisor.AdviceListener;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.handlers.Handler;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.arthas.core.util.matcher.Matcher;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
/**
* 监控请求命令<br/>
* @author vlinux
*/
@Name("monitor")
@Summary("Monitor method execution statistics, e.g. total/success/failure count, average rt, fail rate, etc. ")
@Description("\nExamples:\n" +
" monitor org.apache.commons.lang.StringUtils isBlank\n" +
" monitor org.apache.commons.lang.StringUtils isBlank -c 5\n" +
" monitor -E org\\.apache\\.commons\\.lang\\.StringUtils isBlank\n" +
Constants.WIKI + Constants.WIKI_HOME + "cmds/monitor")
public class MonitorCommand extends EnhancerCommand {
private String classPattern;
private String methodPattern;
private int cycle = 60;
private boolean isRegEx = false;
private int numberOfLimit = 100;
@Argument(argName = "class-pattern", index = 0)
@Description("Path and classname of Pattern Matching")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Argument(argName = "method-pattern", index = 1)
@Description("Method of Pattern Matching")
public void setMethodPattern(String methodPattern) {
this.methodPattern = methodPattern;
}
@Option(shortName = "c", longName = "cycle")
@Description("The monitor interval (in seconds), 60 seconds by default")
public void setCycle(int cycle) {
this.cycle = cycle;
}
@Option(shortName = "E", longName = "regex")
@Description("Enable regular expression to match (wildcard matching by default)")
public void setRegEx(boolean regEx) {
isRegEx = regEx;
}
@Option(shortName = "n", longName = "limits")
@Description("Threshold of execution times")
public void setNumberOfLimit(int numberOfLimit) {
this.numberOfLimit = numberOfLimit;
}
public String getClassPattern() {
return classPattern;
}
public String getMethodPattern() {
return methodPattern;
}
public int getCycle() {
return cycle;
}
public boolean isRegEx() {
return isRegEx;
}
public int getNumberOfLimit() {
return numberOfLimit;
}
@Override
protected Matcher getClassNameMatcher() {
if (classNameMatcher == null) {
classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
}
return classNameMatcher;
}
@Override
protected Matcher getMethodNameMatcher() {
if (methodNameMatcher == null) {
methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
}
return methodNameMatcher;
}
@Override
protected AdviceListener getAdviceListener(CommandProcess process) {
final AdviceListener listener = new MonitorAdviceListener(this, process);
/*
* 通过handle回调在suspend时停止timerresume时重启timer
*/
process.suspendHandler(new Handler<Void>() {
@Override
public void handle(Void event) {
listener.destroy();
}
});
process.resumeHandler(new Handler<Void>() {
@Override
public void handle(Void event) {
listener.create();
}
});
return listener;
}
@Override
protected boolean completeExpress(Completion completion) {
completion.complete(EMPTY);
return true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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