diff --git a/boot/pom.xml b/boot/pom.xml new file mode 100644 index 000000000..12c9a241a --- /dev/null +++ b/boot/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + com.taobao.arthas + arthas-all + 3.0.5-SNAPSHOT + + arthas-boot + arthas-boot + + + + com.alibaba.middleware + cli + + + com.github.oshi + oshi-core + + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + + + arthas-boot + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + UTF-8 + true + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + single + + package + + + jar-with-dependencies + + + + com.taobao.arthas.boot.Bootstrap + + + core engine team, middleware group, alibaba inc. + + + + + + + + + + diff --git a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java new file mode 100644 index 000000000..332fd916a --- /dev/null +++ b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java @@ -0,0 +1,388 @@ +package com.taobao.arthas.boot; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import com.taobao.middleware.cli.CLI; +import com.taobao.middleware.cli.CommandLine; +import com.taobao.middleware.cli.UsageMessageFormatter; +import com.taobao.middleware.cli.annotations.Argument; +import com.taobao.middleware.cli.annotations.CLIConfigurator; +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; + +/** + * @author hengyunabc 2018-10-26 + * + */ +@Name("arthas-boot") +@Summary("Bootstrap Arthas") +@Description("EXAMPLES:\n" + " java -jar arthas-boot.jar \n" + + " java -jar arthas-boot.jar --target-ip 0.0.0.0 --telnet-port 9999' \n" + + " java -jar arthas-boot.jar -f batch.as 127.0.0.1\n") +public class Bootstrap { + private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class); + private static final int DEFAULT_TELNET_PORT = 3658; + private static final int DEFAULT_HTTP_PORT = 8563; + private static final String DEFAULT_TARGET_IP = "127.0.0.1"; + + private int pid = -1; + private String targetIp = DEFAULT_TARGET_IP; + private int telnetPort = DEFAULT_TELNET_PORT; + private int httpPort = DEFAULT_HTTP_PORT; + + private boolean verbose = false; + + /** + *
+     * The directory contains arthas-core.jar/arthas-client.jar/arthas-spy.jar.
+     * 1. When use-version is not empty, try to find arthas home under ~/.arthas/lib
+     * 2. Try set the directory where arthas-boot.jar is located to arhtas home
+     * 3. Try to download from maven repo
+     * 
+ */ + private String arthasHome; + + /** + * under ~/.arthas/lib + */ + private String useVersion; + + /** + * download from maven center repository by default + */ + private String repoMirror = "center"; + + private boolean useHttps = false; + + private boolean attachOnly = false; + + private String command; + private String batchFile; + + @Argument(argName = "pid", index = 0, required = false) + @Description("target pid") + public void setPid(int pid) { + this.pid = pid; + } + + @Option(longName = "target-ip") + @Description("The target jvm listen ip, default 127.0.0.1") + public void setTargetIp(String targetIp) { + this.targetIp = targetIp; + } + + @Option(longName = "telnet-port") + @Description("The target jvm listen telnet port, default 3658") + public void setTelnetPort(int telnetPort) { + this.telnetPort = telnetPort; + } + + @Option(longName = "http-port") + @Description("The target jvm listen http port, default 8563") + public void setHttpPort(int httpPort) { + this.httpPort = httpPort; + } + + @Option(longName = "arthas-home") + @Description("The arthas home") + public void setArthasHome(String arthasHome) { + this.arthasHome = arthasHome; + } + + @Option(longName = "use-version") + @Description("Use special version arthas") + public void setUseVersion(String useVersion) { + this.useVersion = useVersion; + } + + @Option(longName = "repo-mirror") + @Description("Use special maven repository mirror") + public void setRepoMirror(String repoMirror) { + this.repoMirror = repoMirror; + } + + @Option(longName = "use-https") + @Description("Use https to download") + public void setUseHttps(boolean useHttps) { + this.useHttps = useHttps; + } + + @Option(longName = "attach-only") + @Description("attach target process only, do not connect") + public void setAttachOnly(boolean attachOnly) { + this.attachOnly = attachOnly; + } + + @Option(shortName = "c", longName = "command") + @Description("Command to execute, multiple commands separated by ;") + public void setCommand(String command) { + this.command = command; + } + + @Option(shortName = "f", longName = "batch-file") + @Description("The batch file to execute") + public void setBatchFile(String batchFile) { + this.batchFile = batchFile; + } + + public boolean isVerbose() { + return verbose; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, + ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + Bootstrap bootStrap = new Bootstrap(); + + CLI cli = CLIConfigurator.define(Bootstrap.class); + CommandLine commandLine = cli.parse(Arrays.asList(args)); + + try { + CLIConfigurator.inject(commandLine, bootStrap); + } catch (Throwable e) { + e.printStackTrace(); + System.out.println(usage(cli)); + System.exit(1); + } + + // check telnet/http port + int telnetPortPid = -1; + int httpPortPid = -1; + if (bootStrap.getTelnetPort() > 0) { + telnetPortPid = SocketUtils.findTcpListenProcess(bootStrap.getTelnetPort()); + if (telnetPortPid > 0) { + logger.info("Process {} already using port {}", telnetPortPid, bootStrap.getTelnetPort()); + } + } + if (bootStrap.getHttpPort() > 0) { + httpPortPid = SocketUtils.findTcpListenProcess(bootStrap.getHttpPort()); + if (httpPortPid > 0) { + logger.info("Process {} already using port {}", httpPortPid, bootStrap.getHttpPort()); + } + } + + int pid = bootStrap.getPid(); + // select pid + if (pid < 0) { + pid = ProcessUtils.select(bootStrap.isVerbose()); + if (pid < 0) { + System.out.println("Please select an avaliable pid."); + System.exit(1); + } + } + + if (telnetPortPid > 0 && pid != telnetPortPid) { + logger.warn("Target process {} is not the process using port {}, you will connect to an unexpected process.", + pid, bootStrap.getTelnetPort()); + } + + if (httpPortPid > 0 && pid != httpPortPid) { + logger.warn("Target process {} is not the process using port {}, you will connect to an unexpected process.", + pid, bootStrap.getHttpPort()); + } + + // find arthas home + File arthasHomeDir = null; + if (bootStrap.getArthasHome() != null) { + verifyArthasHome(bootStrap.getArthasHome()); + arthasHomeDir = new File(bootStrap.getArthasHome()); + } + if (arthasHomeDir == null && bootStrap.getUseVersion() != null) { + // try to find from ~/.arthas/lib + File specialVersionDir = new File(System.getProperty("user.home"), ".arthas" + File.separator + "lib" + + File.separator + bootStrap.getUseVersion() + File.separator + "arthas"); + verifyArthasHome(specialVersionDir.getAbsolutePath()); + arthasHomeDir = specialVersionDir; + } + + // Try set the directory where arthas-boot.jar is located to arhtas home + if (arthasHomeDir == null) { + CodeSource codeSource = Bootstrap.class.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + String bootstrap = codeSource.getLocation().getFile(); + try { + verifyArthasHome(new File(bootstrap).getParent()); + arthasHomeDir = new File(bootstrap).getParentFile(); + } catch (Exception e) { + // ignore + } + + } + } + + // try to download from remote server + if (arthasHomeDir == null) { + File arthasLibDir = new File( + System.getProperty("user.home") + File.separator + ".arthas" + File.separator + "lib"); + arthasLibDir.mkdirs(); + + List versionList = listNames(arthasLibDir); + + if (versionList.isEmpty()) { + // try to download arthas from remote server. + DownloadUtils.downArthasPackaging(bootStrap.getRepoMirror(), bootStrap.isUseHttps(), + arthasLibDir.getAbsolutePath()); + versionList = listNames(arthasLibDir); + } + + Collections.sort(versionList); + + // get the latest version + arthasHomeDir = new File(arthasLibDir, versionList.get(versionList.size() - 1) + File.separator + "arthas"); + } + + verifyArthasHome(arthasHomeDir.getAbsolutePath()); + + logger.info("arthas home: " + arthasHomeDir); + + // start arthas-core.jar + List attachArgs = new ArrayList(); + attachArgs.add("-jar"); + attachArgs.add(new File(arthasHomeDir, "arthas-core.jar").getAbsolutePath()); + attachArgs.add("-pid"); + attachArgs.add("" + pid); + attachArgs.add("-target-ip"); + attachArgs.add(bootStrap.getTargetIp()); + attachArgs.add("-telnet-port"); + attachArgs.add("" + bootStrap.getTelnetPort()); + attachArgs.add("-http-port"); + attachArgs.add("" + bootStrap.getHttpPort()); + attachArgs.add("-core"); + attachArgs.add(new File(arthasHomeDir, "arthas-core.jar").getAbsolutePath()); + attachArgs.add("-agent"); + attachArgs.add(new File(arthasHomeDir, "arthas-agent.jar").getAbsolutePath()); + + logger.info("Try to attach process " + pid); + logger.debug("Start arthas-core.jar args: " + attachArgs); + ProcessUtils.startArthasCore(pid, attachArgs); + + logger.info("Attach process {} success.", pid); + + // start java telnet client + // find arthas-client.jar + URLClassLoader classLoader = new URLClassLoader( + new URL[] { new File(arthasHomeDir, "arthas-client.jar").toURI().toURL() }); + Class telnetConsoleClas = classLoader.loadClass("com.taobao.arthas.client.TelnetConsole"); + Method mainMethod = telnetConsoleClas.getMethod("main", String[].class); + List telnetArgs = new ArrayList(); + + if (bootStrap.getCommand() != null) { + telnetArgs.add("-c"); + telnetArgs.add(bootStrap.getCommand()); + } + if (bootStrap.getBatchFile() != null) { + telnetArgs.add("-f"); + telnetArgs.add(bootStrap.getBatchFile()); + } + + // telnet port ,ip + telnetArgs.add(bootStrap.getTargetIp()); + telnetArgs.add("" + bootStrap.getTelnetPort()); + + logger.debug("Start arthas-client.jar args: " + telnetArgs); + mainMethod.invoke(null, new Object[] { telnetArgs.toArray(new String[0]) }); + } + + private static List listNames(File dir) { + List names = new ArrayList(); + for (File file : dir.listFiles()) { + String name = file.getName(); + if (name.startsWith(".") || file.isFile()) { + continue; + } + names.add(name); + } + return names; + } + + private static void verifyArthasHome(String arthasHome) { + File home = new File(arthasHome); + if (home.isDirectory()) { + String fileList[] = { "arthas-core.jar", "arthas-agent.jar", "arthas-spy.jar" }; + + for (String fileName : fileList) { + if (!new File(home, fileName).exists()) { + throw new IllegalArgumentException( + fileName + " do not exist, arthas home: " + home.getAbsolutePath()); + } + } + return; + } + + throw new IllegalArgumentException("illegal arthas home: " + home.getAbsolutePath()); + } + + private static String usage(CLI cli) { + StringBuilder usageStringBuilder = new StringBuilder(); + UsageMessageFormatter usageMessageFormatter = new UsageMessageFormatter(); + usageMessageFormatter.setOptionComparator(null); + cli.usage(usageStringBuilder, usageMessageFormatter); + return usageStringBuilder.toString(); + } + + public String getArthasHome() { + return arthasHome; + } + + public String getUseVersion() { + return useVersion; + } + + public String getRepoMirror() { + return repoMirror; + } + + public boolean isUseHttps() { + return useHttps; + } + + public String getTargetIp() { + return targetIp; + } + + public int getTelnetPort() { + return telnetPort; + } + + public int getHttpPort() { + return httpPort; + } + + public String getCommand() { + return command; + } + + public String getBatchFile() { + return batchFile; + } + + public boolean isAttachOnly() { + return attachOnly; + } + + public int getPid() { + return pid; + } +} diff --git a/boot/src/main/java/com/taobao/arthas/boot/DownloadUtils.java b/boot/src/main/java/com/taobao/arthas/boot/DownloadUtils.java new file mode 100644 index 000000000..54a93faa0 --- /dev/null +++ b/boot/src/main/java/com/taobao/arthas/boot/DownloadUtils.java @@ -0,0 +1,109 @@ +package com.taobao.arthas.boot; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * + * @author hengyunabc 2018-11-06 + * + */ +public class DownloadUtils { + + private static final Logger logger = LoggerFactory.getLogger(DownloadUtils.class); + + private static final String MAVEN_METADATA_URL = "${REPO}/com/taobao/arthas/arthas-packaging/maven-metadata.xml"; + private static final String REMOTE_DOWNLOAD_URL = "${REPO}/com/taobao/arthas/arthas-packaging/${VERSION}/arthas-packaging-${VERSION}-bin.zip"; + + /** + * Read release version from maven-metadata.xml + * + * @param mavenMetaDataUrl + * @return + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + public static String readMavenReleaseVersion(String mavenMetaDataUrl) + throws ParserConfigurationException, SAXException, IOException { + InputStream inputStream = new URL(mavenMetaDataUrl).openStream(); + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document document = dBuilder.parse(inputStream); + + NodeList nodeList = document.getDocumentElement().getElementsByTagName("release"); + + return nodeList.item(0).getTextContent(); + } finally { + IOUtils.close(inputStream); + } + } + + public static void downArthasPackaging(String repoMirror, boolean https, String savePath) + throws ParserConfigurationException, SAXException, IOException { + repoMirror = repoMirror.trim(); + String repoUrl = ""; + if (repoMirror.equals("center")) { + repoUrl = "http://repo1.maven.org/maven2"; + } else if (repoMirror.equals("aliyun")) { + repoUrl = "http://maven.aliyun.com/repository/public"; + } else { + repoUrl = repoMirror; + } + if (repoUrl.endsWith("/")) { + repoUrl = repoUrl.substring(0, repoUrl.length() - 1); + } + + if (https && repoUrl.startsWith("http")) { + repoUrl = "https" + repoUrl.substring("http".length(), repoUrl.length()); + } + + String arthasVersion = readMavenReleaseVersion(MAVEN_METADATA_URL.replace("${REPO}", repoUrl)); + + File unzipDir = new File(savePath, arthasVersion + File.separator + "arthas"); + + File tempFile = File.createTempFile("arthas", "arthas"); + + String remoteDownloadUrl = REMOTE_DOWNLOAD_URL.replace("${REPO}", repoUrl).replace("${VERSION}", arthasVersion); + logger.info("Start download arthas from remote server: " + remoteDownloadUrl); + saveUrl(tempFile.getAbsolutePath(), remoteDownloadUrl); + + IOUtils.unzip(tempFile.getAbsolutePath(), unzipDir.getAbsolutePath()); + } + + public static void saveUrl(final String filename, final String urlString) + throws MalformedURLException, IOException { + BufferedInputStream in = null; + FileOutputStream fout = null; + try { + in = new BufferedInputStream(new URL(urlString).openStream()); + fout = new FileOutputStream(filename); + + final byte data[] = new byte[1024]; + int count; + while ((count = in.read(data, 0, 1024)) != -1) { + fout.write(data, 0, count); + } + } finally { + IOUtils.close(in); + IOUtils.close(fout); + } + } + +} diff --git a/boot/src/main/java/com/taobao/arthas/boot/IOUtils.java b/boot/src/main/java/com/taobao/arthas/boot/IOUtils.java new file mode 100644 index 000000000..f494937ef --- /dev/null +++ b/boot/src/main/java/com/taobao/arthas/boot/IOUtils.java @@ -0,0 +1,128 @@ +package com.taobao.arthas.boot; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * + * @author hengyunabc 2018-11-06 + * + */ +public class IOUtils { + + public static void copy(InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + } + + + public static IOException close(InputStream input) { + return close(input); + } + + public static IOException close(OutputStream output) { + return close(output); + } + + public static IOException close(final Reader input) { + return close(input); + } + + public static IOException close(final Writer output) { + return close(output); + } + + public static IOException close(final Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (final IOException ioe) { + return ioe; + } + return null; + } + + // support jdk6 + public static IOException close(final ZipFile zip) { + try { + if (zip != null) { + zip.close(); + } + } catch (final IOException ioe) { + return ioe; + } + return null; + } + + public static void unzip(String zipFile, String extractFolder) throws IOException { + File file = new File(zipFile); + ZipFile zip = null; + try { + int BUFFER = 2048; + + zip = new ZipFile(file); + String newPath = extractFolder; + + new File(newPath).mkdir(); + Enumeration zipFileEntries = zip.entries(); + + // Process each entry + while (zipFileEntries.hasMoreElements()) { + // grab a zip file entry + ZipEntry entry = (ZipEntry) zipFileEntries.nextElement(); + String currentEntry = entry.getName(); + + File destFile = new File(newPath, currentEntry); + // destFile = new File(newPath, destFile.getName()); + File destinationParent = destFile.getParentFile(); + + // create the parent directory structure if needed + destinationParent.mkdirs(); + + if (!entry.isDirectory()) { + BufferedInputStream is = null; + BufferedOutputStream dest = null; + try { + is = new BufferedInputStream(zip.getInputStream(entry)); + int currentByte; + // establish buffer for writing file + byte data[] = new byte[BUFFER]; + + // write the current file to disk + FileOutputStream fos = new FileOutputStream(destFile); + dest = new BufferedOutputStream(fos, BUFFER); + + // read and write until last byte is encountered + while ((currentByte = is.read(data, 0, BUFFER)) != -1) { + dest.write(data, 0, currentByte); + } + dest.flush(); + } finally { + close(dest); + close(is); + } + + } + + } + } finally { + close(zip); + } + + } +} diff --git a/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java b/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java new file mode 100644 index 000000000..9a81ec1da --- /dev/null +++ b/boot/src/main/java/com/taobao/arthas/boot/ProcessUtils.java @@ -0,0 +1,232 @@ +package com.taobao.arthas.boot; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import oshi.SystemInfo; +import oshi.software.os.OSProcess; +import oshi.software.os.OperatingSystem; + +/** + * + * @author hengyunabc 2018-11-06 + * + */ +public class ProcessUtils { + private static final Logger logger = LoggerFactory.getLogger(ProcessUtils.class); + public static int select(boolean v) { + Map processMap = listProcessByJps(v); + if(processMap.isEmpty()) { + processMap = listProcessByOshi(); + } + + if(processMap.isEmpty()) { + System.out.println("Can not find java process."); + return -1; + } + + //print list + int count = 1; + for(String process : processMap.values()) { + if(count == 1) { + System.out.println("* [" + count + "]: " + process); + }else { + System.out.println(" [" + count + "]: " + process); + } + count++; + } + + // read choice + String line = new Scanner(System.in).nextLine(); + if(line.trim().isEmpty()) { + // get the first process id + return processMap.keySet().iterator().next(); + } + + int choice = new Scanner(line).nextInt(); + + if(choice <= 0 || choice > processMap.size()) { + return -1; + } + + Iterator idIter = processMap.keySet().iterator(); + for(int i = 1; i <= choice; ++i) { + if(i == choice) { + return idIter.next(); + } + idIter.next(); + } + + return -1; + } + + private static Map listProcessByOshi() { + SystemInfo info = new SystemInfo(); + OperatingSystem operatingSystem = info.getOperatingSystem(); + Map result = new LinkedHashMap(); + OSProcess[] processes = operatingSystem.getProcesses(-1, null); + for (OSProcess p : processes) { + System.err.println(p); + System.err.println(p.getPath()); + String path = p.getPath(); + String name = new File(path).getName(); + if (name.equals("java") || name.equals("java.exe")) { + result.put(p.getProcessID(), p.getProcessID() + " " + path); + } + } + return result; + } + + private static Map listProcessByJps(boolean v) { + Map result = new LinkedHashMap(); + + File jps = findJps(); + if(jps == null) { + return result; + } + + String[] command = null; + if (v) { + command = new String[] { jps.getAbsolutePath(), "-v" }; + } else { + command = new String[] { jps.getAbsolutePath() }; + } + + ProcessBuilder pb = new ProcessBuilder(command); + try { + Process proc = pb.start(); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); + + // read the output from the command + String line = null; + while ((line = stdInput.readLine()) != null) { + int pid = new Scanner(line).nextInt(); + result.put(pid, line); + } + } catch (Throwable e) { + // ignore + } + + return result; + + } + + public static void startArthasCore(int targetPid, List attachArgs) { + // find java/java.exe, then try to find tools.jar + SystemInfo info = new SystemInfo(); + OperatingSystem operatingSystem = info.getOperatingSystem(); + OSProcess processe = operatingSystem.getProcess(targetPid); + if(processe == null) { + throw new IllegalArgumentException("process do not exist! pid: " + targetPid); + } + + String path = processe.getPath(); + + // some app like eclipse process path is not java/java.exe + if(!path.endsWith("java") && path.endsWith("java.exe")) { + OSProcess myselfProcess = operatingSystem.getProcess(operatingSystem.getProcessId()); + path = myselfProcess.getPath(); + logger.warn("The target process is not an normal java process. try to start by using current java."); + } + + File javaBinDir = new File(path).getParentFile(); + + // current/jre/bin/java + // current/bin/java + // current/lib/tools.jar + // after jdk9, there is no tools.jar + File toolsJar = new File(javaBinDir , "../lib/tools.jar"); + if(!toolsJar.exists()) { + // maybe jre + toolsJar = new File(javaBinDir , "../../lib/tools.jar"); + } + + List command = new ArrayList(); + command.add(path); + + if(toolsJar.exists()) { + command.add("-Xbootclasspath/a:" + toolsJar.getAbsolutePath()); + } + + command.addAll(attachArgs); +// "${JAVA_HOME}"/bin/java \ +// ${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" + + ProcessBuilder pb = new ProcessBuilder(command); + try { + final Process proc = pb.start(); + Thread redirectStdout = new Thread(new Runnable() { + @Override + public void run() { + InputStream inputStream = proc.getInputStream(); + try { + IOUtils.copy(inputStream, System.out); + } catch (IOException e) { + IOUtils.close(inputStream); + } + + } + }); + + Thread redirectStderr = new Thread(new Runnable() { + @Override + public void run() { + InputStream inputStream = proc.getErrorStream(); + try { + IOUtils.copy(inputStream, System.err); + } catch (IOException e) { + IOUtils.close(inputStream); + } + + } + }); + redirectStdout.start(); + redirectStderr.start(); + redirectStdout.join(); + redirectStderr.join(); + + int exitValue = proc.exitValue(); + if(exitValue != 0) { + logger.error("attach fail, targetPid: " + targetPid); + System.exit(1); + } + } catch (Throwable e) { + // ignore + } + + } + + private static File findJps() { + String javaHome = System.getProperty("java.home"); + String[] paths = { "bin/jps", "bin/jps.exe", "../bin/jps", "../bin/jps.exe" }; + + for (String path : paths) { + File jpsFile = new File(javaHome, path); + if (jpsFile.exists()) { + return jpsFile; + } + } + + return null; + } + +} diff --git a/boot/src/main/java/com/taobao/arthas/boot/SocketUtils.java b/boot/src/main/java/com/taobao/arthas/boot/SocketUtils.java new file mode 100644 index 000000000..0703a5c92 --- /dev/null +++ b/boot/src/main/java/com/taobao/arthas/boot/SocketUtils.java @@ -0,0 +1,63 @@ +package com.taobao.arthas.boot; + +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.List; + +import javax.net.ServerSocketFactory; + +import oshi.PlatformEnum; +import oshi.SystemInfo; +import oshi.util.ExecutingCommand; + +/** + * + * @author hengyunabc 2018-11-07 + * + */ +public class SocketUtils { + + public static int findTcpListenProcess(int port) { + try { + PlatformEnum platformEnum = SystemInfo.getCurrentPlatformEnum(); + if (PlatformEnum.WINDOWS.equals(platformEnum)) { + String[] command = { "netstat", "-ano", "-p", "TCP" }; + List lines = ExecutingCommand.runNative(command); + for (String line : lines) { + if (line.contains("LISTENING")) { + // TCP 0.0.0.0:49168 0.0.0.0:0 LISTENING 476 + String[] strings = line.trim().split("\\s+"); + if (strings.length == 5) { + if (strings[1].endsWith(":" + port)) { + return Integer.parseInt(strings[4]); + } + } + } + } + } + + if (PlatformEnum.MACOSX.equals(platformEnum) || PlatformEnum.LINUX.equals(platformEnum)) { + String pid = ExecutingCommand.getFirstAnswer("lsof -t -s TCP:LISTEN -i TCP:" + port); + if (!pid.trim().isEmpty()) { + return Integer.parseInt(pid); + } + } + } catch (Throwable e) { + // ignore + } + + return -1; + } + + public static boolean isTcpPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1, + InetAddress.getByName("localhost")); + serverSocket.close(); + return true; + } catch (Exception ex) { + return false; + } + } + +} diff --git a/client/pom.xml b/client/pom.xml index efc7e16e8..807ab5d71 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -59,7 +59,6 @@ jline jline - 2.14.6 diff --git a/pom.xml b/pom.xml index 7f0714554..cb5186af2 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ core agent client + boot testcase site packaging @@ -164,6 +165,18 @@ netty-codec-http 4.1.29.Final + + + jline + jline + 2.14.6 + + + + com.github.oshi + oshi-core + 3.9.1 +