\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 extends ZipEntry> 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
+