arthas源码刨析:arthas 命令粗谈(3)

news2024/11/16 5:51:08

文章目录

  • dashboard
  • watch
  • retransform

前面介绍了 arthas 启动相关的代码并聊了聊怎么到一个 shellserver 的建立。
本篇我们来探讨一下几个使用频次非常高的命令是如何实现的。

dashboard

在这里插入图片描述
想看这个命令的主要原因是编程这些年来从来没有开发过 terminal 的这种比较花哨的界面,和 top 命令一样。
在这里插入图片描述

  public void process(final CommandProcess process) {

        Session session = process.session();
        timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);

        // ctrl-C support
        process.interruptHandler(new DashboardInterruptHandler(process, timer));

        /*
         * 通过handle回调,在suspend和end时停止timer,resume时重启timer
         */
        Handler<Void> stopHandler = new Handler<Void>() {
            @Override
            public void handle(Void event) {
                stop();
            }
        };

        Handler<Void> restartHandler = new Handler<Void>() {
            @Override
            public void handle(Void event) {
                restart(process);
            }
        };
        process.suspendHandler(stopHandler);
        process.resumeHandler(restartHandler);
        process.endHandler(stopHandler);

        // q exit support
        process.stdinHandler(new QExitHandler(process));

        // start the timer
        timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
    }
  1. 获取会话

    Session session = process.session();
    
    • 调用process对象的session方法获取当前会话对象,并赋值给session变量。process
  2. 创建定时器

    timer = new Timer("Timer-for-arthas-dashboard-" + session.getSessionId(), true);
    
    • 创建一个定时器对象,命名为"Timer-for-arthas-dashboard-" + session.getSessionId(),其中session.getSessionId()获取会话ID。 定时器的主要作用是进行界面刷新。
    • 第二个参数true表示这个定时器是守护线程,当所有非守护线程结束时,程序会自动退出。
  3. 处理中断

    process.interruptHandler(new DashboardInterruptHandler(process, timer));
    
    • 设置一个中断处理器DashboardInterruptHandler,用于处理中断信号(如Ctrl+C)。
  4. 处理挂起、恢复和结束事件

    Handler<Void> stopHandler = new Handler<Void>() {
        @Override
        public void handle(Void event) {
            stop();
        }
    };
    
    Handler<Void> restartHandler = new Handler<Void>() {
        @Override
        public void handle(Void event) {
            restart(process);
        }
    };
    process.suspendHandler(stopHandler);
    process.resumeHandler(restartHandler);
    process.endHandler(stopHandler);
    
    • 定义了两个处理器stopHandlerrestartHandler,分别用于处理挂起、恢复和结束事件。
    • stopHandler调用stop方法停止定时器。
    • restartHandler调用restart(process)方法重新启动定时器。
    • 将这些处理器分别注册到process对象的suspendHandlerresumeHandlerendHandler中。
  5. 处理退出命令

    process.stdinHandler(new QExitHandler(process));
    
    • 设置一个标准输入处理器QExitHandler,用于处理退出命令(如输入q)。
  6. 启动定时器

    timer.scheduleAtFixedRate(new DashboardTimerTask(process), 0, getInterval());
    
    • 使用timer对象定期执行DashboardTimerTask任务。
    • 周期性执行 DashboardTimerTask ,统计信息从 jvm 的 MXBean 中获取并组装 dashboardModel 再后面跟踪,就能发现界面绘制是属于 ProcessImpl 对命令结果的处理,发现没有,arthas 是一个很完整的解耦设计,职责分明。如何绘制界面在 view 文件夹,比如该命令
      在这里插入图片描述

在这里插入图片描述
再往后看,界面就是做的以行为单元的字符串,输出到标准输出中,stdout。
在这里插入图片描述

watch

下面的 watch 命令在分析性能问题是出场率最高的命令,极大的释放了开发的背锅压力。
WatchCommand 继承了 EnhancerCommand ,下面的代码就是增强的核心代码。一般来说我们都知道使用 javaagent 可以在加载到jvm之前做增强,也就是 premain 方法,但 jvm也提供了 attach 的增强,即 agentmain,这也是在上一篇中提到过的

