Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Arthas问题排查集】活用ognl表达式 #11

Closed
hengyunabc opened this issue Sep 12, 2018 · 25 comments
Closed

【Arthas问题排查集】活用ognl表达式 #11

hengyunabc opened this issue Sep 12, 2018 · 25 comments

Comments

@hengyunabc
Copy link
Collaborator

hengyunabc commented Sep 12, 2018

前言

Arthas 3.0中使用ognl表达式替换了groovy来实现表达式的求值功能,解决了groovy潜在会出现内存泄露的问题。灵活运用ognl表达式,能够极大提升问题排查的效率。

ognl官方文档:https://commons.apache.org/proper/commons-ognl/language-guide.html

一个测试应用

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * @author zhuyong on 2017/9/13.
 */
public class Test {

    public static final Map m = new HashMap<>();
    public static final Map n = new HashMap<>();

    static {
        m.put("a", "aaa");
        m.put("b", "bbb");

        n.put(Type.RUN, "aaa");
        n.put(Type.STOP, "bbb");
    }

    public static void main(String[] args) throws InterruptedException {
        List<Pojo> list = new ArrayList<>();

        for (int i = 0; i < 40; i ++) {
            Pojo pojo = new Pojo();
            pojo.setName("name " + i);
            pojo.setAge(i + 2);

            list.add(pojo);
        }

        while (true) {
            int random = new Random().nextInt(40);

            String name = list.get(random).getName();
            list.get(random).setName(null);

            test(list);

            list.get(random).setName(name);

            Thread.sleep(1000l);
        }
    }

    public static void test(List<Pojo> list) {

    }

    public static void invoke(String a) {
        System.out.println(a);
    }

    static class Pojo {
        String name;
        int age;
        String hobby;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getHobby() {
            return hobby;
        }

        public void setHobby(String hobby) {
            this.hobby = hobby;
        }
    }
}

public enum Type {
    RUN, STOP;
}

查看第一个参数

params是参数列表,是一个数组,可以直接通过下标方式访问

$ watch Test test params[0] -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 26 ms.
@ArrayList[
    @Pojo[Test$Pojo@6e2c634b],
    @Pojo[Test$Pojo@37a71e93],
    @Pojo[Test$Pojo@7e6cbb7a],
    ...
]

这里的-n表示只输出一次

查看数组中的元素

第一个参数是一个List,想要看List中第一个Pojo对象,可以通过下标方式,也可以通过List的get方法访问。

$ watch Test test params[0][0] -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 14 ms.
@Pojo[
    name=@String[name 0],
    age=@Integer[2],
    hobby=null,
]

$ watch Test test params[0].get(0) -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 14 ms.
@Pojo[
    name=@String[name 0],
    age=@Integer[2],
    hobby=null,
]

查看Pojo的属性

拿到这个Pojo可以,直接访问Pojo的属性,如age

$ watch Test test params[0].get(0).age -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 21 ms.
@Integer[2]

还可以通过下标的方式访问params[0][0]["age"],这个写法等效于params[0][0].age

$ watch Test test params[0][0]["name"] -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 53 ms.
watch failed, condition is: null, express is: params[0][0][age], ognl.NoSuchPropertyException: com.taobao.arthas.core.advisor.Advice.age, visit /Users/wangtao/logs/arthas/arthas.log for more details.

但这样会报错,这时候需要再加一个引号

$ watch Test test 'params[0][0]["age"]' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 25 ms.
@Integer[2]

集合投影

有时候我们只需要抽取对象数组中的某一个属性,这种情况可以通过投影来实现,比如要将Pojo对象列表中的name属性单独抽出来,可以通过params[0].{name}这个表达式来实现。 ognl会便利params[0]这个List取出每个对象的name属性,重新组装成一个新的数组。用法相当于Java stream中的map函数。

$ watch Test test params[0].{name} -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 56 ms.
@ArrayList[
    @String[name 0],
    @String[name 1],
    @String[name 2],
    @String[name 3],
    null,
    @String[name 5],
    @String[name 6],
    @String[name 7],
    @String[name 8],
    @String[name 9],
]

集合过滤

有时候还需要针对集合对象按某种条件进行过滤,比如想找出所有age大于5的Pojo的name,可以这样写

$ watch Test test "params[0].{? #this.age > 5}.{name}" -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 25 ms.
@ArrayList[
    @String[name 4],
    @String[name 5],
    @String[name 6],
    null,
    @String[name 8],
    @String[name 9],
]

