diff --git a/.gitignore b/.gitignore
index a1c2a23..e9c69b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,2 @@
-# Compiled class file
-*.class
-
-# Log file
-*.log
-
-# BlueJ files
-*.ctxt
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
+nb-configuration.xml
+target
\ No newline at end of file
diff --git a/README.md b/README.md
index 61381ab..3a3dcae 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,30 @@
## xpocket-plugin-arthas

-### XPocket相关插件会陆续开源放出,敬请期待!
\ No newline at end of file
+### Arthas Plugin For XPocket
+#### 简介
+Java诊断工具Arthas的XPocket插件实现
+
+#### 操作指南
+使用
+``` shell
+use arthas@ALIBABA
+```
+进入Arthas插件域
+然后使用
+``` shell
+help
+```
+或者
+``` shell
+system.help arthas@ALIBABA
+```
+来获取详细帮助信息
+
+更多操作以及介绍请参考[官方介绍](https://arthas.aliyun.com/)
+
+[Arthas](https://github.com/alibaba/arthas)
+
+[XPocket主框架](https://github.com/perfma/xpocket)
+
+[插件下载](https://plugin.xpocket.perfma.com/plugin/52)
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..cc291a9
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+ com.perfmaxpocket
+ xpocket-plugin-arthas
+ 1.0.0-RELEASE
+ jar
+
+
+ com.perfma.xlab
+ xpocket-plugin-spi
+ 2.0.0-RELEASE
+ provided
+
+
+ commons-net
+ commons-net
+ 3.6
+
+
+ com.perfma.wrapped
+ com.sun.tools
+ 1.8.0_jdk8u275-b01_linux_x64
+ provided
+
+
+
+ UTF-8
+ 8
+ 8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.0.0
+
+
+ make-assembly-default
+ package
+
+ single
+
+
+
+ jar-with-dependencies
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/perfma/xpocket/plugin/arthas/ArthasCommandInvoker.java b/src/main/java/com/perfma/xpocket/plugin/arthas/ArthasCommandInvoker.java
new file mode 100644
index 0000000..77dae78
--- /dev/null
+++ b/src/main/java/com/perfma/xpocket/plugin/arthas/ArthasCommandInvoker.java
@@ -0,0 +1,80 @@
+package com.perfma.xpocket.plugin.arthas;
+
+import com.perfma.xlab.xpocket.spi.command.AbstractXPocketCommand;
+import com.perfma.xlab.xpocket.spi.command.CommandList;
+import com.perfma.xlab.xpocket.spi.XPocketPlugin;
+import com.perfma.xlab.xpocket.spi.process.XPocketProcess;
+
+/**
+ *
+ * @author gongyu
+ */
+@CommandList(names={"attach","keymap","sc","sm","jad","classloader","getstatic",
+ "monitor","stack","thread","trace","watch","tt","jvm","perfcounter","ognl","mc",
+ "redefine","dashboard","dump","heapdump","options","reset","version",
+ "session","sysprop","sysenv","vmoption","logger","profiler","stop","detach"},
+ usage={"attach [pid],attach a java process and start the Arthas server in localhost 3658,then connect it",
+ "keymap for Arthas keyboard shortcut",
+ "check the info for the classes loaded by JVM",
+ "check methods info for the loaded classes",
+ "decompile the specified loaded classes",
+ "check the inheritance structure, urls, class loading info for the specified class; using classloader to get the url of the resource e.g. java/lang/String.class",
+ "examine class’s static properties",
+ "monitor method execution statistics",
+ "display the stack trace for the specified class and method",
+ "show java thread information",
+ "trace the execution time of specified method invocation",
+ "display the input/output parameter, return object, and thrown exception of specified method invocation",
+ "time tunnel, record the arguments and returned value for the methods and replay",
+ "show JVM information",
+ "show JVM Perf Counter information",
+ "execute ognl expression",
+ "Memory compiler, compiles .java files into .class files in memory",
+ "load external *.class files and re-define it into JVM",
+ "dashboard for the system’s real-time data",
+ "dump the loaded classes in byte code to the specified location",
+ "dump java heap in hprof binary format, like jmap",
+ "check/set Arthas global options",
+ "reset all the enhanced classes. All enhanced classes will also be reset when Arthas server is closed by stop",
+ "print the version for the Arthas attached to the current Java process",
+ "display current session information",
+ "view/modify system properties",
+ "view system environment variables",
+ "view/modify the vm diagnostic options.",
+ "print the logger information, update the logger level",
+ "use async-profiler to generate flame graph",
+ "terminate the Arthas server, all Arthas sessions will be destroyed",
+ "disconnect from the Arthas server,but will not destroyed the other Arthas sessions"
+ })
+public class ArthasCommandInvoker extends AbstractXPocketCommand {
+
+ private ArthasPlugin plugin;
+
+ @Override
+ public boolean isPiped() {
+ return false;
+ }
+
+ @Override
+ public void init(XPocketPlugin plugin) {
+ this.plugin = (ArthasPlugin)plugin;
+ }
+
+ @Override
+ public boolean isAvailableNow(String cmd) {
+ return plugin.isAvaibleNow(cmd);
+ }
+
+ @Override
+ public void invoke(XPocketProcess process) throws Throwable {
+ plugin.invoke(process);
+ }
+
+ @Override
+ public String details(String cmd) {
+ return plugin.details(cmd);
+ }
+
+
+
+}
diff --git a/src/main/java/com/perfma/xpocket/plugin/arthas/ArthasPlugin.java b/src/main/java/com/perfma/xpocket/plugin/arthas/ArthasPlugin.java
new file mode 100644
index 0000000..74bec00
--- /dev/null
+++ b/src/main/java/com/perfma/xpocket/plugin/arthas/ArthasPlugin.java
@@ -0,0 +1,320 @@
+package com.perfma.xpocket.plugin.arthas;
+
+import com.perfma.xlab.xpocket.spi.AbstractXPocketPlugin;
+import com.perfma.xlab.xpocket.spi.context.SessionContext;
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+import org.apache.commons.net.telnet.TelnetClient;
+import org.apache.commons.net.telnet.TelnetNotificationHandler;
+import com.perfma.xlab.xpocket.spi.process.XPocketProcess;
+import com.perfma.xlab.xpocket.spi.process.XPocketProcessAction;
+import com.sun.tools.attach.AgentLoadException;
+
+/**
+ *
+ * @author gongyu
+ */
+public class ArthasPlugin extends AbstractXPocketPlugin implements Runnable, TelnetNotificationHandler {
+
+ private static final String LOGO = " _ ____ _____ _ _ _ ____ \n" +
+ " / \\ | _ \\ |_ _| | | | | / \\ / ___| \n" +
+ " / _ \\ | |_) | | | | |_| | / _ \\ \\___ \\ \n" +
+ " / ___ \\ | _ < | | | _ | / ___ \\ ___) |\n" +
+ " /_/ \\_\\ |_| \\_\\ |_| |_| |_| /_/ \\_\\ |____/ \n";
+
+ private static final String USER_HOME = System.getProperty("user.home");
+
+ private static final String path = USER_HOME + File.separator + ".xpocket"
+ + File.separator + ".arthas" + File.separator;
+
+ private static final String[] files = {"arthas-agent.jar","arthas-core.jar",
+ "arthas-spy.jar","logback.xml","async-profiler/libasyncProfiler-linux-arm.so",
+ "async-profiler/libasyncProfiler-linux-x64.so",
+ "async-profiler/libasyncProfiler-mac-x64.so"};
+
+ private static final byte CTRL_C = 0x03;
+
+ private final TelnetClient telnet = new TelnetClient();
+
+ private boolean attachStatus = false;
+
+ private int pid = -1;
+
+ private SessionContext context;
+
+ private Pattern pattern = Pattern.compile("\\[arthas@[0-9]*\\]\\$");
+
+ private XPocketProcess process;
+
+ @Override
+ public void init(XPocketProcess process) {
+ try {
+ File file = new File(path + "async-profiler");
+
+ if(file.exists()) {
+ return;
+ }
+
+ file.mkdirs();
+
+ for(String f : files) {
+ InputStream is = ArthasPlugin.class.getClassLoader().getResourceAsStream(".arthas/lib/" + f);
+ Path targetFile = new File(path + f).toPath();
+ Files.copy(is, targetFile);
+ is.close();
+ }
+
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public String details(String cmd) {
+ if("attach".equals(cmd)) {
+ return "DESCRIPTION : \n attach [pid] ";
+ }
+
+ return null;
+ }
+
+ public boolean isAvaibleNow(String cmd) {
+ if(attachStatus) {
+ return !"attach".equals(cmd);
+ } else {
+ return "attach".equals(cmd);
+ }
+ }
+
+ public void invoke(XPocketProcess process) throws Throwable {
+ this.process = process;
+ String cmd = process.getCmd();
+ String[] args = process.getArgs();
+
+ process.register(new XPocketProcessAction(){
+ @Override
+ public void userInput(String input) throws Throwable {}
+
+ @Override
+ public void interrupt() throws Throwable {
+ telnet.getOutputStream().write(CTRL_C);
+ telnet.getOutputStream().flush();
+ }
+
+ });
+
+
+ switch (cmd) {
+ case "attach":
+ if (!attachStatus) {
+ attach(args[0]);
+ } else {
+ process.end();
+ }
+ break;
+ case "detach":
+ telnet.disconnect();
+ attachOff();
+ process.end();
+ break;
+ case "stop":
+ if (attachStatus) {
+ telnet.getOutputStream().write(handleCmd(cmd, args));
+ telnet.getOutputStream().flush();
+ attachOff();
+ } else {
+ process.end();
+ }
+ break;
+ default:
+ if (attachStatus) {
+ telnet.getOutputStream().write(handleCmd(cmd, args));
+ telnet.getOutputStream().flush();
+ } else {
+ process.end();
+ }
+ }
+
+ }
+
+ private void startTelnet() throws IOException {
+ telnet.connect("127.0.0.1", 3658);
+ Thread reader = new Thread(this);
+ reader.start();
+ }
+
+ private void attach(String pid) {
+ VirtualMachineDescriptor virtualMachineDescriptor = null;
+ for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
+ if (pid.equals(descriptor.id())) {
+ virtualMachineDescriptor = descriptor;
+ break;
+ }
+ }
+ VirtualMachine virtualMachine = null;
+
+ try {
+ if (null == virtualMachineDescriptor) { // 使用 attach(String pid) 这种方式
+ virtualMachine = VirtualMachine.attach(pid);
+ } else {
+ virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
+ }
+
+ if(virtualMachine == null) {
+ process.output("Can not find Java Processor with : " + pid);
+ process.end();
+ }
+
+ String coreJar = path + "arthas-core.jar";
+ coreJar = URLEncoder.encode(coreJar, "UTF-8");
+
+ String agentJar = path + "arthas-agent.jar";
+
+ String loadOption = String.format("%s;;telnetPort=3658;httpPort=8563;"
+ + "ip=127.0.0.1;arthasAgent=%s;sessionTimeout=1800;"
+ + "arthasCore=%s;javaPid=%s;",coreJar,
+ URLEncoder.encode(agentJar, "UTF-8"),coreJar,pid);
+ virtualMachine.loadAgent(agentJar,loadOption);
+
+ startTelnet();
+ attachOn(Integer.parseInt(pid));
+
+ } catch (AgentLoadException | IOException ex) {
+ process.output("Please check your jdk version both used to run "
+ + "XPocket and target processor! They must be equal!");
+ process.end();
+ } catch (Throwable ex) {
+ process.output(ex.getClass().getName() + ":" + ex.getMessage());
+ process.end();
+ }
+ }
+
+ @Override
+ public void run() {
+ InputStream instr = telnet.getInputStream();
+
+ try {
+ int ret_read = 0, index = 0;
+ char[] line = new char[1024];
+
+ LOOP:
+ for (;;) {
+ ret_read = instr.read();
+
+ if(ret_read == -1) {
+ break;
+ }
+
+ if(process == null) {
+ continue;
+ }
+
+ switch (ret_read) {
+ case '\n':
+ String lineStr = new String(line, 0, index);
+ if (!lineStr.trim().equalsIgnoreCase(process.getCmd())) {
+ process.output(lineStr + "\n");
+ }
+ index = 0;
+ break;
+ case '$':
+ line = put(line,index++,(char) ret_read);
+ String flag = new String(line, 0, index);
+ if (pattern.matcher(flag).matches()) {
+ process.end();
+ process = null;
+ index = 0;
+ }
+ break;
+ default:
+ line = put(line,index++,(char) ret_read);
+ }
+
+ }
+ } catch (IOException e) {
+ process.output("Exception while reading socket:" + e.getMessage());
+ }
+
+ try {
+ telnet.disconnect();
+ attachOff();
+ } catch (IOException e) {
+ System.out.println("Exception while closing telnet:" + e.getMessage());
+ }
+ }
+
+ private char[] put(char[] b,int index,char content) {
+ char[] result = b;
+
+ if (b.length < index && b.length * 2 > index) {
+ result = new char[b.length * 2];
+ System.arraycopy(b, 0, result, 0, b.length);
+ } else if (b.length * 2 < index) {
+ result = new char[index + 1];
+ System.arraycopy(b, 0, result, 0, b.length);
+ }
+ result[index] = content;
+ return result;
+ }
+
+ @Override
+ public void receivedNegotiation(int negotiation_code, int option_code) {
+
+ }
+
+ private byte[] handleCmd(String cmd, String[] args) {
+ StringBuilder cmdStr = new StringBuilder(cmd).append(' ');
+
+ if (args != null) {
+ for (String arg : args) {
+ cmdStr.append(arg).append(' ');
+ }
+ }
+
+ cmdStr.append("\n");
+
+ return cmdStr.toString().getBytes();
+ }
+
+ @Override
+ public void destory() throws Throwable {
+ if(attachStatus) {
+ telnet.getOutputStream().write(handleCmd("stop", null));
+ telnet.getOutputStream().flush();
+ }
+ }
+
+ @Override
+ public void switchOn(SessionContext context) {
+ this.context = context;
+ context.setPid(pid);
+ }
+
+ private void attachOn(int pid) {
+ if(context != null) {
+ attachStatus = true;
+ this.pid = pid;
+ context.setPid(pid);
+ }
+ }
+
+ private void attachOff() {
+ if(context != null) {
+ attachStatus = false;
+ pid = -1;
+ context.setPid(pid);
+ }
+ }
+
+ @Override
+ public void printLogo(XPocketProcess process) {
+ process.output(LOGO);
+ }
+
+}
diff --git a/src/main/resources/.arthas/lib/arthas-agent.jar b/src/main/resources/.arthas/lib/arthas-agent.jar
new file mode 100644
index 0000000..5bac382
Binary files /dev/null and b/src/main/resources/.arthas/lib/arthas-agent.jar differ
diff --git a/src/main/resources/.arthas/lib/arthas-core.jar b/src/main/resources/.arthas/lib/arthas-core.jar
new file mode 100644
index 0000000..160fe81
Binary files /dev/null and b/src/main/resources/.arthas/lib/arthas-core.jar differ
diff --git a/src/main/resources/.arthas/lib/arthas-spy.jar b/src/main/resources/.arthas/lib/arthas-spy.jar
new file mode 100644
index 0000000..3ee863d
Binary files /dev/null and b/src/main/resources/.arthas/lib/arthas-spy.jar differ
diff --git a/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-linux-arm.so b/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-linux-arm.so
new file mode 100644
index 0000000..6606c41
Binary files /dev/null and b/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-linux-arm.so differ
diff --git a/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-linux-x64.so b/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-linux-x64.so
new file mode 100644
index 0000000..ff5912b
Binary files /dev/null and b/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-linux-x64.so differ
diff --git a/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-mac-x64.so b/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-mac-x64.so
new file mode 100644
index 0000000..b5f85cb
Binary files /dev/null and b/src/main/resources/.arthas/lib/async-profiler/libasyncProfiler-mac-x64.so differ
diff --git a/src/main/resources/.arthas/lib/logback.xml b/src/main/resources/.arthas/lib/logback.xml
new file mode 100644
index 0000000..32a3516
--- /dev/null
+++ b/src/main/resources/.arthas/lib/logback.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+ ${ARTHAS_LOG_FILE}
+
+ %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -%msg%n
+
+
+ ${ARTHAS_LOG_FILE}.%d{yyyy-MM-dd}.%i.log
+
+ 7
+ 1MB
+ 10MB
+
+
+
+
+
+ ${RESULT_LOG_FILE}
+
+ %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -%msg%n
+
+
+ ${RESULT_LOG_FILE}.%d{yyyy-MM-dd}.%i.log
+
+ 7
+ 1MB
+ 10MB
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/META-INF/xpocket.def b/src/main/resources/META-INF/xpocket.def
new file mode 100644
index 0000000..5a77e5b
--- /dev/null
+++ b/src/main/resources/META-INF/xpocket.def
@@ -0,0 +1,18 @@
+plugin-name=arthas
+plugin-namespace=alibaba
+plugin-description=Arthas is a Java Diagnostic tool open sourced by Alibaba.
+usage-tips=Arthas allows developers to troubleshoot production issues for Java applications without modifying code or restarting servers.
+plugin-version=1.0.0-RELEASE
+plugin-author=PerfMa
+plugin-project=https://github.com/perfma/xpocket-plugin-arthas
+tool-author=Alibaba
+tool-project=https://github.com/alibaba/arthas
+tool-version=3.4.1
+plugin-type=java
+plugin-dependency=
+jdk-version=8
+jvm=hotspot
+os=all
+sys-dependency=top,ps
+plugin-command-package=com.perfma.xpocket.plugin.arthas
+main-implementation=com.perfma.xpocket.plugin.arthas.ArthasPlugin
\ No newline at end of file