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

文章目录

  • 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 supportprocess.interruptHandler(new DashboardInterruptHandler(process, timer));/** 通过handle回调,在suspend和end时停止timer,resume时重启timer*/Handler<Void> stopHandler = new Handler<Void>() {@Overridepublic void handle(Void event) {stop();}};Handler<Void> restartHandler = new Handler<Void>() {@Overridepublic void handle(Void event) {restart(process);}};process.suspendHandler(stopHandler);process.resumeHandler(restartHandler);process.endHandler(stopHandler);// q exit supportprocess.stdinHandler(new QExitHandler(process));// start the timertimer.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>() {@Overridepublic void handle(Void event) {stop();}
    };Handler<Void> restartHandler = new Handler<Void>() {@Overridepublic 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 effectedif (!StringUtils.isEmpty(effect.getOverLimitMsg())) {process.appendResult(new EnhancerModel(effect, false));process.end(-1);return;}// might be method code too largeprocess.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();}@Overridepublic 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));}}@Overridepublic 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);}@Overridepublic 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.writeObject 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.mzph.cn/news/877768.shtml

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

相关文章

SpringBoot集成kafka-获取生产者发送的消息(阻塞式和非阻塞式获取)

说明 CompletableFuture对象需要的SpringBoot版本为3.X.X以上&#xff0c;需要的kafka依赖版本为3.X.X以上&#xff0c;需要的jdk版本17以上。 1、阻塞式&#xff08;等待式&#xff09;获取生产者发送的消息 生产者&#xff1a; package com.power.producer;import org.ap…

【html+css 绚丽Loading】 000014 三元波动盘

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

JVM系列--初始JVM

根据《黑马程序员JVM虚拟机入门到实战全套视频教程》整理 1 什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 Java源代码执行流程如下&#xff1a; 分为三个步…

书生大模型实战营第三期基础岛第二课——8G 显存玩转书生大模型 Demo

8G 显存玩转书生大模型 Demo 基础任务进阶作业一&#xff1a;进阶作业二&#xff1a; 基础任务 使用 Cli Demo 完成 InternLM2-Chat-1.8B 模型的部署&#xff0c;并生成 300 字小故事&#xff0c;记录复现过程并截图。 创建conda环境 # 创建环境 conda create -n demo pytho…

[Meachines] [Easy] Legacy nmap 漏洞扫描脚本深度发现+MS08-067

信息收集 IP AddressOpening Ports10.10.10.4TCP:135,139,445 $ nmap -p- 10.10.10.4 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows n…

Java二十三种设计模式-责任链模式(17/23)

责任链模式&#xff1a;实现请求处理的灵活流转 引言 在这篇博客中&#xff0c;我们深入探讨了责任链模式的精髓&#xff0c;从其定义和用途到实现方法&#xff0c;再到使用场景、优缺点、与其他模式的比较&#xff0c;以及最佳实践和替代方案&#xff0c;旨在指导开发者如何…

SAP BW:QUERY数据结果写入ADSO

作者 idan lian 如需转载备注出处 如果对你有帮助&#xff0c;请点赞收藏~~~ 需求背景 客户基于QUERY进行报表展示&#xff0c;现需迁移到永洪报表平台&#xff0c;query中的变量参数&#xff0c;公式等无法直接生成视图&#xff0c;query相对复杂&#xff0c;不想直接在视图…

笔记mybatisplus

MP入门 Mybatis-Plus&#xff08;简称MP&#xff09;是一个Mybatis的增强工具&#xff0c;在Mybatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 Mybatis-Plus已经封装好了大量增删改查的方法&#xff0c;程序员只需要继承BaseMapper就可以使用这些方法…

Linux阿里云服务器,利用docker安装EMQX

第一步&#xff0c;给云服务器docker进行加速 阿里云搜索“镜像加速器”&#xff0c;找到下面这个菜单&#xff0c;点进去 然后找到镜像工具下的镜像加速器 把这个加速器地址复制 然后在自己的云服务器中&#xff0c;找到docker的文件夹 点击json配置文件 把地址修改为刚刚…

如何将LaTeX数学公式嵌入到PowerPoint中

如何将LaTeX数学公式嵌入到PowerPoint中 简介 在学术演示或技术报告中&#xff0c;清晰且专业地展示数学公式是至关重要的。PowerPoint虽然提供了一些基本的公式编辑功能&#xff0c;但如果你需要更复杂或格式严格的公式&#xff0c;使用LaTeX生成公式并嵌入到PPT中是一个极佳…

Python酷库之旅-第三方库Pandas(092)

目录 一、用法精讲 391、pandas.Series.hist方法 391-1、语法 391-2、参数 391-3、功能 391-4、返回值 391-5、说明 391-6、用法 391-6-1、数据准备 391-6-2、代码示例 391-6-3、结果输出 392、pandas.Series.to_pickle方法 392-1、语法 392-2、参数 392-3、功能…

KT来袭,打造沉浸式体验的聚合性web3应用平台

随着步入 2024&#xff0c;漫长的区块链熊市即将接近尾声。纵观产业发展&#xff0c;逆流而上往往会是彰显品牌市场影响力和技术实力的最佳证明。在这次周期中&#xff0c;一个名为KT的web3.0聚合平台吸引了市场关注&#xff0c;无论在市场层面还是技术层面&#xff0c;都广泛赢…

听劝❗用AI做职场思维导图仅仅需要几秒钟啊

本文由 ChatMoney团队出品 嘿&#xff0c;各位职场朋友们 是不是常常对着密密麻麻的笔记感到焦虑呢&#xff1f; 想整理却无从下手&#xff1f; 别怕&#xff0c;ChatmoneyAI知识库来拯救你的整理困难症啦&#xff01; 咱们都知道&#xff0c;思维导图是职场中必备的神器 …

zoom 会议机器人web例子

一、需要创建zoom app&#xff0c;创建及配置参考&#xff1a;Zoom会议机器人转写例子-CSDN博客 这里直接使用zoom-recall的配置。 二、需要生成签名&#xff0c;参数为&#xff1a;zoom-recall中的Client ID和Client Secret 1、git clone https://github.com/zoom/meetings…

【PHP入门教程】PHPStudy环境搭建+composer创建项目

文章目录 PHP 的历史PHP 的用途PHP 的特点和优势PHP 环境搭建环境准备安装window 安装CentOS / Ubuntu / Debian 安装 第一个Hello World使用Apache服务运行命令行运行代码 Composer安装 Composer&#xff1a;安装途中报错解决&#xff1a;初始化项目创建文件最终文件目录Compo…

微服务:配置管理和配置热更新

参考&#xff1a;黑马程序员之微服务 &#x1f4a5; 该系列属于【SpringBoot基础】专栏&#xff0c;如您需查看其他SpringBoot相关文章&#xff0c;请您点击左边的连接 目录 一、引言 二、配置共享 1. 添加共享配置到nacos &#xff08;1&#xff09;jdbc的共享配置 shared…

设计模式之Decorator装饰者、Facade外观、Adapter适配器(Java)

装饰者模式 设计模式的基本原则&#xff0c;对内关闭修改。 Decorator Pattern&#xff0c;装饰者模式&#xff0c;也叫包装器模式(Wrapper Pattern)&#xff1a;将一个对象包装起来&#xff0c;增加新的行为和责任。一定是从外部传入&#xff0c;并且可以没有顺序&#xff0…

望繁信科技入选2024年第3批上海市高新技术成果转化项目名单

近日&#xff0c;上海望繁信科技有限公司&#xff08;以下简称“望繁信科技”&#xff09;凭借其自主研发的“数字北极星流程挖掘分析软件”项目&#xff0c;成功入选2024年第3批上海市高新技术成果转化项目名单。这一殊荣根据《上海市高新技术成果转化项目认定办法》&#xff…

Keil Error-Flash Download failed Cortex-M4 擦除芯片还不好使的方案!!!

点击魔术棒-Debug-Settings后看到SWDIO可以正常识别&#xff0c;但是点击Reset下拉只有三个选项。 此时点击Pack&#xff0c;将Enable勾去掉。 回到Reset&#xff0c;此时多了Autodetet选项&#xff0c;选择这个选项后&#xff0c;即可正常烧录。

CLI举例:通过ISAKMP方式建立GRE over IPsec隧道

配置安全策略&#xff0c;允许私网指定网段进行报文交互&#xff0c;放行IKE协商报文。配置静态路由&#xff0c;保证两端路由可达。配置GRE Tunnel接口以及Tunnel口的转发路由。配置基于ACL的IPsec策略。GRE over IPsec中IPsec需要保护的数据流以GRE的起点为源、终点为目的。 …