其中{? #this.age > 5} 相当于stream里面的filter,后面的name相当于stream里面的map

那如果要找到第一个age大于5的Pojo的name,怎么办呢?可以用^$来进行第一个或最后一个的匹配,像下面这样:

$ watch Test test "params[0].{^ #this.age > 5}.{name}" -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 24 ms.
@ArrayList[
    @String[name 4],
]
Command hit execution time limit 1, therefore will be aborted.
$ watch Test test "params[0].{$ #this.age > 5}.{name}" -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 43 ms.
@ArrayList[
    @String[name 9],
]

多行表达式

有些表达式一行之内无法表达,需要多行才能表达,应该怎么写的?比如,假设我们要把所有Pojo的name拿出来,再往里面新加一个新的元素,在返回新的列表,应该如何写?可以通过中括号将多个表达式串联起来,最后一个表达式的返回值代表整个表达式的最终结果。临时变量可以用#来表示。

$ watch Test test '(#test=params[0].{name}, #test.add("abc"), #test)' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 28 ms.
@ArrayList[
    @String[name 0],
    @String[name 1],
    @String[name 2],
    @String[name 3],
    @String[name 4],
    @String[name 5],
    @String[name 6],
    @String[name 7],
    @String[name 8],
    null,
    @String[abc],
]

调用构造函数

调用构造函数,必须要指定要创建的类的全类名。比如下面的例子中,创建一个新的list,然后添加一个新的元素,然后返回添加后的list。

$ watch Test test '(#test=new java.util.ArrayList(), #test.add("abc"), #test)' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 37 ms.
@ArrayList[
    @String[abc],
]

访问静态变量

可以通过@class@filed方式访问,注意需要填写全类名

$ watch Test test '@Test@m' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 35 ms.
@HashMap[
    @String[a]:@String[aaa],
    @String[b]:@String[bbb],
]

调用静态方法

可以通过@class@method(args)方式访问,注意需要填写全类名

$ watch Test test '@java.lang.System@getProperty("java.version")' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 42 ms.
@String[1.8.0_51]

静态方法和非静态方法结合,例如想要获取当前方法调用的TCCL,可以像下面这样写:

$ watch Test test '@java.lang.Thread@currentThread().getContextClassLoader()' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 84 ms.
@AppClassLoader[
    ucp=@URLClassPath[sun.misc.URLClassPath@4cdbe50f],
    $assertionsDisabled=@Boolean[true],
]

访问Map中的元素

Test.n是一个HashMap,假设要获取这个Map的所有key,ongl针对Map接口提供了keys, values这两个虚拟属性,可以像普通属性一样访问。

$ watch Test test '@Test@n.keys' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 57 ms.
@KeySet[
    @Type[RUN],
    @Type[STOP],
]

因为这个Map的Key是一个Enum,假设要把key为RUN这个值的value取出来应该怎么写呢?可以通过Enum的valueOf方法来创建一个Enum,然后get出来,比如下面一样

$ watch Test test '@Test@n.get(@Type@valueOf("RUN"))' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 168 ms.
@String[aaa]

或者是下面这样,通过迭代器+过滤的方式:

$ watch Test test '@Test@n.entrySet().iterator.{? #this.key.name() == "RUN"}' -n 1
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 72 ms.
@ArrayList[
    @Node[RUN=aaa],
]

附录: ognl内置的ognl的虚拟属性

  • Collection:
    • size
    • isEmpty
  • List:
    • iterator
  • Map:
    • keys
    • values
  • Set:
    • iterator
  • Iterator:
    • next
    • hasNext
  • Enumeration:
    • next
    • hasNext
    • nextElement
    • hasMoreElements

最后

欢迎在留言区分享你的牛逼用法,互相交流进步~

@ralf0131 ralf0131 changed the title 活用ognl表达式 【Arthas问题排查集】活用ognl表达式 Sep 14, 2018
@zonghaishang
Copy link

Very nice, :-)

@grindwheel
Copy link

感受到了强大

@wangzh1205
Copy link

wangzh1205 commented Dec 25, 2018

请教一下,watch命令支持,多参数,有响应,针对某个string参数进行条件过滤监控吗? 如果支持的话,具体脚本怎么写呢

@btpka3
Copy link

btpka3 commented Dec 26, 2018

请教一下,watch命令支持,多参数,有响应,针对某个string参数进行条件过滤监控吗? 如果支持的话,具体脚本怎么写呢

eg. watch me.test.XxxClass xxxMethod "{params,returnObj,throwExp}" 'params[0]=="abc"' -x 3

说明:

  • 监控 类 me.test.XxxClass 的方法 xxxMethod 的调用,
  • 监控条件是 第一个参数是字符串 "abc",
  • 条件满足时,打印由 params, returnObj, throwExp 三者构成的List,其中 params 是参数列表
  • -x 3 表示输出上面的结果是,打印3个层级

