housemdソース解析

20966 ワード

1.1準備作業
1.1.1 java agent、java atch appi、Virtual Machineなど
java agentエージェントとvirtual Machineの知識は、http://blog.csdn.net/qyongkang/article/details/7765255を参照することができます。  大体6編の文章があります。読めばいくつかの概念が分かります。
もう一つの簡単な例があります。  金の含有量は高くないですが、我慢して見てもいいです。
 
1.1.2 scala学習
housemdのほとんどのコードはscalaで書かれています。だから、少なくとも中の内容が分かります。主義を持ってきて、これを見てください。
 
1.1.3 jenv
housemdダウンロードはjenvを通しています。便利です。一行の命令で解決します。もちろんjenvを使う前にjenvをインストールする必要があります。簡単です。これは勉強する必要がありません。インストールするだけでいいです。具体的には下の通りです。http://chenjingbo.iteye.com/blog/1966733。 
 
1.1.4 yascli
このカバンは.scalaのコマンドライン開発パッケージです。具体的な住所は?  http://chenjingbo.iteye.com/blog/1974335
 正直に言うと、私はサイドコードを完全に読んでいません。簡単なツールとして見ました。
1.1.5 asm
このバイトコード修正ツールは、housemdのメインフローを見ているだけでは、詳しく検討する必要はありません。trace命令を見たいならば、バイトコードを修正して実現する必要がありますが、どうやって働くのか、もう少し詳しく勉強してください。
 
1.2 本文
1.2.1 housemdの公式住所
housemdの住所はhttps://github.com/linux-china/jenv/wiki/Chinese-Introductionの具体的な使い方はこの文書に説明されていません。
 
1.2.2プログラム入口
Housemidの使用は、最初はもちろん端末に入力します。 
terminalは書いています
housemd$PID
 housemd命令に対応するコードを直接見ます。
#!/bin/sh
if [ -z "$JAVA_HOME" ];
then
    echo "Please set JAVA_HOME to JDK 6+!"
    exit 1
else
    ROOT=`dirname  "$0"`
    if [ -f $JAVA_HOME/lib/tools.jar ]; then
            BOOT_CLASSPATH=-Xbootclasspath/a:$JAVA_HOME/lib/tools.jar
    fi
    $JAVA_HOME/bin/java $BOOT_CLASSPATH -jar $ROOT/housemd_2.9.2-0.2.6.min.jar "$@"
fi
 ここはとても簡単です。tools.jarだけをbootに設定します。classipathは、java-jar命令を実行してhousemd対応のjarを実行します。それなら、直接に対応するMETAINF.MFを見ます。
Manifest-Version: 1.0
Implementation-Title: housemd
Implementation-Version: 0.2.6
Specification-Vendor: com.github.zhongl
Implementation-Vendor: com.github.zhongl
Implementation-Vendor-Id: com.github.zhongl
Specification-Title: housemd
Agent-Class: com.github.zhongl.housemd.duck.Duck
Specification-Version: 0.2.6
Main-Class: com.github.zhongl.housemd.house.House
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Signature-Version: 0.2.6
 この文書は後から使う必要があります。ここで気になるのは 
書き記す
Main-Comp.github.zhongl.housemd.house.House
 そうすると、対応する入り口のクラスを見つけます。
 
 1.2.3 House.scala
このクラスのコアコードは以下の通りです。
 
def run() {
    if (printVersion()) {//        ,       
      println("v" + version)
      return
    }
//   windows  ,        .
    if (ManagementFactory.getOperatingSystemMXBean.getName.toLowerCase.contains("window")) {
      throw new IllegalStateException("Sorry, Windows is not supported now.")
    }
    try {
//       unix terminal     scala    ,    .      unix terminal.
      val terminal = new NoInterruptUnixTerminal()
      terminal.init()
      val sout = terminal.wrapOutIfNeeded(System.out)
      val sin = terminal.wrapInIfNeeded(System.in)
      val vm = VirtualMachine.attach(pid())

      val mobilephone = new Mobilephone(port(), {
        case PickUp              => info("connection established on " + port())
        case ListenTo(earphone)  => earphone(sout)
        case SpeakTo(microphone) => microphone(sin)
        case BreakOff(reason)    => error("connection breaked causeby"); error(reason)
        case HangUp              => terminal.restore(); silentClose(errorDetailWriter); info("bye")

      })

      info("Welcome to HouseMD " + version)
//       .       .
      vm.loadAgent(agentJarFile, agentOptions mkString " ")
      vm.detach()

      mobilephone.start()
    } catch {
      case e: Throwable => error(e); silentClose(errorDetailWriter)
    }
  }
