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

news2024/12/28 2:57:26

文章目录

  • dashboard
  • watch
  • retransform

前面介绍了 arthas 启动相关的代码并聊了聊怎么到一个 shellserver 的建立。
本篇我们来探讨一下几个使用频次非常高的命令是如何实现的。
在开始之前,我们先概要地了解一下 arthas 命令的几个思路。

  1. 自定义命令,普通信息查询。如 cat、 help、 echo 等等,在 process 中写逻辑,并更新到 XXXModel 类中,process.appendResult(new CatModel(file, fileToString)); 在控制台中打印。
  2. MXBean 如 JvmCommand,MemoryCommond,HeapDumpCommand 等。核心是调用 JVM 提供的 MXBean 接口。
  3. 执行 字节码增强,调用自定义 ClassFileTransformer 的 transform 方法,增强字节码功能或者实现 aop 的 trace。如 retransform,jad 等。

整个命令体系如果拆开了看,也会发现还算是比较简单的:信息获取、功能增强或者 trace。难的还是他的架构如何构建,怎么做到功能解耦,还是很好的学习素材。

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 工具链实现热加载也是一个在线上快速修复的一个方法。

在这里插入图片描述
retransform 使用时要注意的一点就是 JVM判断是否是同一个类的时候要判断是不是用一个 classloader 加载。不同 classloader 加载的同一个 class 被认为是不同的 2 个类。

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

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

相关文章

【Hexo】hexo-butterfly主题添加非主站提示

本文首发于 ❄️慕雪的寒舍 说明 因为hexo可以很方便的在多个平台上免费部署&#xff0c;为了让自己的博客能uptime更久一段时间&#xff0c;很多老哥都和我一样&#xff0c;把自己的hexo博客在好多个平台上都部署了一份。 但是我一直想要一个功能&#xff0c;就是在别人访问…

可商用插画

可商用插画 https://www.88sheji.cn/favorites/free-illustration

5G+智慧工业园区解决方案

1. 智慧工业园区架构概览 智慧工业园区采用多层架构设计&#xff0c;包括展示层、应用层、服务层、数据层和安全保障体系。此架构利用云计算、物联网和移动通信技术&#xff0c;实现园区管理的数字化和智能化。 2. 园区大脑功能 园区大脑作为智慧园区的“中枢神经”&#xf…

校园招聘,在线测评包括哪些内容?

一年两次的校园招聘&#xff08;秋招和春招&#xff09;&#xff0c;面对众多职业测评的时候&#xff0c;很多人都会觉得无从下手&#xff0c;从而在竞争中失利。根据实践中的情况来看&#xff0c;校园招聘时的测试其实不难&#xff0c;求职者提前准备&#xff0c;想通过在线测…

SLAM十四讲ch3课后习题

1.验证旋转矩阵是正交矩阵。 2.验证四元数旋转某个点后&#xff0c;结果是一个虚四元数&#xff08;实部为零&#xff09;&#xff0c;所以仍然对应到一个三维空间点。 注意&#xff1a;目前市面上所有的博客都说旋转四元数的逆是共轭除以模的平方 &#xff0c;这么算很正确但…

免费分享:2023甘肃省地质灾害点数据集(附下载方法)

滑坡&#xff0c;在一定自然条件下的斜坡上的十体或岩体在外界的影响和自身的作用下沿着一定的软弱面或带&#xff0c;发生以水平心移为主的变形现象。地面沉降又称为地面下沉或地陷&#xff0c;是在自然或人类工程的影响下&#xff0c;由于地下松散土层固结收缩压密作用&#…

无人机飞手培训考证,超视距大载重吊运组装训练全学就业有保障

关于无人机飞手培训考证&#xff0c;是否必须学习超视距飞行、大载重吊运以及组装训练以确保就业保障&#xff0c;这个问题涉及多个方面&#xff0c;以下是对各点的详细分析&#xff1a; 一、超视距飞行 必要性分析&#xff1a; 超视距飞行是无人机高级应用中的一个重要领域…

大模型备案全网最详细流程解读(附附件+重点解读)

文章目录 一、语料安全评估 二、黑盒测试 三、模型安全措施评估 四、性能评估 五、性能评估 六、安全性评估 七、可解释性评估 八、法律和合规性评估 九、应急管理措施 十、材料准备 十一、【线下流程】大模型备案线下详细步骤说明 十二、【线上流程】算法备案填报流…

Cesium 全球视角 和 多方案镜头切换

一.切换镜头 镜头切换&#xff0c;在一个Pawn里的多个镜头。可以使用UE中World Settings里的玩家控制器中&#xff0c;默认的控制器行为会对当前开启的Camera组件进行激活处理。 谁激活谁就是主相机。 Cast<UCameraComponent>(m_childComponentMap[it.CameraName])-&g…

