0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

测试和性能监控神器JMH和Arthas

jf_ro2CN3Fa 来源:芋道源码 作者:芋道源码 2022-11-09 17:19 次阅读

最近的工作日并不算太平,各种大大小小的case和解case,发现已经有好久好久没有静下心来专心写点东西了。不过倒还是坚持利用业余时间学习了不少微课上的东西,发现大佬们总结的东西还是不一样,相比于大学时的那些枯燥的课本,大佬们总结出来的内容更活,更加容易理解。自己后面也会把大佬们的东西好好消化吸收,变成自己的东西用文字性的东西表达出来。

今天想总结的东西是最近工作中使用到的测试工具JMH以及Java运行时监控工具Arthas。他们在我的实际工作中也算是帮了大忙。所以在这里抛砖引玉一下这些工具的使用方法。同时也加深一下自己对这些工具的熟悉程度。对这两个工具,我都会首先简单介绍一下这些工具的大致使用场景,然后会使用一个在工作中真正遇到的性能问题排查为例详细讲解这两个工具的实际用法。话不多说,直奔主题。

问题描述

为了能够让我后面的实例能够贯穿这两个工具的使用,我首先简单描述下我们在开发中遇到的实际的性能问题。然后再引出这两个性能工具的实际使用,看我们如何使用这两个工具成功定位到性能瓶颈的。

问题如下:为了能够支持丢失率,我们将原先log4j2 的Async+自定义Appender的方式进行了修正,把异步的逻辑放到了自己改版后的Appender中。但我们发现修改后日志性能要比之前Async+自定义Appender的方式下降不少。这里由于隐私原因我并没有用实际公司中的实例,这里我用了一种其他同样能够体现问题的方式。我们暂时先不给出具体的配置文件,先给出性能测试代码和结果

代码

packagecom.bryantchang.appendertest;

importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;

publicclassAppenderTest{

privatestaticfinalStringLOGGER_NAME_DEFAULT="defaultLogger";
privatestaticfinalStringLOGGER_NAME_INCLUDE="includeLocationLogger";
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(LOGGER_NAME_INCLUDE);
publicstaticfinallongBATCH=10000;

publicstaticvoidmain(String[]args)throwsInterruptedException{
while(true){
longstart,end;
start=System.currentTimeMillis();
for(inti=0;i< BATCH; i++) {
                LOGGER.info("msg is {}", i);
            }
            end = System.currentTimeMillis();
            System.out.println("duration of " + LOGGER_NAME_INCLUDE +  " is " + (end - start) + "ms");
            Thread.sleep(1000);
        }
    }
}

代码逻辑及其简单,就是调用logger.info每次打印10000条日志,然后记录耗时。两者的对比如下

117551d0-5cb1-11ed-a3b6-dac502259ad0.png

从这两张图片中我们能够看到同样的逻辑,两个程序的耗时差距相差了数十倍,但看图片,貌似仅仅是logger的名称不一样。对上面的实验结果进行分析,我们可能会有两个疑问

上面的代码测试是否标准,规范

如果真的是性能问题,那么这两个代码到底在哪个方法上有了这么大的差距导致了最终的性能差异

下面这两个工具就分别来回答这两个问题

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

JMH简介

第一个问题就是,测试的方法是否标准。我们在编写代码时用的是死循环+前后“掐秒表”的方式。假如我们要再加个多线程的测试,我们还需要搞一个线程池,除了代码本身的逻辑还要关心测试的逻辑。我们会想,有没有一款工具能够将我们从测试逻辑中彻底解放出来,只需要关心我们需要测试的代码逻辑。JMH就是这样一款Java的测试框架。下面是JMH的官方定义

JMH是一个面向Java语言或者其他Java虚拟机语言的性能基准测试框架

这里面我们需要注意的是,JMH所测试的方法约简单越好,依赖越少越好,最适合的场景就是,测试两个集合put,get性能,例如ArrayList与LinkedList的对比等,这里我们需要测试的是批量打一批日志所需要的时间,也基本符合使用JMH的测试场景。下面是测试代码,bench框架代码以及主函数。

待测试方法

