对Arthas-Trace命令的一次深度剖析,竟发现...

前言:此文仅为笔者学习Arthas源码的一次尝试,不对本文结论负全部责任。

一、背景

笔者在学习arthas这个十分方便的小工具的过程中,发现:

目前据arthas官方解释:因为trace多层是十分消耗资源的,因此trace命令只会支持一层的耗时分析。
如果出现如下图的情况,trace命令的处理就会变得十分麻烦。

image.png

因此,是否可以考虑在arthas中增加一个命令或者给trace命令增加一个参数,来使其支持分析多层结果?
那么理应设立一种规则,如:

  • 展开最大耗时方法的明细
  • 展开大于多少耗时的方法明细
  • 展开耗时前n个方法的明细

本文假定将规则定为展开耗时前n个方法的调用明细。

二、一些初步尝试

2.1、正常trace命令流程

image.png

2.2、使用-E+多个类层级向下展开

一次Trace命令只会调用出一层方法耗时情况,那么在结果出来前应当是不知道哪个方法是耗时最长的,那么在不改变Trace底层逻辑的情况下,可以使用-E+ |来模拟到多层分析结果。例如:

2.2.1、使用Trace命令查看第一层调用情况

trace Main toPrint -n 5

image.png

2.2.2、将红框中的耗时最大的方法-的类名和方法名复制,写入命令中,然后再触发一次请求

trace -E Main|Main toPrint|toPrintD -n 5

image.png
可以看到,是可以实现多层展示的。

2.2.3、继续下钻

若需要继续下钻,则重复2.2.2的操作

2.2.4、总结

(本次案例结构简单,可能看上去不直观,可自行找复杂的场景试试)

这样做有两个不便之处:

  • 需要多次动用复制粘贴快捷键,来复制到新的命令中
  • 需要调用多次方法(但一般而言,调试过程中多调用几次,个人觉得影响不大)

三、源码分析

正所谓知己知彼方能百战不殆,接下来这部分通过分析源码,来了解shell上输入一个arthas命令执行从输入到输出,在这过程中arthas做了什么处理,怎么做到的。以jad java.lang.String为例。

这儿arthas已经是启动状态了,即arthas已经启动了监听器在监听命令行,就等着输入命令。

3.1、接收shell命令

首先会在com.taobao.arthas.core.shell.handlers.term.RequestHandler的accept方法,接收到该请求。参数line为输入的命令。

image.png

接着会调用handle方法,最终会走到ShellLineHandler的handle方法。

image.png

3.2、一些即时命令的处理&准备调用createJob

一些如jobs、fg等命令会直接处理返回,后续就不会创建job任务做处理。

image.png

其余情况,准备调用createJob方法。

image.png

3.3、createJob

这个方法会不断往深处调用,最终具体逻辑实现在com.taobao.arthas.core.shell.system.impl.JobControllerImpl#createJob。

这里有两个地方是主要的逻辑,一个是createProcess创建Process对象,一个是将创建的Process对象转换成job。这两步后面都会详说。

image.png

3.4、createProcess

这儿是先通过命令拿到command,然后创建用其创建Process返回。

image.png

image.png

3.5、createCommandProcess

除了这个方法会对管道符以及一些特殊命令做特殊处理外,就是对command做一个封装,吐出一个Process对象。

image.png

3.6、new JobImpl方法

在3.5后,一路返回走到3.3中所说的构建JobImpl方法。实际上也就是一个封装作用的方法,最后将构建好的job返回出去。

image.png

返回后,到3.2中的代码处,准备调用job.run方法。

image.png

3.7、job.run

实际上job.run也就是会去调用job中Process对象的run方法。最终也就是将process传入CommandProcessTask的构造方法,去创建线程跑。

image.png

CommandProcessTask的run方法实际上是在执行handler的handle方法去处理process。

image.png

1、handle方法会去调用command中的processHandler属性值去进行具体的命令处理。