いくつかの主要ではない機能については、上記の注釈で説明しました。 
 
 
val vm = VirtualMachine.attach(pid()) //pid()       pid
      vm.loadAgent(agentJarFile, agentOptions mkString " ")
      vm.detach()
 彼もVirtual Machine load対応のagentを通じて機能を実現しているように見えます。対応するパラメータを詳しく見てみます。
 
知っています Virtual Machine.loadAgent方法は、最初のパラメータは、agentに対するjarパッケージの全パスであり、第二のパラメータは、agentに着信するパラメータである。
 
private lazy val agentJarFile = sourceOf(Manifest.classType(getClass))
 housemdは全部でjarカバン一つです。つまり、現在のクラスがあるjarアドレスはagentの住所に対応しています。
 
 
private lazy val agentOptions = agentJarFile :://agent   jar    
                                  classNameOf[Telephone] :: //Telephone       .         
                                  port() :://   terminal          .
                                  classNameOf[Trace] :://   6   housemd       ,         .
                                  classNameOf[Loaded] ::
                                  classNameOf[Env] ::
                                  classNameOf[Inspect] ::
                                  classNameOf[Prop] ::
                                  classNameOf[Resources] ::
                                  Nil  //what's this 
 後ろのパラメータが多いです。中のパラメータの内容は大体説明しました。各パラメータは中間にスペースで区切られています。ここのパラメータの内容は説明します。後は全部使います。
 
はい、上でプログラムの入り口のコードを説明しました。中の核心はatech agentです。下には対応するagent classがどこにありますか?上のMETAINF.MFを見てください。見えます。
書き記す
Agent-CSlass:comp.github.zhongl.housemd.duck.Duck
 OK.次は引き続き対応するagent classを見ます。
1.2.4 ダクト
このクラスは対応するプロキシ類です。具体的なコードは以下の通りです。
 
ublic class Duck {
    public static void agentmain(String arguments, Instrumentation instrumentation) throws Exception {
        String[] parts = arguments.split("\\s+", 4);//           ,    4  .(          1.2.3   .      )
        URL agentJar = new File(parts[0]).toURI().toURL();//agent  jar,  URL
        String telephoneClassName = parts[1];//            .
        int port = Integer.parseInt(parts[2]);//terminal      . telephone    .

       //    classloader
        ClassLoader classLoader = new URLClassLoader(new URL[]{agentJar}) {
            @Override
            protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
                Class<?> loadedClass = findLoadedClass(name);
                if (loadedClass != null) return loadedClass;

                try {
                    Class<?> aClass = findClass(name);
                    if (resolve) resolveClass(aClass);
                    return aClass;
                } catch (Exception e) {
                    return super.loadClass(name, resolve);
                }
            }
        };
       //   commond    ,       classloader loader  .
        Class<?>[] commandClasses = loadClasses(parts[3].split("\\s+"), classLoader);

       //    ,    telephoneClass   .         .instrumentation, port, commandClasses
        Runnable executor = (Runnable) classLoader.loadClass(telephoneClassName)
                .getConstructor(Instrumentation.class, int.class, Class[].class)
                .newInstance(instrumentation, port, commandClasses);

        //      ,  telephoneClass.
        Thread thread = new Thread(executor, "HouseMD-Duck");
        thread.setDaemon(true);
        thread.start();
    }
   //   load    class.
    private static Class<?>[] loadClasses(String[] classNames, ClassLoader classLoader) throws ClassNotFoundException {
        Class<?>[] classes = (Class<?>[]) Array.newInstance(Class.class, classNames.length);
        for (int i = 0; i < classes.length; i++) {
            classes[i] = Class.forName(classNames[i], false, classLoader);
        }
        return classes;
    }

}
 やっとjavaコードです。