publicclassLogBenchMarkWorker{

privateLogBenchMarkWorker(){}

privatestaticclassLogBenchMarkWorkerInstance{
privatestaticfinalLogBenchMarkWorkerinstance=newLogBenchMarkWorker();
}

publicstaticLogBenchMarkWorkergetInstance(){
returnLogBenchMarkWorkerInstance.instance;
}

privatestaticfinalStringLOGGER_DEFAULT_NAME="defaultLogger";
privatestaticfinalStringLOGGER_INCLUDE_LOCATION="includeLocationLogger";
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(LOGGER_DEFAULT_NAME);
privatestaticlongBATCH_SIZE=10000;

publicvoidlogBatch(){
for(inti=0;i< BATCH_SIZE; i++) {
            LOGGER.info("msg is {}", i);
        }
    }
}

可以看到待测试方法非常简单,就是单批次一次性打印10000条日志的操作,所以并没有需要额外说明的部分。下面我们再来看benchmark部分。

publicclassLogBenchMarkMain{

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value=1)
@Threads(1)
publicvoidtestLog1(){
LogBenchMarkWorker.getInstance().logBatch();
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value=1)
@Threads(4)
publicvoidtestLog4(){
LogBenchMarkWorker.getInstance().logBatch();
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value=1)
@Threads(8)
publicvoidtestLog8(){
LogBenchMarkWorker.getInstance().logBatch();
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value=1)
@Threads(16)
publicvoidtestLog16(){
LogBenchMarkWorker.getInstance().logBatch();
}

在这段代码中,我们就会发现有了一些JMH中特有的东西,我下面进行简要介绍。

Benchmark注解:标识在某个具体方法上,表示这个方法将是一个被测试的最小方法,在JMH中成为一个OPS
BenmarkMode:测试类型,JMH提供了几种不同的Mode
    Throughput:整体吞吐量
    AverageTime:调用的平均时间,每次OPS执行的时间
    SampleTime:取样,给出不同比例的ops时间,例如99%的ops时间,99.99%的ops时间
fork:fork的次数,如果设置为2,JMH会fork出两个进程来测试
Threads:很容易理解,这个参数表示这个方法同时被多少个线程执行

在上面的代码中,我定义了4个待测试的方法,方法的Fork,BenchmarkMode均为测试单次OPS的平均时间,但4个方法的线程数不同。除了这几个参数,还有几个参数,我会在main函数里面来讲,main代码如下所示