protected void enhance(CommandProcess process) {
        Session session = process.session();
        if (!session.tryLock()) {
            String msg = "someone else is enhancing classes, pls. wait.";
            process.appendResult(new EnhancerModel(null, false, msg));
            process.end(-1, msg);
            return;
        }
        EnhancerAffect effect = null;
        int lock = session.getLock();
        try {
            Instrumentation inst = session.getInstrumentation();
            AdviceListener listener = getAdviceListenerWithId(process);
            if (listener == null) {
                logger.error("advice listener is null");
                String msg = "advice listener is null, check arthas log";
                process.appendResult(new EnhancerModel(effect, false, msg));
                process.end(-1, msg);
                return;
            }
            boolean skipJDKTrace = false;
            if(listener instanceof AbstractTraceAdviceListener) {
                skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace();
            }

            Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getClassNameExcludeMatcher(), getMethodNameMatcher());
            // 注册通知监听器
            process.register(listener, enhancer);
            effect = enhancer.enhance(inst, this.maxNumOfMatchedClass);

            if (effect.getThrowable() != null) {
                String msg = "error happens when enhancing class: "+effect.getThrowable().getMessage();
                process.appendResult(new EnhancerModel(effect, false, msg));
                process.end(1, msg + ", check arthas log: " + LogUtil.loggingFile());
                return;
            }

            if (effect.cCnt() == 0 || effect.mCnt() == 0) {
                // no class effected
                if (!StringUtils.isEmpty(effect.getOverLimitMsg())) {
                    process.appendResult(new EnhancerModel(effect, false));
                    process.end(-1);
                    return;
                }
                // might be method code too large
                process.appendResult(new EnhancerModel(effect, false, "No class or method is affected"));

                String smCommand = Ansi.ansi().fg(Ansi.Color.GREEN).a("sm CLASS_NAME METHOD_NAME").reset().toString();
                String optionsCommand = Ansi.ansi().fg(Ansi.Color.GREEN).a("options unsafe true").reset().toString();
                String javaPackage = Ansi.ansi().fg(Ansi.Color.GREEN).a("java.*").reset().toString();
                String resetCommand = Ansi.ansi().fg(Ansi.Color.GREEN).a("reset CLASS_NAME").reset().toString();
                String logStr = Ansi.ansi().fg(Ansi.Color.GREEN).a(LogUtil.loggingFile()).reset().toString();
                String issueStr = Ansi.ansi().fg(Ansi.Color.GREEN).a("https://github.com/alibaba/arthas/issues/47").reset().toString();
                String msg = "No class or method is affected, try:\n"
                        + "1. Execute `" + smCommand + "` to make sure the method you are tracing actually exists (it might be in your parent class).\n"
                        + "2. Execute `" + optionsCommand + "`, if you want to enhance the classes under the `" + javaPackage + "` package.\n"
                        + "3. Execute `" + resetCommand + "` and try again, your method body might be too large.\n"
                        + "4. Match the constructor, use `<init>`, for example: `watch demo.MathGame <init>`\n"
                        + "5. Check arthas log: " + logStr + "\n"
                        + "6. Visit " + issueStr + " for more details.";
                process.end(-1, msg);
                return;
            }

            // 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃
            if (session.getLock() == lock) {
                if (process.isForeground()) {
                    process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n");
                }
            }

            process.appendResult(new EnhancerModel(effect, true));

            //异步执行,在AdviceListener中结束
        } catch (Throwable e) {
            String msg = "error happens when enhancing class: "+e.getMessage();
            logger.error(msg, e);
            process.appendResult(new EnhancerModel(effect, false, msg));
            process.end(-1, msg);
        } finally {
            if (session.getLock() == lock) {
                // enhance结束后解锁
                process.session().unLock();
            }
        }
    }

这个方法的核心就在这几行代码中:

			Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getClassNameExcludeMatcher(), getMethodNameMatcher());
            // 注册通知监听器
            process.register(listener, enhancer);
            effect = enhancer.enhance(inst, this.maxNumOfMatchedClass);
