DevGuideCN

zhongl edited this page Jun 28, 2012 · 17 revisions

获取源码

  1. Fork我的源码库
  2. Fork 成功后, 从自己的库中克隆都本地(注意替换$yourname), git clone https://github.com/$yourname/HouseMD.git housemd

目录结构

  • src 源代码目录(含单元测试代码)
  • acceptance-test 验收测试代码目录
  • bin 安装和运行脚本目录
  • project SBT构建脚本和插件配置目录
  • sbt SBT启动脚本

使用SBT

执行./sbt, 进入SBT命令行.

注意, 第一次执行./sbt, 此脚本会自动下载SBT, 并启动它. 此后有一段较长时间的初始化, 以及下载依赖包的过程, 期间可能会因为GFW原因导致一些依赖无法下载, 请在sbt脚本文件中配置HTTP代理.

下面的命令全部是在进入SBT命令行之后执行的:

> compile

编译源码

> test

执行测试用例

> assembly

打包

更多SBT的使用请细看官方文档

准备开发环境

HouseMD使用主要编程语言是ScalaJava, 下面是常用IDE准备说明, 若你喜欢的工具不在此列, 我表示很遗憾, 请咨询Google: scala $your_favorite_ide_or_editor

IntelliJ IDEA (推荐首选)

  1. 下载IDEA的社区版(免费)
  2. 启动IDEA, 选择菜单File -> Settings -> Plugins 中搜索并安装Scala插件
  3. SBT的命令行下, 执行gen-idea, 便会生成IDEA的项目文件
  4. IDEA中, 选择菜单File -> Open Project 打开项目, 马上开始为HouseMD贡献你的智慧吧:)

Scala-IDE for Eclipse

  1. 不多废话, 请参考官方文档安装配置
  2. SBT的命令行下, 执行eclipse, 便会生成Eclipse的项目文件
  3. Eclipse中打开项目, 马上开始为HouseMD贡献你的智慧吧:)

注意, 有可能打开项目后你发现.java的文件会有编译错误提示, 也许Scala-IDE目前对Scala project中混合有.java文件的支持不是很好, 这个是可以忽略的, 因为只需要用SBT进行编译构建就可以了.

如果你知道怎么解决请邮件告诉我, 谢谢

贡献什么

原则上, 只要是对HouseMD有帮助的都可以, 例如但不限于:

  • 解决已有的Issue
  • 提交新的Issue, 功能特性, 改进, 缺陷都可以
  • 新增或修订Wiki
  • 分享HouseMD的使用和开发经验技巧, 记得邮件告诉我链接, 我会放到Wiki Home让更多人知道

提交贡献

提交你的贡献之前, 请做到下面几点小小的要求:

  1. 提交中必须含有相应的单元测试代码, 单元测试代码应能清晰的说明且验证你提供了哪些特性上的变化
    • 强烈建议参考HouseMD已有的测试用例, 采用FunSpec风格能够更容易的清晰表达其意图.
  2. 提交之前, 请确保单元测试全部通过

在你的代码提交到你的github上(获取源代码时Fork产生的库)后, 请点击Pull Request向我发起请求, 并在提交的表单中清晰描述你的提交目的和变化要点, Github会自动邮件通知我, 随后我会第一时间处理. 更多操作帮助请见Using Pull Request.

扩展指令的建议

没有比新增一些更酷的指令, 更让人兴奋的啦~

要办到它, 得从下面的类入手.

Command

env 这样不需要用到Instrumentation指令, 可以直接继承Command, 将自定义指令的逻辑实现在run方法中.

切记, 指令只能有一个构造器, 因为只有第一个构造器会被用于实例化指令; 并且, 构造器应至少有一个参数是PrintOut, 用以Command的初始化, 它是用来向控制台回显信息的. 不用担心如何构造一个PrintOut的实例, 只用留好这个参数, HouseMD会帮我们搞定.

每个指令是如何被执行的呢?

典型的过程是:

  1. 首先, 每个Command的具体实现类会被House引导给Duck并由Telephone来实例化它们,
  2. housemd>提示下输入一串字符, 按下回车后, 这串字符会被拆分为为指令和其参数选项两个部分, 通过指令部分匹配相应指令的实例, 而参数选项的部分则交由指令实例的parse方法进行解析,
  3. 解析完成后, 指令实例的run方法会被调用

如何声明参数选项

