clojure世界:利用housemd诊断clojure -凯发k8网页登录

生活、程序、未来
   :: 凯发k8网页登录首页 ::  ::  :: 聚合  :: 管理

clojure世界:利用housemd诊断clojure

posted on 2012-06-15 02:52 dennis 阅读(12077) 评论(2)  编辑  收藏 所属分类: clojure工具和命令

    是淘宝的聚石写的一个非常优秀的java进程运行时诊断和调试工具,如果你接触过btrace,那么housemd也许你应该尝试下,它比btrace更易用,不需要写脚本,类似strace的方式attach到jvm进程做跟踪调试。

    基本的安装和使用请看这篇文档《》,恕不重复。以下内容都假设你正确安装了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编译器生成类没有什么不同。
    具体到上面的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/@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的调用情况

评论

# re: clojure世界:利用housemd诊断clojure  回复     

2012-06-15 12:17 by
java 东西真是难理解 不过大型网站很多用这个的

# re: clojure世界:利用housemd诊断clojure  回复     

2014-09-02 09:17 by
这个块看看就好了,不是非常能看懂!
网站地图