/**
     * 对象增强
     *
     * @param inst              inst
     * @param maxNumOfMatchedClass 匹配的class最大数量
     * @return 增强影响范围
     * @throws UnmodifiableClassException 增强失败
     */
    public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNumOfMatchedClass) throws UnmodifiableClassException {
        // 获取需要增强的类集合
        this.matchingClasses = GlobalOptions.isDisableSubClass
                ? SearchUtils.searchClass(inst, classNameMatcher)
                : SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher));

        if (matchingClasses.size() > maxNumOfMatchedClass) {
            affect.setOverLimitMsg("The number of matched classes is " +matchingClasses.size()+ ", greater than the limit value " + maxNumOfMatchedClass + ". Try to change the limit with option '-m <arg>'.");
            return affect;
        }
        // 过滤掉无法被增强的类
        List<Pair<Class<?>, String>> filtedList = filter(matchingClasses);
        if (!filtedList.isEmpty()) {
            for (Pair<Class<?>, String> filted : filtedList) {
                logger.info("ignore class: {}, reason: {}", filted.getFirst().getName(), filted.getSecond());
            }
        }

        logger.info("enhance matched classes: {}", matchingClasses);

        affect.setTransformer(this);

        try {
            ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing);

            // 批量增强
            if (GlobalOptions.isBatchReTransform) {
                final int size = matchingClasses.size();
                final Class<?>[] classArray = new Class<?>[size];
                arraycopy(matchingClasses.toArray(), 0, classArray, 0, size);
                if (classArray.length > 0) {
                    inst.retransformClasses(classArray);
                    logger.info("Success to batch transform classes: " + Arrays.toString(classArray));
                }
            } else {
                // for each 增强
                for (Class<?> clazz : matchingClasses) {
                    try {
                        inst.retransformClasses(clazz);
                        logger.info("Success to transform class: " + clazz);
                    } catch (Throwable t) {
                        logger.warn("retransform {} failed.", clazz, t);
                        if (t instanceof UnmodifiableClassException) {
                            throw (UnmodifiableClassException) t;
                        } else if (t instanceof RuntimeException) {
                            throw (RuntimeException) t;
                        } else {
                            throw new RuntimeException(t);
                        }
                    }
                }
            }
        } catch (Throwable e) {
            logger.error("Enhancer error, matchingClasses: {}", matchingClasses, e);
            affect.setThrowable(e);
        }

        return affect;
    }

增强的关于字节码增强部分在 WatchAdviceListener 中,相当于做了一个 AOP。而这些 AdviceListenerAdapter 的调用链: SpyImpl ---- SpyAPI — SpyInterceptors — Enhancer.transform

class WatchAdviceListener extends AdviceListenerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(WatchAdviceListener.class);
    private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch();
    private WatchCommand command;
    private CommandProcess process;

    public WatchAdviceListener(WatchCommand command, CommandProcess process, boolean verbose) {
        this.command = command;
        this.process = process;
        super.setVerbose(verbose);
    }

    private boolean isFinish() {
        return command.isFinish() || !command.isBefore() && !command.isException() && !command.isSuccess();
    }

    @Override
    public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
            throws Throwable {
        // 开始计算本次方法调用耗时
        threadLocalWatch.start();
        if (command.isBefore()) {
            watching(Advice.newForBefore(loader, clazz, method, target, args));
        }
    }

    @Override
    public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
                               Object returnObject) throws Throwable {
        Advice advice = Advice.newForAfterReturning(loader, clazz, method, target, args, returnObject);
        if (command.isSuccess()) {
            watching(advice);
        }

        finishing(advice);
    }

    @Override
    public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
                              Throwable throwable) {
        Advice advice = Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable);
        if (command.isException()) {
            watching(advice);
        }

        finishing(advice);
    }

    private void finishing(Advice advice) {
        if (isFinish()) {
            watching(advice);
        }
    }


    private void watching(Advice advice) {
        try {
            // 本次调用的耗时
            double cost = threadLocalWatch.costInMillis();
            boolean conditionResult = isConditionMet(command.getConditionExpress(), advice, cost);
            if (this.isVerbose()) {
                process.write("Condition express: " + command.getConditionExpress() + " , result: " + conditionResult + "\n");
            }
            if (conditionResult) {
                // TODO: concurrency issues for process.write

                Object value = getExpressionResult(command.getExpress(), advice, cost);

                WatchModel model = new WatchModel();
                model.setTs(new Date());
                model.setCost(cost);
                model.setValue(new ObjectVO(value, command.getExpand()));
                model.setSizeLimit(command.getSizeLimit());
                model.setClassName(advice.getClazz().getName());
                model.setMethodName(advice.getMethod().getName());
                if (advice.isBefore()) {
                    model.setAccessPoint(AccessPoint.ACCESS_BEFORE.getKey());
                } else if (advice.isAfterReturning()) {
                    model.setAccessPoint(AccessPoint.ACCESS_AFTER_RETUNING.getKey());
                } else if (advice.isAfterThrowing()) {
                    model.setAccessPoint(AccessPoint.ACCESS_AFTER_THROWING.getKey());
                }

                process.appendResult(model);
                process.times().incrementAndGet();
                if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) {
                    abortProcess(process, command.getNumberOfLimit());
                }
            }
        } catch (Throwable e) {
            logger.warn("watch failed.", e);
            process.end(-1, "watch failed, condition is: " + command.getConditionExpress() + ", express is: "
                    + command.getExpress() + ", " + e.getMessage() + ", visit " + LogUtil.loggingFile()
                    + " for more details.");
        }
    }
}