publicclassMain{
publicstaticvoidmain(String[]args)throwsRunnerException{
Optionsoptions=newOptionsBuilder()
.include(LogBenchMarkMain.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.output("logs/BenchmarkCommon.log")
.build();
newRunner(options).run();
}
}

我们可以看到,在main函数中,我们就是要设置JMH的基础配置,这里面的几个配置参数含义如下:

include:benchmark所在类的名字,可以使用正则表达
warmupIteration:预热的迭代次数,这里为什么要预热的原因是由于JIT的存在,随着代码的运行,会动态对代码的运行进行优化。因此在测试过程中需要先预热几轮,让代码运行稳定后再实际进行测试
measurementIterations:实际测试轮次
output:测试报告输出位置

我分别用两种logger运行一下测试,查看性能测试报告对比

使用普通logger

LogBenchMarkMain.testLog1avgt50.006±0.001s/op
LogBenchMarkMain.testLog16avgt50.062±0.026s/op
LogBenchMarkMain.testLog4avgt50.006±0.002s/op
LogBenchMarkMain.testLog8avgt50.040±0.004s/op

使用了INCLUDE_LOCATION的logger

LogBenchMarkMain.testLog1avgt50.379±0.007s/op
LogBenchMarkMain.testLog16avgt51.784±0.670s/op
LogBenchMarkMain.testLog4avgt50.378±0.003s/op
LogBenchMarkMain.testLog8avgt50.776±0.070s/op

这里我们看到,性能差距立现。使用INCLUDE_LOCATION的性能要明显低于使用普通logger的性能。这是我们一定很好奇,并且问一句“到底慢在哪”!!

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://gitee.com/zhijiantianya/yudao-cloud

视频教程:https://doc.iocoder.cn/video/

Arthas 我的代码在运行时到底做了什么

Arthas是阿里开源的一款java调试神器,与greys类似,不过有着比greys更加强大的功能,例如,可以直接绘制java方法调用的火焰图等。这两个工具的原理都是使用了Java强大的字节码技术。毕竟我也不是JVM大佬,所以具体的实现细节没法展开,我们要做的就是站在巨人的肩膀上,接受并用熟练的使用好这些好用的性能监控工具。

实际操作

talk is cheap, show me your code,既然是工具,我们直接进行实际操作。我们在本机运行我们一开始的程序,然后讲解arthas的使用方法。

我们首先通过jps找到程序的进程号,然后直接通过arthas给到的as.sh对我们运行的程序进行字节码增强,然后启动arthas,命令如下

./as.shpid
119b0164-5cb1-11ed-a3b6-dac502259ad0.png

可以看到,arthas支持查看当前jvm的状态,查看当前的线程状态,监控某些方法的调用时间,trace,profile生成火焰图等,功能一应俱全 我们这里也只将几个比较常用的命令,其他的命令如果大家感兴趣可以详见官网arthas官网。这篇文章主要介绍下面几个功能

反编译代码

监控某个方法的调用

查看某个方法的调用和返回值

trace某个方法

监控方法调用

这个主要的命令为monitor,根据官网的介绍,常用的使用方法为

monitor-cdurationclassNamemethodName

其中duration代表每隔几秒展示一次统计结果,即单次的统计周期,className就是类的全限定名,methodname就是方法的名字,这里面我们查看的方法是Logger类的info方法,我们分别对使用两种不同logger的info方法。这里面的类是org.slf4j.Logger,方法时info,我们的监控语句为

monitor-c5org.slf4j.Loggerinfo

监控结果如下

使用普通appender

11b7c88a-5cb1-11ed-a3b6-dac502259ad0.png

我们可以看到,使用include appeder的打印日志方法要比普通的appender高出了3倍,这就不禁让我们有了疑问,究竟这两个方法各个步骤耗时如何呢。下面我们就介绍第二条命令,trace方法。

trace命令 & jad命令

这两个程序的log4j2配置文件如下


 

 

 

 

 












 
 




 





 














 

 








 






我们都是用了一个AsyncAppender套用了一个FileAppender。查看fileAppender,发现二者相同完全没区别,只有asyncAppender中的一个选项有区别,这就是includeLocation,一个是false,另一个是true。至于这个参数的含义,我们暂时不讨论,我们现在知道问题可能出在AsyncAppender里面,但是我们该如何验证呢。trace命令就有了大用场。

trace命令的基本用法与monitor类似,其中主要的一个参数是-n则是代表trace多少次的意思

trace-ntrace_timesclassNamemethodName

我在之前Log4j2的相关博客里面讲到过,任何一个appender,最核心的方法就是他的append方法。所以我们分别trace两个程序的append方法。

trace-n5org.apache.logging.log4j.core.appender.AsyncAppenderappend

trace结果如下

使用普通appender

11f36f48-5cb1-11ed-a3b6-dac502259ad0.png

我们立刻可以发现,两个trace的热点方法不一样,在使用include的appender中,耗时最长的方法时org.apache.logging.log4j.core.impl.Log4jLogEvent类中的createMemento方法,那么怎么才能知道这个方法到底做了啥呢,那就请出我们下一个常用命令jad,这个命令能够反编译出对应方法的代码。这里我们jad一下上面说的那个createMemento方法,命令很简单

jadorg.apache.logging.log4j.core.impl.Log4jLogEventcreateMemento

结果如下

12397cae-5cb1-11ed-a3b6-dac502259ad0.png

watch命令

watch命令能够观察到某个特定方法的入参,返回值等信息,我们使用这个命令查看一下这个createMemento方法的入参,如果两个程序的入参不同,那基本可以断定是这个原因引起命令如下

watchorg.apache.logging.log4j.core.impl.Log4jLogEventcreateMemento"params"-x2-n5-b-f

这里面的参数含义如下

-x参数展开层次
-n执行次数
-b查看方法调用前状态
-f方法调用后

其中的param代表查看方法的调用参数列表,还有其他的监控项详见官网官网

最终watch结果如下

使用普通logger

12540a60-5cb1-11ed-a3b6-dac502259ad0.png

果不其然,这两个参数果然是一个true一个false,我们简单看下这个参数是如何传进来的,我们jad一下AsyncAppender的append方法。

12893262-5cb1-11ed-a3b6-dac502259ad0.png

不过为了一探究竟,我还是静态跟了一下这段代码

这个includeLocation会在event的createMemento中被用到,在序列化生成对象时会创建一个LogEventProxy,代码如下

publicLogEventProxy(finalLogEventevent,finalbooleanincludeLocation){
this.loggerFQCN=event.getLoggerFqcn();
this.marker=event.getMarker();
this.level=event.getLevel();
this.loggerName=event.getLoggerName();

finalMessagemsg=event.getMessage();
this.message=msginstanceofReusableMessage
?memento((ReusableMessage)msg)
:msg;
this.timeMillis=event.getTimeMillis();
this.thrown=event.getThrown();
this.thrownProxy=event.getThrownProxy();
this.contextData=memento(event.getContextData());
this.contextStack=event.getContextStack();
this.source=includeLocation?event.getSource():null;
this.threadId=event.getThreadId();
this.threadName=event.getThreadName();
this.threadPriority=event.getThreadPriority();
this.isLocationRequired=includeLocation;
this.isEndOfBatch=event.isEndOfBatch();
this.nanoTime=event.getNanoTime();
}

如果includeLocation为true,那么就会调用getSource函数,跟进去查看,代码如下

publicStackTraceElementgetSource(){
if(source!=null){
returnsource;
}
if(loggerFqcn==null||!includeLocation){
returnnull;
}
source=Log4jLogEvent.calcLocation(loggerFqcn);
returnsource;
}
publicstaticStackTraceElementcalcLocation(finalStringfqcnOfLogger){
if(fqcnOfLogger==null){
returnnull;
}
//LOG4J2-1029newThrowable().getStackTraceisfasterthanThread.currentThread().getStackTrace().
finalStackTraceElement[]stackTrace=newThrowable().getStackTrace();
StackTraceElementlast=null;
for(inti=stackTrace.length-1;i>0;i--){
finalStringclassName=stackTrace[i].getClassName();
if(fqcnOfLogger.equals(className)){
returnlast;
}
last=stackTrace[i];
}
returnnull;
}

我们看到他会从整个的调用栈中去寻找调用这个方法的代码行,其性能可想而知。我们用arthas监控一下,验证一下。

首先我们trace crateMemento方法

trace-n5org.apache.logging.log4j.core.impl.Log4jLogEventcreateMemento
12ad86b2-5cb1-11ed-a3b6-dac502259ad0.png
trace-n5org.apache.logging.log4j.core.impl.Log4jLogEventserialize
12e6ff78-5cb1-11ed-a3b6-dac502259ad0.png
trace-n5org.apache.logging.log4j.core.impl.Log4jLogEvent$LogEventProxy
130ec940-5cb1-11ed-a3b6-dac502259ad0.png
trace-n5trace-n5org.apache.logging.log4j.core.LogEventgetSource
135258ae-5cb1-11ed-a3b6-dac502259ad0.png

至此我们通过结合JMH和arthas共同定位出了一个线上的性能问题。不过我介绍的只是冰山一角,更多常用的命令还希望大家通过官网自己了解和实践,有了几次亲身实践之后,这个工具也就玩熟了。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 监控
    +关注

    关注

    6

    文章

    2178

    浏览量

    55103
  • 代码
    +关注

    关注

    30

    文章

    4753

    浏览量

    68369
  • Case
    +关注

    关注

    0

    文章

    27

    浏览量

    13356

原文标题:测试和性能监控神器 JMH & Arthas

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    无线网络智能摄像机 店铺安防监控必备神器

    最不省心的问题有两个:一是就是夜里的防盗问题,二是店里的员工有没有消极怠工、偷懒打游戏,甚至有没有挪用收银台的钱。下面给大家分享Vimtag智能摄像机店铺监控必备神器。  说到店铺安装智能摄像机,有以下
    发表于 06-07 15:25

    使用测试工具iPerf监控无线网络性能(转)

    使用测试工具iPerf监控无线网络性能(转)
    发表于 08-17 12:37

    数十万应用结点全息监控,ARMS新上线的应用监控神器到底有多牛?

    的应用的高效运行。而ARMS的“应用监控”就是保障这些复杂应用有效运行的秘密武器。近日,阿里中间件(Aliware)将该功能正式对外开放,目的是为用户在应用性能管理方面提供更多便利。在微服务时代,IT企业
    发表于 12-25 13:55

    变量命名神器

    中国程序员开发的神奇网站:变量命名神器
    发表于 05-31 06:44

    linux服务器性能测试步骤

    linux服务器性能测试-服务器实时【磁盘】监控
    发表于 06-02 06:54

    移动APP测试的android性能测试

    当应用实现了新功能后,准备发布版本前,必须进行性能测试以确定没有性能问题,内存使用情况便是其中必须要测试性能之一。由于内存组成的复杂性,并
    发表于 06-09 16:17

    NXP电力变换的神器

    其实很早之前在公众号给大家推送过NXP的这个神器,freeMaster,这个工具其实已经好多年了,在最早的飞思卡尔早期就存在,一直是工程师调试电机,电力变换的神器,确实好...
    发表于 09-17 06:48

    CoreSight性能监控单元架构

    体系结构包括当计数器达到阈值时产生中断的机制。 在CoreSight性能监视单元体系结构中,事件计数器是单调增加的。但是,在某些情况下,PMU提供监控器来测量组成部分例如,监控器可能在分配资源时递增
    发表于 08-09 07:20

    Arthas可以解决哪些问题

    诊断利器 Arthas,是阿里的一款开源工具。Github-alibaba/arthas 上可以看到它的介绍
    的头像 发表于 12-24 14:22 757次阅读

    国产测试工具神器ATECLOUD智能云测试平台技术特点

    语言编程,让不懂代码的人也可以快速搭建测试方案,让测试测量更直观、更简单。本篇文章纳米软件Namisoft小编将为大家分享一下关于:对标Labview的国产测试工具神器ATECLOUD
    发表于 09-28 15:43 1547次阅读
    国产<b class='flag-5'>测试</b>工具<b class='flag-5'>神器</b>ATECLOUD智能云<b class='flag-5'>测试</b>平台技术特点

    Arthas -- 一款释放潜力的神器

    阿里巴巴 Arthas 是一个诊断工具,可以用于监视、分析和解决 Java 应用程序的问题。使用 Arthas 的一个主要优点是,我们不需要修改代码,甚至不需要重新启动我们想要监视的 Java 服务。
    的头像 发表于 07-05 11:21 533次阅读
    <b class='flag-5'>Arthas</b> -- 一款释放潜力的<b class='flag-5'>神器</b>

    基于Java的阿里巴巴Arthas调优方案

    在 main 方法中,我们使用一个循环和相对较大的数字,以便让计算机进行较长时间的计算。这当然正是我们想要的,以便演示 Arthas
    发表于 07-05 11:19 137次阅读
    基于Java的阿里巴巴<b class='flag-5'>Arthas</b>调优方案

    介绍一款基于java的渗透测试神器-CobaltStrike

    Cobalt Strike是一款基于java的渗透测试神器,常被业界人称为CS神器
    的头像 发表于 01-16 09:16 902次阅读
    介绍一款基于java的渗透<b class='flag-5'>测试</b><b class='flag-5'>神器</b>-CobaltStrike

    性能测试主要测什么 性能测试的指标有哪些

    性能测试是软件测试的一个重要组成部分,主要用于评估软件系统在各种负载条件下的性能表现。性能测试
    的头像 发表于 05-29 15:42 2153次阅读

    使用Arthas火焰图工具的Java应用性能分析和优化经验

    分享作者在使用Arthas火焰图工具进行Java应用性能分析和优化的经验。
    的头像 发表于 10-28 09:27 172次阅读
    使用<b class='flag-5'>Arthas</b>火焰图工具的Java应用<b class='flag-5'>性能</b>分析和优化经验