2、接着AnnotatedCommandImpl的process方法最后又会去调用com.taobao.arthas.core.shell.command.AnnotatedCommand#process这个方法。

image.png

3、最后,AnnotatedCommand又如图一样多个子类,会去调用具体命令的AnnotatedCommand的子类的process方法。

image.png

此处即会走到JadComm的process方法。随即在此方法中进行详细处理,输出结果等。

image.png

四、设计方案&具体内容

4.1、方案A:模拟多层调用

image.png

4.2、方案B:Trace内部向下调用多层

image.png

4.3、方案分析

首先,大致讲一下Trace命令的执行原理。分两步:

第一步,在输入trace命令后,会对参数中需要监听的方法,进行字节码增强。

在com.taobao.arthas.core.command.monitor200.TraceAdviceListener#invokeBeforeTracing这个方法中插入,调试可以看到参数类中平层的所有方法都有调用到,如图中的toPrintB(i);toPrintD(i);toPrintE(i);三个方法均会进行字节码增强操作。

image.png

第二步,在监听的方法被调用后,输出相应的结果。

那么,理论上这儿可以在第一步进行字节码增强时进行多层字节码增强操作,但是此时耗时结果是没有出来的,即我们无法实现第一大节中假定的规则:展开耗时前n大方法的调用明细。最终可能会导致输出多层的全部调用链路,这显然是不合理的。那么理论上方案B实现效率比较低。

4.3.1、方案实现流程图

image.png

五、具体内容&效果

5.1、源码改动部分

