Arthas源码学习-1

先前准备

1:源码下载:https://github.com/alibaba/arthas.git

2:导入到idea,我这里选择直接使用idea下载并打开

image-20210101140826921

3:整体目录结构

image-20210101141004000

image-20210101141023095

attach前准备工作

找到启动类并debug模式运行

​ com.taobao.arthas.boot.Bootstrap#main

 //获取一些配置
 CLI cli = CLIConfigurator.define(Bootstrap.class);

拿到启动类上面的一系列注解以及里面的内容,最后返回一个客户端

image-20210101142331695

将客户端包装成一个命令行

  //将客户端包装成命令行
  CommandLine commandLine = cli.parse(Arrays.asList(args));

将client注入到启动类

 CLIConfigurator.inject(commandLine, bootstrap);

筛选进程

pid = ProcessUtils.select(bootstrap.isVerbose(), telnetPortPid, bootstrap.getSelect());

通过jps命令列出进程

Map<Long, String> processMap = listProcessByJps(v);

findJps命令在哪个地方

//获取系统变量
String javaHome = System.getProperty("java.home");

获取jps的命令集合,可能存在着多个,返回第一个

 if (jpsList.isEmpty()) {
            AnsiLog.debug("Can not find jps under :" + javaHome);
            String javaHomeEnv = System.getenv("JAVA_HOME");
            AnsiLog.debug("Try to find jps under env JAVA_HOME :" + javaHomeEnv);
            for (String path : paths) {
                File jpsFile = new File(javaHomeEnv, path);
                if (jpsFile.exists()) {
                    AnsiLog.debug("Found jps: " + jpsFile.getAbsolutePath());
                    jpsList.add(jpsFile);
                }
            }
        }

返回jps的目录,现在已经知道jps在哪个目录下面了,待会会去执行jps命令,列出所有的java进程

image-20210101144617893

构建 jps 命令,这里的v 代表的是 verbose,相当于我们经常执行的 jps -lvm中的v

image-20210101144820459

执行 jps 命令,

List<String> lines = ExecutingCommand.runNative(command);

image-20210101145135878

中间对 jps返回的数据字符串进行格式化处理

获取到PID,和对应进程的映射关系,存储在一个map中

image-20210101145619886

将进程号以及对应的jps line打印在控制台,然后阻塞住

  int count = 1;
        for (String process : processMap.values()) {
            if (count == 1) {
                System.out.println("* [" + count + "]: " + process);
            } else {
                System.out.println("  [" + count + "]: " + process);
            }
            count++;
        }

如下,接下来叫我们输入对应的进程号,这里选择先前运行的 TestArthasApplication,选择5

image-20210101145807475

接下来做些参数校验

 //参数校验
        if (choice <= 0 || choice > processMap.size()) {
            return -1;
        } 
............
............

我们按了5后,最终选出来的 PID=12558 ,和我们预期的一样

image-20210101150542505

再来一轮参数校验,-- id校验之类的,以及会提示一些错误信息

  private static void checkTelnetPortPid(Bootstrap bootstrap, long telnetPortPid, long targetPid) {
        if (telnetPortPid > 0 && targetPid != telnetPortPid) {
            AnsiLog.error("The telnet port {} is used by process {} instead of target process {}, you will connect to an unexpected process.",
                    bootstrap.getTelnetPortOrDefault(), telnetPortPid, targetPid);
            AnsiLog.error("1. Try to restart arthas-boot, select process {}, shutdown it first with running the 'stop' command.",
                            telnetPortPid);
            AnsiLog.error("2. Or try to stop the existing arthas instance: java -jar arthas-client.jar 127.0.0.1 {} -c \"stop\"", bootstrap.getTelnetPortOrDefault());
            AnsiLog.error("3. Or try to use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1");
            System.exit(1);
        }
    }

获取到arthas的home,紧接着获取源码目录

CodeSource codeSource = Bootstrap.class.getProtectionDomain().getCodeSource();

file:/C:/Users/zhucc/IdeaProjects/arthas/boot/target/classes/

列出arthas lib 的目录,主要是获取arthas版本

 List<String> versionList = listNames(ARTHAS_LIB_DIR);

获取本地的版本arthas

 String localLastestVersion = null;
            if (!versionList.isEmpty()) {
                localLastestVersion = versionList.get(versionList.size() - 1);
            }
            //获取最新版本的arthas,下载工具
            String remoteLastestVersion = DownloadUtils.readLatestReleaseVersion();
 public static String readLatestReleaseVersion() {
        InputStream inputStream = null;
        try {
            URLConnection connection = openURLConnection(ARTHAS_LATEST_VERSIONS_URL);
            inputStream = connection.getInputStream();
            return IOUtils.toString(inputStream).trim();
        } catch (Throwable t) {
            AnsiLog.error("Can not read arthas version from: " + ARTHAS_LATEST_VERSIONS_URL);
            AnsiLog.debug(t);
        } finally {
            IOUtils.close(inputStream);
        }
        return null;
    }

核对是否要下载

localLastestVersion.compareTo(remoteLastestVersion) < 0

else {
                if (remoteLastestVersion != null) {
                    if (localLastestVersion.compareTo(remoteLastestVersion) < 0) {
                        AnsiLog.info("local lastest version: {}, remote lastest version: {}, try to download from remote.",
                                        localLastestVersion, remoteLastestVersion);
                        needDownload = true;
                    }
                }
            }

image-20210101153721453

获取到arthas的觉得路径

image-20210101153854174

check 进程id

   if (telnetPortPid > 0 && pid == telnetPortPid) {
            AnsiLog.info("The target process already listen port {}, skip attach.", bootstrap.getTelnetPortOrDefault());
        }

开始类加载工作

通过类url类加载器来加载磁盘上的文件,获取文件

image-20210101154614946

通过反射获取到 main 方法

在命令中拼装参数

 if (bootstrap.getCommand() != null) {
            telnetArgs.add("-c");
            telnetArgs.add(bootstrap.getCommand());
        }
        if (bootstrap.getBatchFile() != null) {
            telnetArgs.add("-f");
            telnetArgs.add(bootstrap.getBatchFile());
        }
        if (bootstrap.getHeight() != null) {
            telnetArgs.add("--height");
            telnetArgs.add("" + bootstrap.getHeight());
        }
        if (bootstrap.getWidth() != null) {
            telnetArgs.add("--width");
            telnetArgs.add("" + bootstrap.getWidth());
        }

在当前线程中设置一个上下文,并invoke刚刚 telnetConsole.main方法

image-20210101155237307

在telnetConsole.main 中断点,已经进入

image-20210101155337574

com.taobao.arthas.client.TelnetConsole#process(java.lang.String[], java.awt.event.ActionListener)

调用启动类一样的接口,做参数设置,等等,这里不再赘述, 紧接着一系列参数校验

TelnetConsole telnetConsole = new TelnetConsole();
CLI cli = CLIConfigurator.define(TelnetConsole.class);
CommandLine commandLine = cli.parse(Arrays.asList(args));
CLIConfigurator.inject(commandLine, telnetConsole);

获取一个终端 Terminal

final ConsoleReader consoleReader = new ConsoleReader(System.in, System.out);
consoleReader.setHandleUserInterrupt(true);
Terminal terminal = consoleReader.getTerminal();

// support catch ctrl+c event
terminal.disableInterruptCharacter();
if (terminal instanceof UnixTerminal) {
    ((UnixTerminal) terminal).disableLitteralNextCharacter();
}

连接终端,连接上后,返回成功 – STATUS_OK

org.apache.commons.net.SocketClient#connect(java.lang.String, int)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值