Enhance 是个自定义的 ClassTransformer ,在 Instrumentation 进行 addTransformer 时,将 enhance 增强纳进管理,在 redefined 或者 retransformed 的时候调用 transform 方法。。
Instrumentation 的方法:
在这里插入图片描述

retransform

retransform 这个命令的牛逼之处是可以进行灵活的热加载,用过 jrebel 的同学应该比较清除,在开发时能使用热加载,compile 一下修改的功能就能生效在开发时能节省大量的时间。
在arthas 中 jad–mc–retransform 工具链实现热加载也是一个在线上快速修复的一个方法。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新出炉 -Web自动化测试之playwright:概述

概述 playwright是由微软开发的Web UI自动化测试工具&#xff0c; 支持Node.js、Python、C# 和 Java语言&#xff0c;本文将介绍playwright的特性以及它的简单使用。 playwright特性 playwright具有以下特点&#xff1a; 一、支持所有主流浏览器 支持所有主流浏览器&#x…

从开发到集成:视频美颜SDK与直播美颜API详解

在本文中&#xff0c;我们将详细探讨视频美颜SDK的开发过程及其与直播美颜API的集成方案&#xff0c;帮助开发者更好地理解和应用这些技术。 一、视频美颜SDK的开发概述 视频美颜SDK是一个用于实时视频处理的开发工具包&#xff0c;提供了包括磨皮、美白、瘦脸、眼睛放大等多…

各类软件历史版本的下载地址

postman,notpad等 https://www.filehorse.com/software-developer-tools/https://www.filehorse.com/software-developer-tools/

数业智能心大陆AI大模型,共情陪伴你的心理健康

大模型的出现&#xff0c;使得AI在语音识别、自然语言处理、计算机视觉等领域的性能得到了极大的提升&#xff0c;随着硬件设备的不断升级和优化&#xff0c;以及算法的不断改进&#xff0c;大模型的规模和性能也在不断提升&#xff0c;大模型的优势在于其强大的表示能力和泛化…

不愿回流上班,离职博主们不断寻找新的“栖息地”

文 | 螳螂观察 作者 | 如意 “替大家试过了&#xff0c;不上班真的很爽。” “985本硕&#xff0c;年薪40万裸辞了。” “不干了&#xff0c;谁家好人半夜12点还在司啊&#xff01;” 标题熟悉吧&#xff1f;对&#xff0c;这拨人你一定看到了&#xff0c;说人生是旷野&am…

45+用户占比近30%,网文产业如何赋能IP长链?

网文市场加速发展&#xff0c;巨头抢占中老年用户 作者&#xff5c;吕娆炜 排版&#xff5c;张思琪 干货抢先看 1. 我国网文产业市场规模突破3000亿元&#xff0c;在用户方面&#xff0c;截至2023年底&#xff0c;我国网文用户数量达5.37亿&#xff0c;同比增长9%&#xff0c…

【FreeRTOS】信号量

0 前言 学习视频&#xff1a; 【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS&#xff08;FreeRTOS教程 基于STM32&#xff0c;以实际项目为导向&#xff09;】 【精准空降到 00:42】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p39&share_sourcecopy_web&…

源2.0-M32大模型发布4bit/8bit量化版! 运行显存仅需23GB,性能可媲美LLaMA3

近日&#xff0c;浪潮信息发布源2.0-M32大模型4bit和8bit量化版&#xff0c;性能比肩700亿参数的LLaMA3开源大模型。4bit量化版推理运行显存仅需23.27GB&#xff0c;处理每token所需算力约为1.9 GFLOPs&#xff0c;算力消耗仅为同等当量大模型LLaMA3-70B的1/80。而LLaMA3-70B运…

