日曜日 5 13, 2007
日曜日 5 13, 2007
前回のエントリ(Part 1)では、JRubyを用いてアプリケーションサーバに含まれるJMXサーバに接続し、HTTPリスナMBeanからスレッド数を取得するまでの基本的な流れを示しました。
ここでの目的は、自分が興味のある監視項目を一定間隔で取得するツールに仕上げることですので、前回のサンプルに以下の2つの機能を追加することになります。
MBean名と属性名の一覧定義には、簡略に定義できるように以下のような2次元配列(MBean:属性=1:nなので、実際には3次元配列)の表現を用いることにします。
# 監視対象のMBean名と属性の定義
mbeandefs = [
# [<MBean名>, [<属性1>, <属性2>,...]]
["com.sun.appserv:name=http-listener-1," +
"virtual-server=server,type=http-listener,category=monitor,server=server",
["currentthreadcount-count", "currentthreadbusy-count"]
]
].each { |def| def[0] = ObjectName.new(def[0]) }
上記の例では、Sun Java System Application Server (glassfish)におけるhttp-listener-1のMBeanに対して、プール内のスレッド数("currentthreadcount-count")とその中で実際に処理中のスレッド数("currentthreadbusy-count")を取得することを定義しています。なお、ここではRuby配列に対してeachメソッドを適用し、配列内のMBean名を予めObjectNameに変換しておきます。
後は、ループを定義して、mbeandefs配列に定義された全ての監視項目についての属性値の取得を、一定間隔毎に実行する定義をすればよいことになります。
# 各MBeanの属性を取得・表示する
while true
mbeandefs.each do |mbeandef|
name, attrs = mbeandef[0], mbeandef[1]
values = con.getAttributes(name, attrs.to_java(:String))
values.each do |data|
print "#{data.value}¥t"
end
end
puts
sleep interval
end
getAttributes(ObjectName,String[])メソッドの戻り値はAttributeListオブジェクトですが、JRubyではJavaのCollectionクラスであってもeachメソッドを用いたループが定義できる点が嬉しいところです。なお、Rubyの配列はJavaのString[]に自動変換されないようなので、to_javaメソッドを用いて明示的にString[]に変換しています。
後は、mbeandefs配列に監視したいMBeanの定義を追加していけばいいのですが、実際にはもう少し汎用性を高める必要があります。
MBeanには、単純にgetAttribute(ObjectName,String)メソッドで単純な値を返してくれるものだけではありません。たとえば、JVMに標準で含まれるメモリ概要に関するMBean("java.lang:type=Memory")の属性"HeapMemoryUsage"を取得すると、複数のプロパティをもったCompositeDataとなっていることが分かります。
irb(main):032:0> data = con.getAttribute(
irb(main):033:1* ObjectName.new("java.lang:type=Memory"), "HeapMemoryUsage")
=> #<#<Class:01x3228a1>:0x10980e7 @java_object=javax.management.openmbean.Compos
iteDataSupport(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.manage
ment.openmbean.SimpleType(name=java.lang.Long)),(itemName=max,itemType=javax.man
agement.openmbean.SimpleType(name=java.lang.Long)),(itemName=used,itemType=javax
.management.openmbean.SimpleType(name=java.lang.Long)))),contents={committed=415
66208, init=0, max=518979584, used=33731368})>
このようなCompositeDataオブジェクトに対しては、更にget(String)メソッドを用いてサブプロパティのキーを与えてあげる必要があります。
irb(main):034:0> data.get("used")
=> 29729624
そこで、監視項目の定義mbeandefs[]の仕様を見直し、CompositeDataの属性値を取得する場合は、属性値とサブプロパティのキーを"."で結合した"A.e"の形式で定義することにします。
# 監視対象のMBean名と属性の定義
mbeandefs = [
# [<MBean名>, [<属性1>, <属性2>,...]]
["java.lang:type=Memory" ["HeapMemoryUsage.used"]
].each { |def| def[0] = ObjectName.new(def[0]) }
属性値を取得する時には、属性名に"."が含まれているかどうかで以下のように条件分岐すれば、シンプルな属性とCompositeDataの値をまとめて取得することができます。MBean属性値を取得するループの部分をCompositeDataにも対応できるように改良すると以下のようになります。CompositeDataの場合であってもJavaのようなキャストは必要なく、そのままget(String)メソッドを呼び出せるのは嬉しいところです。
# 各MBeanの属性を取得・表示する
while true
mbeandefs.each do |mbeandef|
name = mbeandef[0]
# "<属性>.<サブ属性>"を"."で分解し、それぞれattrs[]、subattrs[]にまとめる
attrs, subattrs = [], []
mbeandef[1].each do |attrdef|
attr = attrdef.split(".")
attrs << attr[0]
subattrs << attr[1] # "."がなければnil
end
# MBeanの属性値をまとめて取得する
values = con.getAttributes(name, attrs.to_java(:String))
# 属性値のリストを表示する
for i in 0..(values.size-1)
if subattrs[i].nil? then
print "#{values[i].value}¥t"
else
print "#{values[i].value.get(subattrs[i])}¥t"
end
end
end
puts
sleep interval
end
MBeanの中には、環境が変わると名前が変わってしまうものがあります。例えば、JVMのメモリ概要が取得できるtype=MemoryPoolなMBeanは、デフォルトでは以下のような名前で取得されます。
もし、JVMでパラレルGCを有効にすると、"Eden Space"と"Survivor Space"は、それぞれ"Par Eden Space"と"Par Survivor Space"という名前に変わります。
また、コンカレントGCを有効にすると、"Tenured Gen"と"Perm Gen"が"CMS Old Gen"と"CMS Perm Gen"に変わります。
このように環境によって名前が変わるMBeanを監視対象にする場合、監視項目定義であるmbeandefs[]の管理が面倒になります。このような場合は、MBean名の中で変更される可能性のある部分をワイルドカード"*"に置き換え、queryNames(ObjectName,QueryExp)メソッドを用いて実行時に目的のMBeanを見つけ出すようにするとよいでしょう。MemoryPoolを監視対象とする場合は、以下のようなコードになります。
objnames = con.queryNames(ObjectName.new("java.lang:type=MemoryPool,*"), nil)
objnames.each do |name|
mbeandefs << [name, ["Usage.used", "Usage.max"]]
end
また、アプリケーションを構成する各コンポーネント(Servlet、JSP、EJBなど)を監視対象にしたい場合では、全てのコンポーネントのMBean名をmbeandefs[]に列挙するのは面倒ですし、コンポーネントの名前が変われば、それに合わせてmbeandefs[]もメンテナンスしなければなりませんん。このような場合も同様にワイルドカードが役に立ちます。例えば、Sun Java System Application Server (glassfish)のEJBコンテナ上で動作している全てのEJBについて、EJBプールの使用状態を監視するには、以下のような定義をすればよいことになります。
objnames = con.queryNames(
ObjectName.new("com.sun.appserv:type=bean-pool,*"), nil)
objnames.each do |name|
mbeandefs << [name, ["numbeansinpool-highwatermark",
"numbeansinpool-current"]]
end
いかがでしょうか。JRubyのようなスクリプト言語がJavaの実行環境で利用できるようになったことにより、多彩なJavaのAPIをスクリプト言語の簡潔なシンタックスを用いてアクセスできるようになりました。これにより、アプリケーション開発をサポートするためのカスタムツールを簡単に作成できるようになったメリットは大変大きいものがあります。JMXカスタムクライアントは、Javaにおけるスクリプト言語サポートを有効に利用する1つの代表的な例であるということができるでしょう。ここで紹介したサンプルのJRubyスクリプトを以下のリンクからダウンロードできるようにしました。
上記スクリプトはまだまだ改善の余地が沢山ありますので、興味のある方は自由にダウンロードして、ご自身の開発プロジェクト用にカスタマイズして使ってみてください。