ほとんどのコードの説明は上に書いてあります。唯一説明していないのはtelephone Classです。1.2.3の章をよく読んで、telephone Classの対応が分かります。 comple.github.zhongl.housemd.duck.Telephone.このagentの中で他の何もしていないと言えます。Telephoneのスレッドを立ち上げました。
 
1.2.4 Telephone
このクラスは、主な機能は、Instructionオブジェクトとコマンドラインをリンクさせることです。ソースは以下の通りです。
 
class Telephone(inst: Instrumentation, port: Int, classes: Array[Class[Command]]) extends Runnable {
 
 
  def run() {
//        teminal     
    val socket = new Socket("localhost", port)
    val reader = new ConsoleReader(socket.getInputStream, socket.getOutputStream)
    val history = new FileHistory(new File("/tmp/housemd/.history"))
    reader.setHistory(history)
 
    try {
//Shell     .         Shell
      new Shell(name = "housemd", description = "a runtime diagnosis tool of jvm.", reader = reader) {
        private val lastCommand = new Last(out)
 
        override protected def commands = Quit :: helpCommand :: lastCommand :: classes
          .map { toCommand(_, PrintOut(reader)) }
          .toList
      //       (    ).
        override def error(a: Any) {
          super.error(a)
          if (a.isInstanceOf[Throwable]) lastCommand.keep(a.asInstanceOf[Throwable])
        }
 
      } main (Array.empty[String])
    } finally {
      TerminalFactory.reset()
      history.flush()
      socket.shutdownOutput()
      socket.shutdownInput()
      socket.close()
    }
  }
//       Commond  
  private def toCommand(c: Class[Command], out: PrintOut) = {
    val I = classOf[Instrumentation]
    val O = classOf[PrintOut]
    val constructors = c.getConstructors
    val args = constructors(0).getParameterTypes map {
      case I => inst
      case O => out
      case x => throw new IllegalStateException("Unsupport parameter type: " + x)
    }
    constructors(0).newInstance(args: _*).asInstanceOf[Command]
  }
 
}
  ここは覆っています command方法.私たちが必要な命令が追加されました。それぞれhelpCommand、lastCommandそして前にDuckから入ってきた命令があります。つまりHouse.scalaからDuckに伝えられました。
classNameOf[Trace] ::
                                  classNameOf[Loaded] ::
                                  classNameOf[Env] ::
                                  classNameOf[Inspect] ::
                                  classNameOf[Prop] ::
                                  classNameOf[Resources] ::
 その後、反射によって対応例を作成します。このシェルサポートのコマンドがあります。
 Telphone類の要約コードはShellオブジェクトを作成しました。その後、対応するmainメソッドを呼び出しました。コードは継続して、Shellオブジェクトのmain方法を見るべきです。
 
1.2.5シェル
 ソースを直接見る