删除Eureka注册中心已经注册的服务

1.登录Eureka查看需要删除的服务。 2.使用postman或者apipost工具&#xff0c;请求方式DELETE, 接口地址输入&#xff1a;eureka的ip地址/eureka/apps/ Application / Status 例如: http://192.168.194.60:8761/eureka/apps/VUE-MANAGER-SERVICE/10.42.0.138:vue-manager…

酷家乐 同盾滑块分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我…

【Hot100】LeetCode—114. 二叉树展开为链表

目录 1- 思路技巧——借助指针 2- 实现⭐114. 二叉树展开为链表——题解思路 3- ACM 实现 原题连接&#xff1a;114. 二叉树展开为链表 1- 思路 技巧——借助指针 思路&#xff1a;通过 ① 将左子树的右下结点的 .next ——> 拼接到当前节点的右子树上。 构造 cur 指针&a…

KPaaS还是ESB?怎样选择合适的集成方案?

在全球经济一体化和数字化转型的背景下&#xff0c;企业正面临着前所未有的挑战与机遇。随着业务的快速发展&#xff0c;企业内部的信息系统日益复杂&#xff0c;系统间的信息孤岛、系统割裂以及高昂的维护成本等问题逐渐凸显&#xff0c;严重制约了企业的创新能力和市场竞争力…

Live800:以数据驱动的客户服务优化,精准洞察,超越期待

在当今这个数字化时代&#xff0c;企业之间的竞争已不仅仅局限于产品本身的质量与功能&#xff0c;更在于如何通过卓越的客户服务体验来赢得客户的忠诚与信赖。数据&#xff0c;作为这一转型过程中的核心驱动力&#xff0c;正引领着客户服务向更加精准、高效、个性化的方向迈进…

3ds Max - 导出顶点色模型

很久之前的笔记&#xff0c;整理归档&#xff1b; 在3ds Max中&#xff0c;给模型添加VetexPaint修改器后&#xff0c;可以给模型&#xff08;顶点色通道R\G\B默认值为255\255\255&#xff09;刷不同颜色的顶点色&#xff08;默认为黑色&#xff0c;即让RGB通道都为0&#xff0…

最新SD换脸插件ReActor,视频换脸全流程!流畅丝滑!(附插件及安装报错说明)

在今天&#xff0c;我给大家继续分享一个使用“ReActor”插件来进行视频换脸的案例。 视频换脸的思路其实也很简单&#xff0c;其实就是把视频的每一帧都提取出来&#xff0c;然后把每一帧的图片都进行换脸&#xff0c;最后重新把这些图片重新合成一个视频。 废话不多说&…

下载bison操作步骤

bison官网链接 bison-3.8.2官网源代码下载链接 选择下载版本

strchr 和 strrchr

char *strchr(const char *s, int c); 在 s 中查找第一次出现字符c的位置&#xff0c;返回指向找到的位置&#xff0c;找不到返回null。 char *strrchr(const char *s, int c); 在 s 中查找最后一次出现字符c的位置&#xff0c;返回指向找到的位置&#xff0c;找不到返回null。…

用Python插入SVG到PDF文档

将SVG&#xff08;可缩放矢量图形&#xff09;文件插入到PDF&#xff08;便携式文档格式&#xff09;文件中不仅能够保留SVG图像的矢量特性&#xff0c;确保图像在任何分辨率下都保持清晰&#xff0c;还能够充分利用PDF格式在跨平台文档分享方面的优势&#xff0c;使得技术文档…

解决 mfc140.dll 文件丢失的问题,5种mfc140.dll解决方法

当您尝试启动一个用 Microsoft Visual Studio 2015 开发的 Windows 应用程序时&#xff0c;如果出现“无法找到 mfc140.dll 文件”的错误&#xff0c;请不要紧张。这类问题通常由DLL文件缺失、损坏或未正确安装引起。好消息是&#xff0c;存在多种解决方案可以帮助您解决这一挑…

打卡学习Python爬虫第五天|Xpath解析的使用

什么是Xpath&#xff1f;是在XML文档中搜索内容的一门语言&#xff0c;HTML可以看作是xml的一个子集。 目录 1、安装lxml模块 2、导入lxml中的etree子模块 3、Xpath使用方法 3.1.选择节点 3.2.选择属性 3.3.选择文本内容 3.4.使用通配符*过滤节点 3.5.使用中括号[]索引…