Tomcatのパフォーマンスについて調べる事になると必ず必要になってくる、「JMXメトリクスを取得できる仕組み」を用意しておきます。TomcatのJMXメトリクスを取得する方法はいくつかありますが、今回はJolokiaを使う方法で行います。Jolokiaを使うとHTTPベース(curlコマンド)でJMXメトリクスを取得できるためです。Jolokiaを利用するにあたっても、JAR形式とWAR形式を利用する2パターンがあります。今回はTomcatが動作するJVMにアタッチする(-javaagentオプション)方法で行います。WAR形式の場合TomcatへデプロイするだけでJMXメトリクスが取得できるようになってお手軽なのですが、JAR形式のほうがJVMの低レベルのメトリクスまで取得可能という事らしいです。(まだそれを実感できるまで使い込めてはいないですが、、)今回WARタイプを選択しなかった理由は、純粋にTomcatにデプロイしているアプリケーションの動作を観察したいためです。WARタイプを使うとJolokiaエージェント自体もTomcatアプリケーションのJMXメトリクスに含まれてしまいます。
以下のダウンロードサイトから、JVM-Agent版をダウンロードしてください。私がダウンロードしたタイミングのバージョンは「jolokia-jvm-2.2.9-javaagent.jar」でした。
https://jolokia.org/download.html
これを適当なところに配備してください。私は/opt/jolokia/配下にjolokia-agent.jarという名前で配備しました。
JMXの有効化
rootで以下実施します。デフォルトでsetenv.shは存在していませんが、$CATALINA_HOME/binの配下に作成しておけばtomcat起動時に、catalina.shの中から起動がかかるようになっています。
touch $CATALINA_HOME/bin/setenv.sh
chmod +x $CATALINA_HOME/bin/setenv.sh
chown tomcat:tomcat $CATALINA_HOME/bin/setenv.sh
vi $CATALINA_HOME/bin/setenv.sh
以下内容(JVMオプション)を記載し、JVMにJolokiaのエージェントを組み込みます。
export CATALINA_OPTS="$CATALINA_OPTS \
-javaagent:/opt/jolokia/jolokia-agent.jar=port=8778,host=127.0.0.1"
port 8778がリスニングポイントで、host=127.0.0.1とする事でローカルからの接続のみ可能となります。
Tomcatを再起動して確認する
[sooni@vm105 ~]$ sudo systemctl restart tomcat
[sudo] sooni のパスワード:
[sooni@vm105 ~]$
-- リスニングポート確認
[sooni@vm105 ~]$ netstat -tulnp | grep 8778
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp6 0 0 127.0.0.1:8778 :::* LISTEN -
[sooni@vm105 ~]$
実際にcurlをたたいてメトリクスを取得
curl -s -X POST http://localhost:8778/jolokia/ -H 'Content-Type: application/json' -d '
[
{ "type": "read", "mbean": "java.lang:type=Memory", "attribute": "HeapMemoryUsage" },
{ "type": "read", "mbean": "java.lang:type=MemoryPool,name=Metaspace", "attribute": "Usage" },
{ "type": "read", "mbean": "java.lang:type=Threading", "attribute": "ThreadCount" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool", "attribute": "currentThreadCount" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool", "attribute": "currentThreadsBusy" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool", "attribute": "connectionCount" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "requestCount" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "processingTime" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "errorCount" },
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "bytesReceived" },
{ "type": "read", "mbean": "java.lang:name=PS Scavenge,type=GarbageCollector", "attribute": "CollectionCount" },
{ "type": "read", "mbean": "java.lang:name=PS MarkSweep,type=GarbageCollector", "attribute": "CollectionCount" }
]' | jq
以下解説
--ヒープメモリの使用状況を取得
{ "type": "read", "mbean": "java.lang:type=Memory", "attribute": "HeapMemoryUsage" }
--取得例
{
"request": {
"mbean": "java.lang:type=Memory",
"attribute": "HeapMemoryUsage",
"type": "read"
},
"value": {
"init": 536870912,
"committed": 514850816,
"max": 954728448,
"used": 49032976
},
"status": 200,
"timestamp": 1746792116
}
--Metaspace のメモリ使用状況 を取得
{ "type": "read", "mbean": "java.lang:type=MemoryPool,name=Metaspace", "attribute": "Usage" }
--取得例
{
"request": {
"mbean": "java.lang:name=Metaspace,type=MemoryPool",
"attribute": "Usage",
"type": "read"
},
"value": {
"init": 0,
"committed": 21299200,
"max": -1, -- 無制限
"used": 20767008
},
"status": 200,
"timestamp": 1746780166
}
--JVM全体のアクティブなスレッド数 を取得
{ "type": "read", "mbean": "java.lang:type=Threading", "attribute": "ThreadCount" }
--取得例
{
"request": {
"mbean": "java.lang:type=Threading",
"attribute": "ThreadCount",
"type": "read"
},
"value": 25, -- アクティブなスレッド数
"status": 200,
"timestamp": 1746760832
}
-- Tomcat のスレッドプールスレッド総数 を取得
-- 負荷が上がればmaxThreadsまでは増えるが減る事はない。(Tomcat再起動でリセット)
--
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool", "attribute": "currentThreadCount" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool",
"attribute": "currentThreadCount",
"type": "read"
},
"value": 10,
"status": 200,
"timestamp": 1746780166
}
-- Tomcat のスレッドプール内で実際にリクエストを処理しているスレッドの数 を取得
-- リクエストが殺到すると増加し、処理完了後は減少。動作していない状況においては0となる
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool", "attribute": "currentThreadsBusy" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool",
"attribute": "currentThreadsBusy",
"type": "read"
},
"value": 1,
"status": 200,
"timestamp": 1746775370
}
--Tomcat がうけつけているリクエスト数 を取得
-- 瞬間値。currentThreadsBusyとほぼ同じ値になる。Keep-Aliveが利用された場合は
-- 違いが出てきそう、、
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool", "attribute": "connectionCount" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=ThreadPool",
"attribute": "connectionCount",
"type": "read"
},
"value": 1,
"status": 200,
"timestamp": 1746779017
}
-- Tomcat の HTTP コネクタ (http-nio-8080) が処理したリクエストの総数 を取得
-- Tomcatが立ち上がっている限り減る事は無い。再起動で0クリアされる。
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "requestCount" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor",
"attribute": "requestCount",
"type": "read"
},
"value": 28,
"status": 200,
"timestamp": 1746775939
}
-- Tomcatが各リクエストにどれくらい時間を使っているかの累積時間(ミリ秒) を取得
-- Tomcatが立ち上がっている限り減る事は無い。再起動で0クリアされる。
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "processingTime" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor",
"attribute": "processingTime",
"type": "read"
},
"value": 4837,
"status": 200,
"timestamp": 1746776109
}
-- HTTPリクエストで発生したエラーの累積総数 を取得
-- Tomcatが立ち上がっている限り減る事は無い。再起動で0クリアされる。
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "errorCount" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor",
"attribute": "errorCount",
"type": "read"
},
"value": 0,
"status": 200,
"timestamp": 1746776410
}
-- Tomcat起動時から現在までに受信したリクエストのデータ量(累積値)
-- HTTPリクエストのヘッダー・ボディ・パラメータすべてが含まれる
-- Tomcatが立ち上がっている限り減る事は無い。再起動で0クリアされる。
{ "type": "read", "mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor", "attribute": "bytesReceived" }
--取得例
{
"request": {
"mbean": "Catalina:name=\"http-nio-8080\",type=GlobalRequestProcessor",
"attribute": "bytesReceived",
"type": "read"
},
"value": 16643,
"status": 200,
"timestamp": 1746776744
}
GCに関するMBean名はGCアルゴリズムによって複数あります
以下curlで対象JVMのGCアルゴリズムがわかります。
curl -s http://localhost:8778/jolokia/search/java.lang:name=*,type=GarbageCollector | jq
Parallel GC用
--JVM の Young GC (PS Scavenge) の実行回数 を取得
{ "type": "read", "mbean": "java.lang:name=PS Scavenge,type=GarbageCollector", "attribute": "CollectionCount" }
--取得例
{
"request": {
"mbean": "java.lang:name=PS Scavenge,type=GarbageCollector",
"attribute": "CollectionCount",
"type": "read"
},
"value": 10,
"status": 200,
"timestamp": 1746760832
}
--JVM の Old GC (PS MarkSweep) (フルGC)の実行回数 を取得
{ "type": "read", "mbean": "java.lang:name=PS MarkSweep,type=GarbageCollector", "attribute": "CollectionCount" }
--取得例
{
"request": {
"mbean": "java.lang:name=PS MarkSweep,type=GarbageCollector",
"attribute": "CollectionCount",
"type": "read"
},
"value": 3,
"status": 200,
"timestamp": 1746760832
}
G1 GC用
以下MBean名を利用すると取得できます。
"java.lang:name=G1 Young Generation,type=GarbageCollector"
"java.lang:name=G1 Old Generation,type=GarbageCollector"
"java.lang:name=G1 Concurrent GC,type=GarbageCollector"
Fluent-Bitで収集してElasticsearchへ連携
おまけ
JMXのメトリクスを取得するにはRMIを使う方法もあります。その場合は以下を$CATALINA_HOME/bin/setenv.shへ追記してください。
RMIを使う場合
export CATALINA_OPTS="$CATALINA_OPTS \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false"