CloudWatch のカスタムメトリクスで JVM の GC を監視する

Posted on

JVM 上で動くアプリケーションを運用するには GC に気を配る必要があります。 GC をうまくチューニングするためには、まずは現状を知ることが大切です。

GC の統計情報は jstat -gcutil で取得することができます。試しに Jenkins のプロセスを見てみます。

$ pid=`sudo jps | grep jenkins | awk '{ print $1 }'`
$ sudo jstat -gcutil ${pid}
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
  0.00  57.68  21.33  66.26  99.51     73    0.179     4    0.271    0.450

この統計情報を定期的に取得してビジュアライズすれば GC の傾向がつかめます。この AWS 全盛期に昔ながらの RRDtool は使いたくないので、今回は CloudWatch でビジュアライズしてみます。

CloudWatch を使うメリット

CloudWatch にデータがあれば AWS の他のサービスと連携しやすいので、GC の傾向に応じてスケールアウトさせることも可能になります。たとえば、バッチ処理で Full GC の回数が増えて処理が遅延してきたら新しいインスタンスを立ち上げて分担する、など(もちろん設計によりますが)。

運用オペレーターが RRDtool のグラフをチェックして対応するのはオンプレミス時代の話。 AWS の強みを活かして運用コストを下げましょう。

シェルスクリプトの解説

シェルスクリプトで GC の統計情報から必要な値を抜き出して、AWS CLI (Python 版) で CloudWatch に送ります。 IAM role で EC2 インスタンスに権限を与えているので、スクリプトの中にアクセスキーなどの Security Credentials 情報は書いていません。

今回は Old 領域の使用率、Full GC の回数、Full GC にかかった時間を取得しています。

#!/bin/sh

if [ $# -eq 1 ]; then
    process_name="${1}"
else
    exit 1
fi

meta_data='http://169.254.169.254/latest/meta-data'
instance_id=`curl -sS ${meta_data}/instance-id`
pid=`jps | grep ${process_name} | awk '{ print $1 }'`
jstat=`jstat -gcutil ${pid} | tail -n 1`

OLD=`echo ${jstat} | awk '{ print $4 }'`
FGC=`echo ${jstat} | awk '{ print $8 }'`
FGCT=`echo ${jstat} | awk '{ print $9 }'`

export AWS_DEFAULT_REGION=`curl -sS ${meta_data}/placement/availability-zone | sed -e 's/.$//g'`

aws cloudwatch put-metric-data \
    --namespace 'JVM GC' \
    --dimensions InstanceId=${instance_id} \
    --metric-name 'Old Ratio' \
    --value ${OLD} \
    --unit Percent

aws cloudwatch put-metric-data \
    --namespace 'JVM GC' \
    --dimensions InstanceId=${instance_id} \
    --metric-name 'Full GC Count' \
    --value ${FGC} \
    --unit Count

aws cloudwatch put-metric-data \
    --namespace 'JVM GC' \
    --dimensions InstanceId=${instance_id} \
    --metric-name 'Full GC Time' \
    --value ${FGCT} \
    --unit Milliseconds

インスタンス ID やリージョンはインスタンスメタデータから、Java のプロセス名はシェルスクリプトの引数で与えています。実行してエラーにならなければ OK です。

$ sudo sh -x gc-report.sh jenkins

AWS Management Console から CloudWatch を開くと、Custom Metrics のプルダウンメニューに「JVM GC」というメニューが増えているはずです。

cron で実行する

ここまでできれば、あとは cron で定期的に実行するだけです。ここでは 5 分おきに実行しています。

$ sudo sh -c 'echo "*/5 * * * * root /usr/local/bin/gc-report.sh jenkins" > /etc/cron.d/gc-report'

アプリケーションを実行しているユーザーは ec2-user とは別だと思うので、root ユーザーの cron に設定します。

CloudWatch でグラフを確認する

少し時間を開けてから、ちゃんとグラフが描けているか確認してみます。

CloudWatch のグラフ

いい感じに取れていますね。

まとめ

CloudWatch のカスタムメトリクスを使えば JVM の GC も簡単にビジュアライズできます。工夫次第でどんな値でも取れるので、うまく活用してみたいと思います。