0
0

More than 3 years have passed since last update.

JMXでTomcatのMBeanからメモリ/スレッド利用数を取得してCloudWatchへ通知する

Posted at

本内容について

JMX(Java Management eXtentions)は稼働中のJavaに対する管理インターフェースを提供します。
今回はAWS EC2で稼働するLinuxサーバのTomcatでJMX設定を有効化し、Javaのヒープやスレッド数を取得、CloudWatchで可視化する内容です。

プログラム概要

※Javaは1.2で学習が止まっているのと、そこからほぼ書いていなかったため、記述方法などについてはご容赦ください。

サーバ内へ常駐し、所定の間隔で情報を通知する形にしています。
ループ内でJMXの参照を取得、各メトリックを取得し、一括でCloudWatchに通知する流れです。

取得する対象のMemoryPoolMxBeanや、スレッドの名称などは、後述のCommand-line JMX Client等で確認し、環境に合った対象にしておく必要があります。(JavaのGC方式によって取得対象のMemoryPoolMxBeanの名称などが異なります。)

  • 毎回 getMBeanServerConnection() を呼んでいるのは、Tomcatサーバの一時的な停止が発生した場合にも、起動後に取得できるようにしています。
  • 本プログラムはログを出力していないので、必要であれば追加してください。(Exceptionを黙殺しているのはそのため・・・)

参考

JMXで具体的に何が取得できるのかは、「Command-line JMX Client」のお世話になった。

コード

code.java
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.ThreadMXBean;
import javax.management.ObjectName;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClientBuilder;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.MetricDatum;
import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
import com.amazonaws.services.cloudwatch.model.PutMetricDataResult;
import com.amazonaws.services.cloudwatch.model.StandardUnit;

/**
 * リモートJVMからMemoryMBeanを取得する
 * */
public class JMXReporter{

        private static MBeanServerConnection remote = null;
        public static void main(String[] args) throws Exception {

                if (args.length != 4){
                        System.out.println("引数: ホスト名 取得対象tomcat service名称 ポート番号 間隔(秒)");
                        System.exit(1);
                }

                // JMX接続先ホスト名、ポート、接続間隔の取得・設定
                String host = "localhost";
                int port = Integer.parseInt(args[2]);
                int iv = Integer.parseInt(args[3]);

                final String serviceUrlStr = "service:jmx:rmi:///jndi/rmi://"
                                + host + ":" + port + "/jmxrmi";
                final JMXServiceURL target = new JMXServiceURL(serviceUrlStr);

                // 無限ループ
                while(true){

                       JMXConnector connector = null;
                        try {
                                // JMX接続のコネクション取得
                                // 対象の起動停止を考慮し、都度接続する。
                                connector = JMXConnectorFactory.connect(target);
                                remote = connector
                                        .getMBeanServerConnection();
                        } catch (IOException e) {
                                // 接続エラー時はスキップ
                                System.err.println("failed to connect: " + target.toString());
                                Thread.sleep(iv * 1000);
                                continue;
                        }

                        // MemoryPoolMxBeanからの情報取得(GC方式変更時は対象名称の修正が必要)
                        List<MemoryPoolMXBean> beans = new ArrayList<MemoryPoolMXBean>();

                        beans.add(getMemoryPoolMxBean("Par Survivor Space"));
                        beans.add(getMemoryPoolMxBean("Par Eden Space"));
                        beans.add(getMemoryPoolMxBean("CMS Old Gen"));
                        beans.add(getMemoryPoolMxBean("Metaspace"));
                        beans.add(getMemoryPoolMxBean("Compressed Class Space"));
                        beans.add(getMemoryPoolMxBean("Code Cache"));

                        // CloudWatchのクライアント取得
                        final AmazonCloudWatch cw =
                                AmazonCloudWatchClientBuilder.defaultClient();

                        // CloudWatchのディメンションを作成
                        List<Dimension> dimList = new ArrayList<Dimension>();
                        dimList.add( new Dimension()
                        .withName("host")
                        .withValue(args[0]));
                        dimList.add( new Dimension()
                        .withName("tomcat instance")
                        .withValue(args[1]));

                        // MemoryPoolの各beanから値を取り出してListへ積んでおく
                        List<MetricDatum> datumList = new ArrayList<MetricDatum>();

                        for(MemoryPoolMXBean bean : beans) {
                                if(bean == null){continue;}

                                datumList.add( new MetricDatum()
                                                .withMetricName(bean.getName())
                                                .withUnit(StandardUnit.None)
                                                .withValue(new Double(bean.getUsage().getUsed()))
                                                .withDimensions(dimList));
                        }

                        // スレッド数データも追加
                        ObjectName ajpThreadName = new ObjectName(
                                args[1] + ":name=\"ajp-nio-8009\",type=ThreadPool");

                        Double busyth = 0.0;
                        try {
                                busyth = new Double( remote.getAttribute(
                                                ajpThreadName, "currentThreadsBusy").toString());
                        }
                        catch(Exception e) {
                // 黙殺
                        }

                        datumList.add( new MetricDatum()
                                .withMetricName("currentThreadsBusy")
                                .withUnit(StandardUnit.None)
                                .withValue( busyth )
                                .withDimensions(dimList));


                        // 積んだデータを一括でPut
                        PutMetricDataRequest request = new PutMetricDataRequest()
                                .withNamespace("CUSTOM JMX")
                                .withMetricData( datumList );

                        try {
                                PutMetricDataResult response = cw.putMetricData(request);
                        }
                        catch( Exception e) {
                            // 黙殺
                        }

                        if(connector != null){connector.close();}

                        Thread.sleep(iv * 1000);
                }
        }
        /**
         * MxBean名からMemoryPoolMXBeanインスタンスを取得します。
         * @param beanName MxBean名
         * */
        private static MemoryPoolMXBean getMemoryPoolMxBean(String beanName) throws IOException{
                MemoryPoolMXBean mpmxbean
                = ManagementFactory.newPlatformMXBeanProxy(
                        remote,
                        ManagementFactory.MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",name=" + beanName,
                        MemoryPoolMXBean.class);
                return mpmxbean;
        }
}


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0