Clojure世界:利用HouseMD诊断clojure

    xiaoxiao2023-12-05  132

    HouseMD是淘宝的聚石写的一个非常优秀的Java进程运行时诊断和调试工具,如果你接触过btrace,那么HouseMD也许你应该尝试下,它比btrace更易用,不需要写脚本,类似strace的方式attach到jvm进程做跟踪调试。     基本的安装和使用请看这篇文档《 UserGuide》,恕不重复。以下内容都假设你正确安装了housemd。     本文主要介绍下怎么用housemd诊断跟踪clojure进程。Clojure的java实现也是跑在JVM里,当然也可以用housemd。     我们以一个简单的例子开始,假设我们有如下clojure代码: (loop [x 1]   (Thread/sleep 1000)   (prn x)   (recur (inc x)))     这段很简单,只是间隔一秒不断地打印递增的数字x。我们准备用housemd跟踪这个程序的运行,首先运行这个程序,你可以用lein,也可以直接java命令运行: java -cp clojure.jar clojure.main test.clj     运行时不断地在控制台打印数字,通过jps或者ps查询到该进程的id,假设为pid,使用housemd连接到该进程: housemd <pid>     顺利进入housemd的交互控制台,通过help命令可以查询支持的命令: housemd> help quit      terminate the process. help      display  this infomation. trace     display or output infomation of method invocaton. loaded    display loaded classes information.     要用housemd调试clojure,你需要对clojure的实现有一点点了解,有兴趣可以看过去的一篇blog《 clojure hacking guide》,简单来说,clojure的编译器会将clojure代码编译成java类并运行。对于JVM来说,clojure生成的类,跟java编译器生成类没有什么不同。     具体到上面的clojure代码,会生成一个名为 user$eval1的类,user是默认的namespace,而eval1是clojure编译器自动生成的一个标示类名,通过 loaded命令查询类的加载情况: housemd> loaded user$eval1 -h user$eval1 ->  null     - clojure.lang.DynamicClassLoader@1d25d06e         - clojure.lang.DynamicClassLoader@1d96f4b5             - sun.misc.Launcher$AppClassLoader@a6eb38a                 - sun.misc.Launcher$ExtClassLoader@69cd2e5f     通过-h选项打印了加载user$eval1的类加载器的层次关系,因为user$eval1是动态生成的(clojure启动过程中),因此它不在任何一个class或者jar文件中。除了查询user namespace的类之外,你还可以查询clojure.core,clojure.lang,clojure.java等任何被加载进来的类,例如查询clojure.core.prn的类,在clojure里这是一个函数,在jvm看来这只是一个类: housemd> loaded -h core$prn clojure.core$prn -> /Volumes/HDD/Users/apple/clojure/clojure.jar     - sun.misc.Launcher$AppClassLoader@a6eb38a         - sun.misc.Launcher$ExtClassLoader@69cd2e5f    注意,不需要完整的namespace——clojure.core,直接core$prn即可。其他也是类似。 小技巧:如果你实在不知道clojure编译器生成的类名,你可以利用jvm自带的jmap命令来查询。    接下来,我们尝试用trace命令跟踪方法的运行,例如例子中的clojure代码用到了loop和recur两个sepcial form,我们跟踪下loop: housemd> trace -t 5 core$loop INFO : probe  class clojure.core$loop core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms     null INFO : Ended by timeout INFO : reset  class clojure.core$loop     在5秒内,clojure.core$loop类有两个方法各被调用了5次,doInvoke是实际的调用,而getRequiredArity用来查询loop所需要的参数个数。trace还可以跟踪到具体的方法,例如我们跟踪prn函数的调用情况: housemd> trace -t 5 core$prn. doInvoke INFO : probe  class clojure.core$prn core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            1            1ms    clojure.core$prn@3e4ac866 core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            2           <1ms    clojure.core$prn@3e4ac866 core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            3           <1ms    clojure.core$prn@3e4ac866 core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            4           <1ms    clojure.core$prn@3e4ac866 core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            5           <1ms    clojure.core$prn@3e4ac866 INFO : Ended by timeout INFO : reset  class clojure.core$prn       trace打印了方法的调用次数(5秒内)和每次调用的时间(毫秒级别),以及调用的target object 。小技巧:没有可变参数的函数生成类最终调用的是invoke方法(参数个数可能重载),有可变参数的函数调用的是doInvoke方法。   trace命令还支持打印调用堆栈到文件,例如: trace -t 5 -d -s  core$prn.doInvoke    利用-s和-d命令会将详细的调用信息输出到临时目录,临时目录的路径可以通过trace help命令查询到,在我的机器上是/tmp/trace/<pid>@host目录下。调用堆栈的输出类似: example$square.invoke(Long) call by thread [main]     example$eval9.invoke(test.clj:11)     clojure.lang.Compiler.eval(Compiler.java:6465)     clojure.lang.Compiler.load(Compiler.java:6902)     clojure.lang.Compiler.loadFile(Compiler.java:6863)     clojure.main$load_script.invoke(main.clj:282)     clojure.main$script_opt.invoke(main.clj:342)     clojure.main$main.doInvoke(main.clj:426)     clojure.lang.RestFn.invoke(RestFn.java:421)     clojure.lang.Var.invoke(Var.java:405)     clojure.lang.AFn.applyToHelper(AFn.java:163)     clojure.lang.Var.applyTo(Var.java:518)     clojure.main.main(main.java:37)    上面这个简单的例子展示了使用housemd跟踪诊断clojure进程的方法。    自定义ns和函数的调试与此类似,假设我们有下面的clojure代码: (ns example) (defn square [x]   (* x x)) (loop [x 1]   (Thread/sleep 1000)   (square x)   (recur (inc x)))      ns为example,自定义函数square并定期循环调用。使用housemd诊断这段代码: loaded -h example$square     #查询square的加载情况 trace -t 10 -d -s example$square.invoke  #跟踪10秒内square的调用情况 文章转自庄周梦蝶  ,原文发布时间 2012-06-15 相关资源:七夕情人节表白HTML源码(两款)
    最新回复(0)