CLOVER🍀

That was when it all began.

GroovyでJMX

JavaアプリケーションをJMXで覗いてみるのに、JConsoleもいいんですけど、ちょっとGroovyを使ってみたくなりまして。

こちらのページを参考に、さらっと書いてみました。

Groovy and JMX
http://groovy.codehaus.org/Groovy+and+JMX

とりあえず、監視する何かのプログラムがないと始まらないので、簡単なダミーを。
dummy-script.groovy

Thread.start('dummy-thread') {
    while (true) {
        println("waiting...")
        Thread.sleep(3 * 1000L)
    }
}

JMX関連の起動パラメータの設定をして

$ export JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9012 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"

動かしておきます。

$ groovy dummy-script.groovy
waiting...
waiting...

では、このGroovyスクリプトを除いてみるプログラムを。

まずはimport文。

import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL

まあ、普通です。

今回のJMX接続URLと合わせて、MBeanServerConnectionを作成します。

def serverUrl = 'service:jmx:rmi:///jndi/rmi://localhost:9012/jmxrmi'

def serviceUrl = new JMXServiceURL(serverUrl)
def connector = JMXConnectorFactory.connect(serviceUrl)

で、MBeanServerConnectionを取得して、closeするまでの間にいろいろやります。

try {
    def server = connector.MBeanServerConnection

    // serverを使って、いろいろやる
} finally {
    connector.close()
}

JMXにクエリがあるなんて知らなかったのですが、Groovyのページを見て知ったので、せっかくなので使ってみました。

    def querys =
        [new ObjectName('java.lang:*'),
         new ObjectName('java.nio:*')]

    def allModules =
        querys.collect { objectName ->
            server
              .queryNames(objectName, null)
              .collect { resultName -> new GroovyMBean(server, resultName) }
        }

ObjectNameを作るときに、「*」とその前の「java.lang」とかのJMXドメインで絞り込めるみたいです。絞り込むときは、MBeanServerConnection#queryNamesとかqueryMBeansで。

今回は、queryNamesを使用しています。検索したJMXドメインは、「java.lang」と「java.nio」です。メソッドの戻り値は、ObjectNameのSetとなります。そこから、ObjectNameを使ってGroovyMBeanを作成しています。

GroovyMBean
http://groovy.codehaus.org/api/groovy/util/GroovyMBean.html

GroovyMBeanを使用すると、MBeanの属性や操作の一覧が簡単に取得できたりします。

では、取得したGroovyMBeanに対して、属性や操作の一覧を出力するようにしてみます。

    allModules.each { modules ->
        modules.each { mbean ->
            def format = { collection ->
                    collection
                      .collect { "      Name: [$it]${System.lineSeparator()}" }
                      .join('')
            }

            def safeFormat = { cls ->
                try {
                     cls.call()
                } catch (Exception e) {
                    "Got Exception: $e"
                }
            }

            def formatAttributes = { names ->
                    names
                      .collect { name ->
                        "      Name: [$name], " +
                        "Value: [${safeFormat { mbean.getProperty(name) }}]" +
                        "${System.lineSeparator()}" }
                      .join('')
            }

            def attributes = formatAttributes(mbean.listAttributeNames())
            def operations = format(mbean.listOperationNames())
            def operationDescriptions = format(mbean.listOperationDescriptions())

            def mbeanInfo = """|[MBean]
                               |  Name: ${mbean.name()}
                               |    Attributes:
                               |${attributes}
                               |    Operations:
                               |${operations}
                               |    OperationDescriptions:
                              |${operationDescriptions}""".stripMargin()
            println(mbeanInfo)
        }
    }

属性の値を抜く時、今回みたいに総当たりで取得しようとすると、一部の属性は取得できず例外が飛んでくやつがいたので、ちょっとごまかしています。

            def safeFormat = { cls ->
                try {
                     cls.call()
                } catch (Exception e) {
                    "Got Exception: $e"
                }
            }

            def formatAttributes = { names ->
                    names
                      .collect { name ->
                        "      Name: [$name], " +
                        "Value: [${safeFormat { mbean.getProperty(name) }}]" +
                        "${System.lineSeparator()}" }
                      .join('')
            }

ホントは、取得する項目はきっちり定義するのでしょうが。

これを動作させると、こんな結果が出力されます。
*それなりに出力されるので、けっこう端折っています

[MBean]
  Name: java.lang:type=Memory
    Attributes:
      Name: [Verbose], Value: [false]
      Name: [HeapMemoryUsage], Value: [javax.management.openmbean.CompositeDataSupport(compositeType=javax.management.openmbean.CompositeType(name=java.lang.management.MemoryUsage,items=((itemName=committed,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=init,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=max,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=used,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)))),contents={committed=90243072, init=74642048, max=1062600704, used=35327544})]
      Name: [NonHeapMemoryUsage], Value: [javax.management.openmbean.CompositeDataSupport(compositeType=javax.management.openmbean.CompositeType(name=java.lang.management.MemoryUsage,items=((itemName=committed,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=init,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=max,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=used,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)))),contents={committed=24313856, init=24313856, max=136314880, used=17065640})]
      Name: [ObjectPendingFinalizationCount], Value: [0]
      Name: [ObjectName], Value: [java.lang:type=Memory]

    Operations:
      Name: [gc]

    OperationDescriptions:
      Name: [void gc()]

