Arthas是Alibaba开源的一款Java诊断工具,其提供了丰富的命令。这里介绍其中的trace、monitor、watch命令

trace
可以跟踪指定方法调用的耗时,为便于演示,提供下述测试代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | package com.aaron.Arthas;
 import org.junit.Test;
 
 public class TestTrace {
 
 @Test
 public void testMain() throws InterruptedException {
 while (true) {
 addUserController();
 }
 }
 
 
 private void addUserController() throws InterruptedException {
 serviceA();
 serviceB();
 Thread.sleep(10 * 1000);
 }
 
 
 private void serviceA() throws InterruptedException {
 Thread.sleep( 3 * 1000);
 add2Mysql();
 add2Redis();
 }
 
 
 private void serviceB() throws InterruptedException {
 Thread.sleep(20 * 1000);
 add2Redis();
 }
 
 
 private void add2Mysql() throws InterruptedException {
 Thread.sleep( 8 * 1000 );
 }
 
 
 private void add2Redis() throws InterruptedException {
 Thread.sleep( 4 * 1000 );
 }
 }
 
 | 
基本实践
该命令用法如下所示
| 12
 3
 4
 5
 6
 7
 8
 
 | trace <类名> <方法名>
 
 
 trace com.aaron.Arthas.TestTrace addUserController
 
 
 trace -n 5 com.aaron.Arthas.TestTrace addUserController
 
 | 
不难看出,trace命令只会跟踪、统计被匹配到的函数里的子调用,并不会向下trace多层

统计JDK方法调用
默认情况下,trace结果中不会对JDK方法调用进行耗时统计。故如果期望含有对JDK方法调用的耗时统计,需显式使用 —skipJDKMethod false 选项
| 12
 
 | trace --skipJDKMethod false com.aaron.Arthas.TestTrace addUserController
 
 | 

支持正则匹配
trace命令提供了-E选项,可以通过正则表达式来匹配多个类、方法。在一定程度上可以达到实现多层trace对效果
| 12
 
 | trace -E com.aaron.Arthas.TestTrace addUserController|serviceA|serviceB
 
 | 

| 12
 
 | trace -E com.aaron.Arthas.TestTrace add2.*
 
 | 

动态trace
我们在终端1对addUserController进行trace,从下述输出中不难看出,其listenerId为7

假设我们现在期望对serviceA方法中的调用耗时进行分析,则可以在终端2中,先使用telnet连接Arthas,然后使用trace命令跟踪serviceA方法。与此同时在trace命令中使用—listenerId选项指定在终端1中看到的listenerId值即可
| 12
 3
 
 | telnet localhost 3658
 trace com.aaron.Arthas.TestTrace serviceA --listenerId 7
 
 | 

现在我们回到终端1进行观察,即会发现输出结果中包含了对servcieA方法的调用耗时分析结果。事实上通过这种方式,我们可以不断按需深入trace

monitor
该命令可以对方法进行监控,监控指标包括调用次数、成功次数、失败次数、平均RT(平均响应时间)、失败率。其中这里失败的判定标准为方法执行抛出异常。这里提供如下的测试代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | package com.aaron.Arthas;
 import org.junit.Test;
 import java.util.Random;
 
 public class TestMonitor {
 @Test
 public void test1() {
 Random random = new Random();
 while (true) {
 try {
 if( random.nextInt(10) < 5 ) {
 calc(true);
 } else {
 calc(false);
 }
 } catch (Exception e) {
 }
 }
 }
 
 private void calc(boolean flag) throws InterruptedException {
 if( flag==true ) {
 throw new RuntimeException();
 } else {
 Thread.sleep( 700 );
 }
 }
 }
 
 | 
使用方式很简单,指定类名、方法名即可。同时-c选项表示统计周期,单位为秒。默认为120秒
| 1
 | monitor -c 5  com.aaron.Arthas.TestMonitor calc
 | 

还可以通过-b选项搭配条件表达式,实现对特定场景下的方法调用进行统计
| 12
 3
 4
 5
 
 | monitor -b -c 5  com.aaron.Arthas.TestMonitor calc "params[0] == false"
 
 
 monitor -b -c 5  com.aaron.Arthas.TestMonitor calc "params[0] == true"
 
 | 

watch
便于观察指定方法的调用情况。具体地,可以观察到:方法入参、方法返回值、方法抛出异常。故watch命令定义了4个观察事件点。并提供了对应的方法选项
-b:在方法被调用前进行观察。当使用该选项时,由于观察事件点是在方法被调用之前,此时方法的返回值或异常均无法观察到。即只能观察到方法的入参
-e:在方法调用抛出异常时,进行观察。
-s:在方法调用正常返回时,进行观察。
-f:在方法调用结束后进行观察。显然该选项即会观察到方法抛出异常的情形,也会观察到方法正常返回的情形。除了该选项默认打开外,上述3个选项均默认关闭
为了便于演示,提供下述测试代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | package com.aaron.Arthas;
 import org.junit.Test;
 import java.util.Random;
 
 public class TestWatch {
 private static final String AUTHOR = "Aaron Zhu";
 
 private Random random;
 
 @Test
 public void test1() {
 random = new Random();
 while (true) {
 try {
 Thread.sleep(1000);
 int num = random.nextInt(10);
 String msg = calc(num, "Hello, ");
 System.out.println(msg);
 } catch (Exception e) {
 }
 }
 }
 
 private String calc(int num, String info) {
 String res = null;
 if( num>=0 && num<=2 ) {
 throw new IllegalArgumentException("Number is Error !!");
 } else if ( num>=3 && num<=5 ) {
 res = "Dog : " + num;
 }  else if ( num>=6 && num<=9 ) {
 res = "Cat :" + num;
 }
 
 res = info + res;
 return res;
 }
 
 }
 
 | 
观察方法调用时的入参
我们通过 -b选项 观察TestWatch中calc方法被调用时的方法入参,同时指定观察表达式 {params,target,returnObj} ,其中params、target、returnObj分别表示入参、调用类的实例、返回值。 -x选项 则用于指定对输出结果中属性遍历的深度,默认为1,最大为4
| 12
 
 | watch -b -x 2 com.aaron.Arthas.TestWatch calc "{params,target,returnObj}"
 
 | 
下述红框表示观察表达式的第1个元素params,即方法的入参;下述蓝框表示观察表达式的第2个元素target,即调用方法的实例;下述蓝框表示观察表达式的第3个元素returnObj,即方法的出参。此外输出结果的location值为AtEnter,表示观察事件的位置在函数入口

观察方法调用时的返回值
| 12
 
 | watch -s -x 2 com.aaron.Arthas.TestWatch calc "{params,target,returnObj}"
 
 | 
此外输出结果的location值为AtExit,表示观察事件的位置在函数正常返回

观察方法调用时抛出的异常
在观察表达式中,可使用throwExp表示异常信息
| 12
 
 | watch -e -x 2 com.aaron.Arthas.TestWatch calc "{params,target,throwExp}"
 
 | 
此外输出结果的location值为AtExceptionExit,表示观察事件的位置在函数抛出异常

观察方法调用的结果
| 12
 
 | watch -f -x 2 com.aaron.Arthas.TestWatch calc "{params,target,returnObj,throwExp}"
 
 | 

使用条件表达式进行过滤
我们还可以通过条件表达式实现仅对我们期望的场景进行观察,以免输出结果太多造成干扰
| 12
 
 | watch -f -x 2 com.aaron.Arthas.TestWatch calc "{params,target,returnObj,throwExp}" "params[0]>2 && params[0]%2==0"
 
 | 
