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](resourse/xpocket.jpg) -### 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