Clojure世界:利用HouseMD诊断clojure

简介:
  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
目录
相关文章
|
Java PHP 开发工具
编程语言Clojure入门
在众多的编程语言中,不少开发人员熟悉Java、C#、PHP等。但是很早以前,也有一些小众的语言,比如Lisp语言,它是一种适用于符号处理和自动推理的编程语言,内部使用表结构来表达非数值计算。而Clojure语言是在JVM上实现的Lisp风格的语言,语法与Lisp类似,且可以和Java语言进行互操作
1316 0
编程语言Clojure入门
|
7月前
|
监控 Java 测试技术
性能工具之Java分析工具BTrace入门
【5月更文挑战第25天】性能工具之Java分析工具BTrace入门
132 2
|
7月前
|
JavaScript Java 编译器
Java概述:跨足编程世界的瑞士军刀
Java概述:跨足编程世界的瑞士军刀
68 0
Java概述:跨足编程世界的瑞士军刀
|
Java 测试技术 API
带你快速看完9.8分神作《Effective Java》—— 通用编程篇(二)
57 最小化局部变量的作用域 58 for-each循环优先于传统的for循环 59 了解并使用类库 60 若需要精确答案就应避免使用float 和double 类型 61 基本类型优先于包装基本类型 62 如果其他类型更合适,尽量避免使用字符串 63 当心字符串连接的性能问题 64 通过接口引用对象 65 接口优于反射 66 谨慎使用本地方法 67 谨慎地进行优化 68 遵守被广泛认可的命名约定
带你快速看完9.8分神作《Effective Java》—— 通用编程篇(二)
|
存储 安全 Java
带你快速看完9.8分神作《Effective Java》—— 通用编程篇(一)
57 最小化局部变量的作用域 58 for-each循环优先于传统的for循环 59 了解并使用类库 60 若需要精确答案就应避免使用float 和double 类型 61 基本类型优先于包装基本类型 62 如果其他类型更合适,尽量避免使用字符串 63 当心字符串连接的性能问题 64 通过接口引用对象 65 接口优于反射 66 谨慎使用本地方法 67 谨慎地进行优化 68 遵守被广泛认可的命名约定
|
Java 网络架构
Clojure快餐教程(1) - 运行在JVM上的Lisp方言
Clojure是一门运行在JVM上的Lisp方言.与Lisp还是有一些不同,比如使用`[]`,`{}`,`#{}`来表示向量、哈希表和集合。比如不支持car, cdr, setq等。好处是可以无缝调用java的基础设施,同时做了一些修改,更加适合多人协作开发。
2345 0

热门文章

最新文章