当前位置: 首页 > news >正文

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

相关文章:

  • pat解题报告【1082】
  • Java IO详解(七)------随机访问文件流
  • Spring+SpringMVC+MyBatis深入学习及搭建(三)——MyBatis全局配置文件解析
  • 硬链接和软链接
  • 【python自制】让大白成为你的个人助手!
  • java RabbitMQ
  • linux 21的用法
  • shell脚本编程30分钟入门上手
  • 跟着实例学习ZooKeeper的用法: Leader选举
  • R语言数据可视化2—ggplot2各种维度的业务量统计根据类型统计不同月份的业务量...
  • 使用InteliJ IDEA导出jar包执行报错:找不到主清单属性
  • Ubuntu14.04下sogou输入法的输入框只显示英文不显示中文的问题
  • CentOS 7 更新源 – 使用国内 163 yum 源
  • Linux 的五个重启命令及具体说明
  • 蓝桥杯练习
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • [nginx文档翻译系列] 控制nginx
  • C++类中的特殊成员函数
  • C++入门教程(10):for 语句
  • CentOS从零开始部署Nodejs项目
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Electron入门介绍
  • ES6系列(二)变量的解构赋值
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Java多线程(4):使用线程池执行定时任务
  • k8s 面向应用开发者的基础命令
  • Lucene解析 - 基本概念
  • MySQL的数据类型
  • PhantomJS 安装
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 第十八天-企业应用架构模式-基本模式
  • 分布式事物理论与实践
  • ------- 计算机网络基础
  • 每天一个设计模式之命令模式
  • 使用agvtool更改app version/build
  • 数组的操作
  • 我这样减少了26.5M Java内存!
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 用jquery写贪吃蛇
  • (11)MSP430F5529 定时器B
  • (javascript)再说document.body.scrollTop的使用问题
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (四)Controller接口控制器详解(三)
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .mysql secret在哪_MYSQL基本操作(上)
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .Net Core 中间件验签