[MBean]
  Name: java.lang:type=GarbageCollector,name=PS MarkSweep
    Attributes:
      Name: [LastGcInfo], Value: [null]
      Name: [CollectionCount], Value: [0]
      Name: [CollectionTime], Value: [0]
      Name: [Name], Value: [PS MarkSweep]
      Name: [Valid], Value: [true]
      Name: [MemoryPoolNames], Value: [[PS Eden Space, PS Survivor Space, PS Old Gen, PS Perm Gen]]
      Name: [ObjectName], Value: [java.lang:type=GarbageCollector,name=PS MarkSweep]

    Operations:

    OperationDescriptions:

[MBean]
  Name: java.lang:type=Threading
    Attributes:
      Name: [ThreadAllocatedMemoryEnabled], Value: [true]
      Name: [ThreadAllocatedMemorySupported], Value: [true]
      Name: [ThreadContentionMonitoringEnabled], Value: [false]
      Name: [DaemonThreadCount], Value: [14]
      Name: [PeakThreadCount], Value: [16]
      Name: [CurrentThreadCpuTimeSupported], Value: [true]
      Name: [ObjectMonitorUsageSupported], Value: [true]
      Name: [SynchronizerUsageSupported], Value: [true]
      Name: [ThreadContentionMonitoringSupported], Value: [true]
      Name: [ThreadCpuTimeEnabled], Value: [true]
      Name: [AllThreadIds], Value: [[28, 27, 26, 24, 23, 22, 21, 15, 13, 12, 11, 10, 9, 4, 3, 2]]
      Name: [CurrentThreadCpuTime], Value: [94469784]
      Name: [CurrentThreadUserTime], Value: [50000000]
      Name: [ThreadCount], Value: [16]
      Name: [TotalStartedThreadCount], Value: [24]
      Name: [ThreadCpuTimeSupported], Value: [true]
      Name: [ObjectName], Value: [java.lang:type=Threading]

    Operations:
      Name: [getThreadCpuTime]
      Name: [getThreadCpuTime]
      Name: [getThreadUserTime]
      Name: [getThreadUserTime]
      Name: [getThreadAllocatedBytes]
      Name: [getThreadAllocatedBytes]
      Name: [dumpAllThreads]
      Name: [findDeadlockedThreads]
      Name: [findMonitorDeadlockedThreads]
      Name: [getThreadInfo]
      Name: [getThreadInfo]
      Name: [getThreadInfo]
      Name: [getThreadInfo]
      Name: [getThreadInfo]
      Name: [resetPeakThreadCount]

    OperationDescriptions:
      Name: [[J getThreadCpuTime([J p0)]
      Name: [long getThreadCpuTime(long p0)]
      Name: [[J getThreadUserTime([J p0)]
      Name: [long getThreadUserTime(long p0)]
      Name: [[J getThreadAllocatedBytes([J p0)]
      Name: [long getThreadAllocatedBytes(long p0)]
      Name: [[Ljavax.management.openmbean.CompositeData; dumpAllThreads(boolean p0, boolean p1)]
      Name: [[J findDeadlockedThreads()]
      Name: [[J findMonitorDeadlockedThreads()]
      Name: [[Ljavax.management.openmbean.CompositeData; getThreadInfo([J p0, boolean p1, boolean p2)]
      Name: [javax.management.openmbean.CompositeData getThreadInfo(long p0)]
      Name: [javax.management.openmbean.CompositeData getThreadInfo(long p0, int p1)]
      Name: [[Ljavax.management.openmbean.CompositeData; getThreadInfo([J p0, int p1)]
      Name: [[Ljavax.management.openmbean.CompositeData; getThreadInfo([J p0)]
      Name: [void resetPeakThreadCount()]

一部の属性とか、かなり大きな出力になります。

JMXに慣れていれば、けっこう簡単に使える?スクリプトで簡単に書くなら、やっぱりGroovyですよね。

では、今回作成したコードを貼っておきます。
jmx-monitor.groovy

import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL

def serverUrl = 'service:jmx:rmi:///jndi/rmi://localhost:9012/jmxrmi'

def serviceUrl = new JMXServiceURL(serverUrl)
def connector = JMXConnectorFactory.connect(serviceUrl)

try {
    def server = connector.MBeanServerConnection

    def querys =
        [new ObjectName('java.lang:*'),
         new ObjectName('java.nio:*')]

    def allModules =
        querys.collect { objectName ->
            server
              .queryNames(objectName, null)
              .collect { resultName -> new GroovyMBean(server, resultName) }
        }

    allModules.each { modules ->
        modules.each { mbean ->
            def format = { collection ->
                    collection
                      .collect { "      Name: [$it]${System.lineSeparator()}" }
                      .join('')
            }

            def safeFormat = { cls ->
                try {
                     cls.call()
                } catch (Exception e) {
                    "Got Exception: $e"
                }
            }

            def formatAttributes = { names ->
                    names
                      .collect { name ->
                        "      Name: [$name], " +
                        "Value: [${safeFormat { mbean.getProperty(name) }}]" +
                        "${System.lineSeparator()}" }
                      .join('')
            }

            def attributes = formatAttributes(mbean.listAttributeNames())
            def operations = format(mbean.listOperationNames())
            def operationDescriptions = format(mbean.listOperationDescriptions())

            def mbeanInfo = """|[MBean]
                               |  Name: ${mbean.name()}
                               |    Attributes:
                               |${attributes}
                               |    Operations:
                               |${operations}
                               |    OperationDescriptions:
                              |${operationDescriptions}""".stripMargin()
            println(mbeanInfo)
        }
    }
} finally {
    connector.close()
}