Command提供了三种方法来实现声明:

  • flag 声明布尔类型的选项, 通常用于开启什么模式或特定的动作, 例如loaded指令的-h选项
  • option 声明除布尔类型之外的单值选项, 选项是必须有默认值的, 例如trace-p选项
  • parameter 声明参数,
    • 可以声明多个参数, 输入的顺序与声明的顺序一致
    • 可以是单值或多值, 多个参数的情况下, 只允许最后一个参数是多值
    • 可以设定默认值, 不设的话表明必须要输入参数.

请确保参数选项的声明, 使用在声明属性的位置完成的, 目的是为了保证在执行Commandparse方法之前, 所有的声明已完成.

细节请参考代码作为示例:

没有InstrumentationCommand实现能做什么?

运行在目标进程的环境中, 很多JVM提供的工具方法就可以拿来了:

  • env, 就是通过java.lang.System.getenv()来实现查看目标进程的系统环境变量( 顺便提一下, 我刻意没有实现查看Properities的功能, 留给大家练手用 :D )
  • 还可以利用java.lang.management.ManagementFactory获得系统提供的管理Bean, 来实现诸如: 查看线程数, 内存, 执行Full GC等等

总之, 发挥你的想象力, 指令可以运行在目标进程中了, 还有什么可以直接拿来用的呢?

Instrumentation

若是要用到Instrumentation的指令, 也同样要继承Command, 与 env, 但构造器就要多一个Instrumentation的参数, 如loaded. HouseMD在实例化指令的时候, 会将Instrumentation的实例传入.

指令的构造器最多就InstrumentationPrintOut两个参数, 多了会导致实例化失败.

TransformCommand

要实现一个比trace更牛逼的指令, 前面的方法已经足够让你办到了. 但我还是强烈推荐继承TransformCommand, 它已经帮你实现了字节码增强的部分(这部分是有很多陷阱的, 除非你有足够的经验, 真的如此我热切的希望你能帮我改进它), 还有常规选项(如-i,-t,-l以及-p) 剩下你需要做的是:

  1. 声明参数
  2. 实现isCandidate方法, 以过滤那些需要进行字节码增强的类,
  3. 实现isDecorating方法,以过滤那些需要进行字节码增强的类的方法,
  4. 实现hook方法, 返回一个回调对象, 它的方法会在特定的时机被调用:
    1. enterWith会在所跟踪的方法进入之前调用, Context对象有方法上下文的数据
    2. exitWith 会在所跟踪的方法退出之后调用, Context对象有方法上下文的数据
    3. heartbeat会像心跳一样被调用(可能一会快,可能一会慢), 主要用于实时显示信息, 如trace的调用摘要, now是当前时间(毫秒)
    4. finalize 会在指令结束前调用, 主要用于释放清理资源, 一旦指令运行中出现了异常, Option[Throwable]会传入它.

细节请参考代码作为示例:

Context

Context的属性说明如下:

className: String 
methodName: String
loader: ClassLoader              // className 所指 Class的 ClassLoader
arguments: Array[AnyRef],        // 方法调用参数值数组
descriptor: String,              // 方法的在字节码中的描述形式, 例如(Ljava/lang/String;)Ljava/lang/String; 表示方法有个String类型的参数, 返回值也是String类型
isVoidReturn: Boolean,           // true的话表示方法返回值为void
thisObject: AnyRef,              // 当前方法的宿主对象, 等同于BTrace中@self 传入的对象
started: Long,                   // 方法调用的开始时间(毫秒)
stack: Array[StackTraceElement], // 方法的调用栈
thread: Thread,                  // 调用该方法的线程
stopped: Option[Long],           // 方法调用的结束时间(毫秒), 方法调用前此值为stopped.get会抛异常, 方法调用后为stopped.get会获得时间
resultOrException: Option[AnyRef]// 如果方法调用正常结束, 则为返回值(void时,为null); 反之, 是异常对象 

Completer

要想自定义的指令具备良好的交互体验, 那么支持参数自动补全这个功能一定要提供. 实现这点就需要实现Completer接口的complete方法, 具体怎么做, 文字不好描述, 还是看源码吧.

不要忘了最后一步

新增的指令需在HouseagentOptions的值(:: Nil之前)增加一下, 现在方式有点土, 欢迎高手来改进.

更多相关疑问请提交Issue, 我们一起来讨论:)

参考资料

  1. BTrace
  2. javaagent
  3. 动态跟踪Java