abstract class Shell(
  name: String,
  description: String,
  reader: ConsoleReader = new ConsoleReader(System.in, System.out))
  extends Command(name, description, PrintOut(reader)) with Suite {

//    main       interact  .
  final def main(arguments: Array[String]) { interact() }

  override final def run() {}

  protected def prompt: String = name + "> "

  override protected def decorate(list: String) = "
" + list + "
" // . private def interact() { reader.setPrompt(prompt)// shell housemd> reader.addCompleter(DefaultCompleter) // completer, // , , . @tailrec def parse(line: String) { if (line == null) return val array = line.trim.split("\\s+") // , try { // .. scala . // run if (!array.head.isEmpty) run(array.head, array.tail) { name => println("Unknown command: " + name) } } catch { //quit .. case e: QuitException => return // . case t => error(t) } // , . parse(reader.readLine()) } parse(reader.readLine()) reader.shutdown() } object Quit extends Command("quit", "terminate the process.", PrintOut(reader)) { def run() { throw new QuitException } } class QuitException extends Exception //*** Completer , , ***// }
 コマンドヘッドの設定、コマンドの取得、コマンドの実行の終了など、このクラスでいくつかのエッジロジックが処理されていますが、最もコアな処理コマンドの論理パッケージは、 
 run(array.head, array.tail)
 この方法は Suite類の中で、Shell類が継承するSuite類。以下、Suite類のrunを見続けます。
 
1.2.6 Suite
このクラスは、パッケージクラスであり、基本的にはコアのロジックがなく、helpCommandを定義しています。ここでは多くの説明はありません。コアはやはりrunの方法です。
def run(command: String, arguments: Array[String])(handleUnknown: String => Unit) {
//    Shell        .
    commands find {_.name == command} match {
      case Some(c) => c.parse(arguments); c.run()
      case None    => handleUnknown(command)
    }
  }
 これはrun方法も簡単で、直接Commonに対応するparse方法とrun方法を呼び出しました。
 
1.2.7 Command
このクラスは、すべてのコマンドの親です。以前はsuiteで見られましたが、彼はパース方法とrun方法を呼び出しました。パース方法は対応するオプトとパラマーを解析する方法です。run方法は Runnableインターフェースで定義されているrunインターフェースです。各コマンドに対応して、対応するrunインターフェースが実現されます。まず、parse方法です。
 
def parse(arguments: Array[String]) {
//    values   
    values.clear() // reset last state

//           ,     -   ,    addOption      , addParameter.        ,     ,     .   otpion   parameter,    values 
    @tailrec
    def read(list: List[String])(implicit index: Int = 0) {
      list match {
        case head :: tail if (head.matches("-[a-zA-Z-]+")) => read(addOption(head, tail))
        case head :: tail                                  => read(addParameter(index, list))(index + 1)
        case Nil                                           => // end recusive
      }
    }

    read(arguments.toList)
  }
 Parseの方法を見たら、対応するrunの方法を見なければなりません。対応するrunの方法はコマンドごとに違っています。コマンドごとには言わないと思います。簡単なloaded命令を先に言ってください。対応するrunの方法を見てください。
 
1.2.8 Loaded
loadedコマンドの機能はクラスローディングパスを見ることです。1つの-hパラメータと一緒に、対応するクラスloaderの階層を印刷できます。大体の機能は以下の通りです。
loadedは書いています
housemd>loaded-h Juwl Communt opServiceImpl
comple.taobao.juwl.iserver.service.top.impl.JuwlCommon MTopServiceImpl->/home/admin/juwliserver/target/juwliserver.war/WEB-INF/lib/juwl-service-1.0-service-1.0-SCHOT.jar.
-comp.taobao.tomcat.classloader.TomcatWebAppClassLoader@55f49969
-org.apache.ccaraina.loader.StandardClassLoader@6b12da40
-sun.misc.Launcher$AppClassLoader@4aad3ba4
-sun.misc.Launcher$ExtClassLoader@3326b249
 BootClass Loaderはjavaではないので、ここでは見えません。
対応コードを見てください
class Loaded(val inst: Instrumentation, out: PrintOut)
  extends Command("loaded", "display loaded classes information.", out)
          with ClassSimpleNameCompleter {

  private val tab = "\t"

  private val hierarchyable   = flag("-h" :: "--classloader-hierarchies" :: Nil, "display classloader hierarchies of loaded class.")
  private val classSimpleName = parameter[String]("name", "class name without package name.")

  override def run() {
    val k = classSimpleName()
//    loaded class,     
    val matched = inst.getAllLoadedClasses filter { simpleNameOf(_) == k }
    if (matched.isEmpty) println("No matched class")
//      
    else matched foreach {
      c =>
        println(c.getName + " -> " + sourceOf(Manifest.classType(c)))
//   -h   ,     classloader 
        if (hierarchyable()) layout(Option(c.getClassLoader))
    }
  }

//        ,         classloader .
  @tailrec
  private def layout(cl: Option[ClassLoader], lastIndents: String = "- ") {
    cl match {
      case Some(loader) =>
        val indents = tab + lastIndents
        println(indents + getOrForceToNativeString(loader))
        layout(Option(loader.getParent), indents)
      case None         =>
    }
  }

}
 ここまでは、housemdの主な流れを完全に説明し、housemdのすべてのサポートの命令を見てください。
書き記す
housemd>help
quit terminate the process.
help display this infomation.
last show exception stack trace of last error.
trace display or output infomation of method invocaton.
loaded display loaded classis information.
env display system env.
inspect display fields of a class.
prop display system properties.
resource paths can loaded from everry classloader by resource full name.
 
 上の命令は三つに分けられます。
書き記す
1機能型命令quit、help、last.は、houssd命令自体の機能を提供するだけです。
2一般命令(第三の名前を区別するため)。loaded env prop resource
3バイトコードの変更が必要な命令trace inspect
 機能型命令です。多くは言いません。上で説明したコードの中で直接見ることができます。基本的には死にます。一般的な命令は、loaded命令を解析しました。他のいくつかは似たようなものです。対応する情報を入手して、それに合わせて印刷します。一つ一つ説明しません。以下はtrace命令を解析します。(inspect命令とtrace命令は似ています。ソースコードを見れば分かります。)を参照してください。バイトコードを修正する必要がある実装はどうなりますか?本質的にはやはりinstructionによって対応方法を修正します。
 
1.2.9 Trace
loaded命令を見ているのと似ています。直接に対応するrun方法を見ますが、Traceクラス全体を見てみると、run方法がないことが分かります。引き続き見てください。対応するrun方法は彼の父親タイプTrans formCommmandにあります。Trans formCommandのrun方法を直接見ることができます。
override def run() {
    val delegate = hook
//      .
    val h = new Hook {
      val intervalMillis = interval().toMillis
      var last           = 0L

      def heartbeat(now: Long) {
        if (now - last > intervalMillis) {
          delegate.heartbeat(now)
          last = now
        }
      }

      def finalize(throwable: Option[Throwable]) {
        delegate.finalize(throwable)
      }

      def enterWith(context: Context) {
        delegate.enterWith(context)
      }

      def exitWith(context: Context) {
        delegate.exitWith(context)
      }
    }

    transform(inst, filter, timeout(), overLimit(), this, h)
  }
 見られます。ここではフックを作成する以外に、transformメソッドを呼び出しました。transformはTrans formクラスにあります。
def apply(inst: Instrumentation, filter: Filter, timeout: Seconds, overLimit: Int, log: Loggable, hook: Hook) {
    implicit val i = inst
    implicit val t = timeout
    implicit val l = log
    implicit val h = hook
//    loaded  ,           .
    val candidates = inst.getAllLoadedClasses filter {
      c =>
//           ,    .       
        @inline
        def skipClass(description: String)(cause: Class[_] => Boolean) =
          if (cause(c)) {log.warn("Skip %1$s %2$s" format(c, description)); false } else true

        @inline
        def isNotBelongsHouseMD = skipClass("belongs to HouseMD") { _.getName.startsWith("com.github.zhongl.housemd") }

        @inline
        def isNotInterface = skipClass("") { _.isInterface }

        @inline
        def isNotFromBootClassLoader = skipClass("loaded from bootclassloader") { isFromBootClassLoader }

        filter(c) && isNotBelongsHouseMD && isNotInterface && isNotFromBootClassLoader
    }

    if (candidates.isEmpty) {
      log.println("No matched class")
    } else {
     //              ,    ,    asm     .       .
      val probeDecorator = classFileTransformer(filter, candidates)
     //       .
      inst.addTransformer(probeDecorator, true)
//        addTransformer    true class  load.
      probe(candidates, advice(overLimit))
      //       .        .         option,           
      try {handleAdviceEvent} finally {
    //      ,        .
        inst.removeTransformer(probeDecorator)
        reset(candidates)
      }

    }

  }
 
1.3エンディング
housemdは非常にシンプルな解析で、そろそろ一段落しました。その後、javaで時間があるかどうかも確認してみます。時間があるかどうかを確認します。