参考:

  • ONGL 中关于 List 的语法
  • 阿尔萨斯 watch 命令的文档,支持 condition-express 条件表达式
  • 阿尔萨斯 表达式核心变量 关于 params, returnObj, throwExp 等预定义变量的说明。

@liuluo129
Copy link

使用
ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
返回 Failed to get static, exception message: java.lang.NullPointerException, please check $HOME/logs/arthas/arthas.log for more details.
日志里抛空指针

@xym-loveit
Copy link

😄👍👍

@winshu
Copy link

winshu commented Jun 18, 2019

请教在调用方法时,如果参数是 Class 类型,应该如何传入

@hengyunabc
Copy link
Collaborator Author

请教在调用方法时,如果参数是 Class 类型,应该如何传入

ognl '@java.lang.String@class'

@katlog
Copy link

katlog commented Jun 24, 2019

如何给一个静态变量赋值呢?

@hengyunabc
Copy link
Collaborator Author

如何给一个静态变量赋值呢?

#641

@BixinLu
Copy link

BixinLu commented Jul 30, 2019

👍

@MccreeFei
Copy link

请问多个过滤表达式怎么写呢?

@liugddx
Copy link

liugddx commented Sep 6, 2019

使用
ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
返回 Failed to get static, exception message: java.lang.NullPointerException, please check $HOME/logs/arthas/arthas.log for more details.
日志里抛空指针

怎么解决的?

@huting0211
Copy link

使用
ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
返回 Failed to get static, exception message: java.lang.NullPointerException, please check $HOME/logs/arthas/arthas.log for more details.
日志里抛空指针

怎么解决的?

logger --name ROOT --level debug

@huting0211
Copy link

使用
ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
返回 Failed to get static, exception message: java.lang.NullPointerException, please check $HOME/logs/arthas/arthas.log for more details.
日志里抛空指针

logger --name ROOT --level debug

@ZHANGLIANGGIT
Copy link

niubility

@kylixs
Copy link
Contributor

kylixs commented Nov 18, 2020

ognl list 过滤的例子
遍历语法:
#list.{ #el = #this, #el... }
参考文档: https://www.pingidentity.com/en/company/blog/posts/2013/looping-in-ognl-take-2.html

ognl "#list=new java.util.ArrayList(),#list.add(100), #list.add(38),#list.add(120), #new_list=new java.util.ArrayList(), #list.{ #el = #this, #el >= 100 ? #new_list.add(#el) : null }, {#list, #new_list}"

@ArrayList[
    @ArrayList[
        @Integer[100],
        @Integer[38],
        @Integer[120],
    ],
    @ArrayList[
        @Integer[100],
        @Integer[120],
    ],
]

@zjunothing
Copy link

👍

1 similar comment
@bailuoxi66
Copy link

👍

@839284824
Copy link

厉害

@ghimi-g
Copy link

ghimi-g commented Jul 18, 2022

收下我的膝盖👍👍👍👍

@kennywgx
Copy link

kennywgx commented Aug 19, 2022

关于数组的遍历,你举的例子有点误导人...
params[0].{name} // 这里的params[0]表示要遍历的数组,但我一开始误解成params才是你说的那个要遍历的数组

后来看了阿里云的文档:

// 返回Root对象中的group属性中users这个集合中所有元素的name构成的集合  
group.users.{name}   // 新的以name为元素的集合  
// 将group中users这个集合中的元素的code和name用-连接符拼起来构成的字符串集合  
group.users.{code + ‘-’ + name}   // 新的以‘code – name’为元素的集合  
// 返回Root对象的group中users这个集合所有元素中name不为null的元素构成的集合  
group.users.{? #this.name != null}  // 过滤后的users集合  

才知道其实正确的数组投影用法是: list.{property}
在此之前我以为是: list[0].{property} ,虽然质疑这里的[0],但是一直在尝试这个而得不到我要的结果。。。

@qixiang-mft
Copy link

在 jdk11 环境下,

package foo.bar

interface Context {
   
    static Context current() {
         // ... code;
    }

}

run watch with ognl expression to call interface static method will failed.

watch  a.b.C xxxmethod   `@foo.bar.Context@current()`

with error info

ognl  no method  accessor  for interface xxxx 

@winnerwbx
Copy link

Very powerful!

@UDnian
Copy link

UDnian commented Nov 10, 2023

请教各大佬个问题:
例如方法:public static long del(String... keys) {}
参数为可变长参数,使用ognl表达式执行时,参数该怎么传呢?
方式1:ognl -x 3 '@...RedisHelper@del(new java.lang.String[]{"mykey"})' -c c6da8bb
方式2:ognl -x 3 '@...RedisHelper@del("mykey")' -c c6da8bb
都会报错:java.lang.IllegalArgumentException: argument type mismatch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests