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
顺利进入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编译器生成类没有什么不同。www.2cto.com
具体到上面的clojure代码,会生成一个名为user$eva l1的类,user是默认的namespace,而eva l1是clojure编译器自动生成的一个标示类名,通过loaded命令查询类的加载情况:
housemd> loaded user$eva l1 -h
user$eva l1 -> null
- clojure.lang.DynamicClassLoader@1d25d06e
- clojure.lang.DynamicClassLoader@1d96f4b5
- sun.misc.Launcher$AppClassLoader@a6eb38a
- sun.misc.Launcher$ExtClassLoader@69cd2e5f
通过-h选项打印了加载user$eva l1的类加载器的层次关系,因为user$eva l1是动态生成的(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 -m