5.1.1、TraceCommand新增参数
//新增参数analysisLen代表展开前n耗时方法
private int analysisLen = 0;
//绑定逻辑
@Option(shortName = "L", longName = "analysisLen")
@Description("the length to analysis time")
public void setAnalysisLen(int setLen) {this.analysisLen = setLen;
}
public int getAnalysisLen() {return analysisLen;
}
5.1.2、AbstractTraceAdviceListener新增新参数处理逻辑
//加在com.taobao.arthas.core.command.monitor200.AbstractTraceAdviceListener#finishing原逻辑if (conditionResult) {}前面if(command.getAnalysisLen() >= 1){//多层分析List<String> stringRusults = AnalysisUtils.analysis(command.getAnalysisLen(),process.args(),command,traceEntity);String traceOneMethod = "";//将stringRusults.get(2)-最后 加在该字符串for(int i=2;i<stringRusults.size();i++){traceOneMethod+=stringRusults.get(i);}process.end(1, "the longest "+command.getAnalysisLen()+" method is "+stringRusults.get(0)+" and you can use this command to get more info:\n"+stringRusults.get(1)+"\n"+traceOneMethod);}
5.1.3、AnalysisUtils工具类
public class AnalysisUtils {/** @Description: 将最长的n个方法以及优化的方法返回* @Param: [args, command, traceEntity]* @return: java.util.List<java.lang.String>* @Author: lz* @Date: 2023/5/19*/public static List<String> analysis(Integer num , List<String> args,TraceCommand command, TraceEntity traceEntity) {List<String> stringRusult = new ArrayList<>();//1、找到所有的叶子节点,并存在一个list里面List<MethodAnalysis> methodList = new ArrayList<>();buildMethodList(methodList,traceEntity.getModel().getRoot());//2、排序一下Collections.sort(methodList, new Comparator<MethodAnalysis>() {@Overridepublic int compare(MethodAnalysis o1, MethodAnalysis o2) {return o2.getTimeCost().compareTo(o1.getTimeCost());}});//3、取出最长的n个值 同时取一下方法名和类名StringBuilder methodLongestLength = new StringBuilder();StringBuilder classString = new StringBuilder(command.getClassPattern());StringBuilder methodStrign = new StringBuilder(command.getMethodPattern());for(int i = 0 ;i < num; i++){MethodAnalysis methodAnalysis = methodList.get(i);methodLongestLength.append(methodAnalysis.getMethodName());if(i != num-1){methodLongestLength.append(" and ");}classString.append("|").append(methodAnalysis.getClassName());methodStrign.append("|").append(methodAnalysis.getMethodName());}//考虑如何灵活赋值Integer classIndex = getArgsClassInxdex(args);args.set(classIndex, classString.toString());args.set(classIndex+1, methodStrign.toString());stringRusult.add(methodLongestLength.toString());//4、输出优化的方法String firstArg = "";if(command.isRegEx()){firstArg = "trace ";}else {firstArg = "trace -E ";}StringBuilder methodAfterDeal = new StringBuilder(firstArg);for(String childString : args){//遇到'-L'跳出if(childString.equals("-L")){break;}methodAfterDeal.append(childString);methodAfterDeal.append(" ");}stringRusult.add(methodAfterDeal.toString());//将每个单独的方法的trace命令也列出来for(int i = 0 ;i < num; i++){MethodAnalysis methodAnalysis = methodList.get(i);String oneTraceMethod = "";oneTraceMethod += "if you want just know the time-analysis for "+ methodAnalysis.getClassName()+"."+methodAnalysis.getMethodName();oneTraceMethod += " you can use this command: \n"+"trace "+methodAnalysis.getClassName()+" "+methodAnalysis.getMethodName()+" -n 5\n";stringRusult.add(oneTraceMethod);}//调试debug
//        String de = "";
//        de = JSONObject.toJSONString(methodList);
//        stringRusult.add(de);return stringRusult;}private static Integer getArgsClassInxdex(List<String> args) {//获取class的index,// 如trace -E com.dao.xxxx.controler 为2// 如trace com.dao.xxx.contoller 为1for(int i = 0 ; i < args.size(); i++){//-EString arg = args.get(i);if(arg.equals("-E")) {continue;}if(arg.equals("--skipJDKMethod")){continue;}if(arg.equals("false")){continue;}if(arg.equals("true")){continue;}return i;}return 0;}private static void buildMethodList(List<MethodAnalysis> methodList, TraceNode root) {if(root.getChildren() == null){MethodNode node = (MethodNode) root;MethodAnalysis methodAnalysis = new MethodAnalysis();methodAnalysis.setMethodName(node.getMethodName());methodAnalysis.setClassName(node.getClassName());methodAnalysis.setTimeCost(BigDecimal.valueOf(node.getTotalCost()));methodList.add(methodAnalysis);}else {for(TraceNode node : root.getChildren()){buildMethodList(methodList , node);}}}private static void getAllJsonString( TraceNode root,String re) {String s = JSONObject.toJSONString(root);re+=s;if(root.getChildren() == null){return;}for(TraceNode node : root.getChildren()){getAllJsonString(node,re);}}protected static class MethodAnalysis{private String methodName;private String className;private BigDecimal timeCost;//省略set get等方法}}
5.1.4、其它

其它的是前期学习阶段,对pom文件的一些改动。如拉取的版本为3.6.7改为3.6.8、在sore模块中加了个maven的编译插件等。

  • 1、如果编译不成功,报一些包找不到的话。可能是jdk版本不对,官方说法是jdk8一定可以编译成功,我下载了很多版本,最终用的评论区推荐的jdk1.8.0_372。
  • 2、本地windows编译,推荐使用执行as-package.sh,将最新代码编译打包至C:\Users\用户名\.arthas\lib中。详情可点击->官方链接。

5.2、效果

image.png