VLDB 2024丨与 TiDB 一起探索数据库学术前沿

VLDB 2024 将于2024年8月26日至8月30日在中国广州举行。VLDB 是数据库领域的顶级国际会议&#xff0c;旨在为数据管理、可扩展数据科学、数据库研究者、供应商、从业者、应用开发者和用户提供交流平台。 2024年的VLDB会议涵盖了数据管理、数据库架构、图形数据管理、数据隐私与…

切换JDK版本

JDK&#xff08;Java Development Kit&#xff09;是Java开发工具包&#xff0c;它包含了Java的开发环境和运行环境。JDK是整个Java的核心&#xff0c;包括了Java运行环境&#xff08;Java Runtime Environment, JRE&#xff09;、Java工具和Java基础的类库&#xff08;rt.jar&…

九、枚举和注解

文章目录 一、枚举介绍二、自定义类实现枚举三、enum关键字实现枚举3.1 enum案例3.2 enum关键字实现枚举注意事项3.3 enum常用方法说明3.4 enum实现接口 四、JDK内置的基本注解类型4.1 注解的理解4.2 基本的Annotation介绍4.3 Override4.4 Deprecated4.5 SuppressWarnings 五、…

【性能优化】:设计模式与技术方案解析(二)

引言 在 【性能优化】&#xff1a;探索系统瓶颈的根源&#xff08;一&#xff09;文章中&#xff0c;我们已经分析了手动结算的弊端和瓶颈&#xff0c;本文来分析下怎么优化系统性能。 需求分析 既然手动结算耗时费力易出错&#xff0c;那么能不能开发一个**程序自动化处理*…

ARM32开发——(十八)RTC实时时表

1. RTC内置实时时钟 1.1 RTC时钟介绍 RTC是实时时钟(Real-Time Clock)的缩写。它是一种硬件模块或芯片,用于提供准确的日期和时间信息。 GD32F407上有RTC的外设,它提供了一个包含日期(年/月/日)和时间(时/分/秒/亚秒)的日历功能。除亚秒用二进制码显示外,时间和日期都以BCD码…

JSP中的动态INCLUDE与静态INCLUDE:简明对比

JSP中的动态INCLUDE与静态INCLUDE&#xff1a;简明对比 1、静态INCLUDE2、动态INCLUDE3、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在JSP开发中&#xff0c;页面包含分为动态INCLUDE和静态INCLUDE两种&#xff0c;它们各有特点。…

详细分析Oracle中的ALL_TAB_COLUMNS视图语句

目录 1. 基本知识2. Demo 1. 基本知识 ALL_TAB_COLUMNS 是 Oracle 数据库的一个数据字典视图&#xff0c;用于提供关于数据库中所有可见表的列信息 对于数据库管理、开发和调试非常有用 基本的字段描述如下&#xff1a; 字段名描述OWNER列出表所在的所有者&#xff08;sche…

神经网络——CIFAR10小实战

1.引子 Sequential的使用&#xff1a;将网络结构放入其中即可&#xff0c;可以简化代码。 找了一个对CIFAR10进行分类的模型。 2.代码实战 from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linearclass Tudui(nn.Module):def __init__(self):super(T…

SOMEIP_ETS_069: Unaligned_SOMEIP_Messages_overUDP

测试目的&#xff1a; 验证设备&#xff08;DUT&#xff09;能够处理在单个UDP包中发送的三个SOME/IP消息&#xff0c;并且即使其中一个SOME/IP消息未对齐&#xff0c;DUT仍能对所有三个请求给出正确的响应。 描述 本测试用例旨在检查DUT在通过UDP协议接收到包含一个未对齐S…

【Pytorch】Linear 层,举例:相机参数和Instance Feaure通过Linear层生成Group Weights

背景 看论文看到这个pipeline&#xff0c;对于相机参数和Instance Fature 的融合有点兴趣&#xff0c;研究如下&#xff1a; Linear 层 Linear 层是最基本的神经网络层之一&#xff0c;也称为全连接层。它将输入与每个输出神经元完全连接。每个连接都有一个权重和一个偏置。…

inflight 守恒算法负反馈解析

终于说到这个话题了。 bbr 不好吗&#xff0c;我自己也做过很多关于 bbr 的仿真验证&#xff0c;现网数据分析以及数学建模&#xff0c;结论均指向 bbr 是一个公平高效且天然不会引发 bufferbloat 的算法&#xff0c;但细节值得商榷&#xff1a; 非理想哑铃拓扑下测量误差的叠…