  • 可以看到,在Trace命令后加入-L 2 后,结果将返回耗时最长的两个方法,并将后续可以直接深入查看这两个方法的执行情况的Trace命令返回。
  • 这儿还分别将单独Trace这两个方法的命令给出来了,算是一个小优化,如果不关心父方法的耗时情况,就可以直接往下Trace。

5.3、总结

1、本次方案是将2.2的步骤交给arthas执行,提高排查效率,减轻开发人员的操作成本。

2、当-L后的参数过大时,可能会超时,后续考虑优化分析效率。

六、后续升级思路

目前是需要两次调用来拿到最终数据。如果需要一次trace命令直接拿到最终数据,就需要在代码字节码增强时就直接将两层全部插入字节码。然后接口触发时,将原本应当返回多层的耗时分析树进行剪枝,如将所有的叶子节点的上一层进行比较,保留唯n的分枝,其余全部去除,最后返回。(思路如此,但感觉会超时,因为分析一旦超时,就无法正常地将TraceNode正常转换为MethodNode,或者说无法得到准确的分析数据)

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

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

相关文章

【期刊出版征稿】2024年艺术、教育和管理国际学术会议(ICAEM2024)

2024年艺术、教育和管理国际学术会议 2024 International Conference on Arts, Education and Management&#xff08;ICAEM2024&#xff09; 2024年艺术、教育和管理国际学术会议&#xff08;ICAEM2024&#xff09;将于2024年2月02-04日在马来西亚-吉隆坡召开。会议主题主要…

跨境助手:提升跨境电商卖家运营效率的利器

在如今全球化的商业环境中&#xff0c;跨境电商成为越来越多卖家追逐的商机。然而&#xff0c;对于新手卖家来说&#xff0c;跨境电商的复杂性和竞争激烈的市场环境可能会成为入坑的风险。如何降低风险、提高运营效率成为卖家们关注的焦点。而跨境助手作为一款专为跨境电商卖家…

Python Pandas 如何增加/插入一列数据(第5讲)

Python Pandas 如何增加/插入一列数据(第5讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

Python实现冰墩墩

目录 一、运行效果 图片效果 二、项目概述 三、开发环境 四、实现步骤及代码 1.导入需要的库。 2.完成剩余部分代码。 五、项目总结 六、源码获取 一、运行效果 图片效果 二、项目概述 这个项目使用了turtle库绘制了一个编程乐学的Logo。Logo中包含了一个笑脸&#xf…

SpringBoot接入轻量级分布式日志框架GrayLog

1.前言 日志在我们日常开发定位错误&#xff0c;链路错误排查时必不可少&#xff0c;如果我们只有一个服务&#xff0c;我们可以只简单的通过打印的日志文件进行排查定位就可以&#xff0c;但是在分布式服务环境下&#xff0c;多个环境的日志统一收集、展示则成为一个问题。目…

基于CNN+数据增强+残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)+数据集+模型(一)

系列文章目录 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xff08;一&#xff09; 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xf…

1U、2U、4U和42U服务器,看完秒懂!

晚上好&#xff0c;我的网工朋友。 服务器是一个很广泛的概念&#xff0c;涵盖了各种类型和规格的计算机&#xff0c;用于提供各种网络和数据服务。 而机架服务器是当前数据中心和专业计算环境中&#xff0c;使用最为广泛的服务器类型之一。 机架式服务器的外形看来不像计算…

C++面试宝典第7题:重载自增自减运算符

题目 编程实现一个自定义类CMyInteger,它重载了前缀和后缀形式的++和--操作符。同时,CMyInteger类还有一个Print成员函数,用于输出内部成员变量的值。完成该类后,下面使用CMyInteger的代码应能够编译通过,并得到与内置整形int相同的效果。 int main() {CMyInteger mi1(10…

考研英语一图表作文必背模版句

英语一的作文还是很靠日常积累的&#xff0c;依据潘赟老师的九宫格理论&#xff1a; 2——图画描述5——意义论证8——建议措施 这3个模块式最为核心也是最容易拉开分差的&#xff0c;对于时间有限的同志不建议忙下功夫浪费时间&#xff0c;而对于另外6个模块&#xff0c;还是…

Flink系列之:自定义函数

Flink系列之&#xff1a;自定义函数 一、自定义函数二、概述三、开发指南四、函数类五、求值方法六、类型推导七、自动类型推导八、定制类型推导九、确定性十、内置函数的确定性十一、运行时集成十二、标量函数十三、表值函数十四、聚合函数十五、表值聚合函数 一、自定义函数 …

【深度学习目标检测】八、基于yolov5的抽烟识别(python,深度学习)

YOLOv5是目标检测领域一种非常优秀的模型&#xff0c;其具有以下几个优势&#xff1a; 1. 高精度&#xff1a;YOLOv5相比于其前身YOLOv4&#xff0c;在目标检测精度上有了显著的提升。YOLOv5使用了一系列的改进&#xff0c;如更深的网络结构、更多的特征层和更高分辨率的输入图…

Git及Linux命令介绍

Git介绍 Git 命令如何工作 首先&#xff0c;必须确定我们的代码存储在哪里。常见的假设是只有两个位置 - 一个位于 Github 等远程服务器上&#xff0c;另一个位于我们的本地计算机上。然而&#xff0c;这并不完全准确。 Git 在我们的机器上维护了三个本地存储&#xff0c;这意…

Linux-----12、时间日期

# 时间日期 # 时区设置 在Linux (opens new window)系统中&#xff0c;默认使用的是UTC时间。 即使在安装系统的时候&#xff0c;选择的时区是亚洲上海&#xff0c;Linux默认的BIOS时间&#xff08;也称&#xff1a;硬件时间&#xff09;也是UTC时间 (opens new window)。 在…

90%的人学Python爬虫都干过这种事,别不承认!

可以说&#xff0c;我是因为想批量下载一个网站的图片&#xff0c;才开始学的python爬虫。当一张一张图片自动下载下来时&#xff0c;满满的成就感&#xff0c;也满满的罪恶感……哈哈哈&#xff01;&#xff01;&#xff01;窈窕淑女&#xff0c;君子好逑&#xff0c;这篇文章…

Android 大版本升级变更截图方法总结

Android 大版本升级变更截图方法总结 一、Android R (11) 平台二、Android S (12) 平台三、Android U (14) 平台 Android 原生的截屏功能是集成在 SystemUI 中&#xff0c;因此我们普通应用想要获取截图方法&#xff0c;就需要研读下 SystemUI 截屏部分的功能实现。 一、Androi…

Android 移动端编译 cityhash动态库

最近做项目&#xff0c; 硬件端 需要 用 cityhash 编译一个 动态库 提供给移动端使用&#xff0c;l 记录一下 编译过程 city .cpp // // Created by Administrator on 2023/12/12. // // Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charg…

java配置+J_IDEA配置+git配置+maven配置+基本语句

当前目录文件夹dir 进入文件夹cd 返回上一级cd.. 创建文件夹&#xff1a;mkdir 文件名删除文件夹&#xff1a;rd 文件夹名&#xff0c; 目录不为空不能直接删 rd /s 带子文件夹一起删 清屏cls 切换d盘才能进入 下载git地址&#xff1a; Git - Downloading Package (g…

使用youtube的api

如何使用youtube的data api https://console.cloud.google.com/apis/dashboard 到这个地方先启用api,找到YouTube Data API v3 这个api,启用它 然后创建凭据 去创建凭据,里面创建相应的客户端,web的需要填写redirect地址,就是回调用的.客户端不需要这个. 创建客户端不需要详…

一文读懂Allins-首个基于 AMM 的多链铭文资产交易协议

“Allins 是铭文赛道中基础设施类的代表&#xff0c;该协议致力于以 AMM 的方式推动铭文资产的流动性&#xff0c;并为铭文资产交易者提供更好的 UI/UX。” 2023年1月份后&#xff0c;比特币Ordinals协议的推出为铭文赛道的兴起奠定了基础。该协议以聪为单位将比特币划分&#…

ipa分发平台绑定域名有什么优势

大家好我是咕噜签名分发可爱多。今天跟大家分享一下&#xff0c;为什么建议大家将自己的域名绑定到分发平台&#xff08;比如咕噜分发&#xff09;。 将自己的域名绑定分发平台有几个原因和优势&#xff1a; 1. 专业性和品牌建设&#xff1a; 使用自己的域名可以让您的在线存…