こんにちは、SWX3人目の熊谷(悠)です。
ZabbixやDatadogを入れるほどではないけど折角AWS使ってるんだしCloudWatchでグラフを見たい!
という事でスクリプトを作りました。
環境情報
まず、スクリプトからCloudWatchへ書き込みを行うために、EC2に割り当てるIAMロールにCloudWatchFullAccess
等のポリシーをアタッチしておいてください。
$ cat /etc/system-release Amazon Linux 2 $ uname -a Linux hogehuga.ap-northeast-1.compute.internal 4.14.77-81.59.amzn2.x86_64 #1 SMP Mon Nov 12 21:32:48 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ aws --v aws-cli/1.16.102 Python/2.7.14 Linux/4.14.77-81.59.amzn2.x86_64 botocore/1.12.92
$ java -version openjdk version "1.8.0_181" OpenJDK Runtime Environment (build 1.8.0_181-b13) OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) $ yum list installed | grep java java-1.8.0-openjdk.x86_64 1:1.8.0.191.b12-0.amzn2 @amzn2-core java-1.8.0-openjdk-devel.x86_64 1:1.8.0.191.b12-0.amzn2 @amzn2-core java-1.8.0-openjdk-headless.x86_64 1:1.8.0.191.b12-0.amzn2 @amzn2-core javapackages-tools.noarch 3.4.1-11.amzn2 @amzn2-core python-javapackages.noarch 3.4.1-11.amzn2 @amzn2-core tzdata-java.noarch 2018e-3.amzn2 @amzn2-core
openjdk-develもインストールしておかないと、jps
とjstat
コマンドが使用できないので無ければ以下コマンド等を用いてインストールください。
# yum -y install java-1.8.0-openjdk # yum -y install java-1.8.0-openjdk-devel
監視スクリプト
#!/bin/bash function usage { cat <<EOF Usage: $(basename ${0}) [options] <PROCESS_NAME> <NAMESPACE_NAME> Description: $(basename ${0}) is a tool for Get JVM information and send report to cloudwatch Required Args: PROCESS_NAME Java Process name to report on NAMESPACE_NAME Cloudwatch custom namespace name to report Options: -h show this help, then exit EOF return 0 } # オプション解析 while getopts h OPT ; do case $OPT in h) usage exit 0 ;; esac done if [ $# -ge 2 ]; then PROCESS_NAME="${1}" NAMESPACE_NAME="${2}" elif [ $# -eq 1 ]; then echo 'Error: Argument <NAMESPACE_NAME> is Required' usage exit 1 else echo 'Error: Argument <PROCESS_NAME> is Required' usage exit 1 fi PID=`jps -v | grep ${PROCESS_NAME} | awk '{ print $1 }'` if [ -z "$PID" ]; then echo 'Error: Process Not Found' exit 1 fi JSTAT=`jstat -gcutil ${PID} | tail -n 1` S0=`echo ${JSTAT} | awk '{ print $1 }'` S1=`echo ${JSTAT} | awk '{ print $2 }'` EDEN=`echo ${JSTAT} | awk '{ print $3 }'` OLD=`echo ${JSTAT} | awk '{ print $4 }'` META=`echo ${JSTAT} | awk '{ print $5 }'` FGC=`echo ${JSTAT} | awk '{ print $9 }'` FGCT=`echo ${JSTAT} | awk '{ print $10 }'` META_DATA='http://169.254.169.254/latest/meta-data' INSTANCE_ID=`curl -sS ${META_DATA}/instance-id` export AWS_DEFAULT_REGION=`curl -sS ${META_DATA}/placement/availability-zone | sed -e 's/.$//g'` aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Survivor area 0 usage rate' \ --unit Percent \ --value ${S0} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Survivor area 1 usage rate' \ --unit Percent \ --value ${S1} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Eden area usage rate' \ --unit Percent \ --value ${EDEN} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Old area usage rate' \ --unit Percent \ --value ${OLD} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Metaspace usage rate' \ --unit Percent \ --value ${META} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Full GC Count' \ --unit Count \ --value ${FGC} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Full GC Time' \ --unit Milliseconds \ --value ${FGCT} \ --dimensions InstanceId=${INSTANCE_ID} JSTAT=`jstat -gccapacity ${PID} | tail -n 1` S0C=`echo ${JSTAT} | awk '{ print $4 }'` S1C=`echo ${JSTAT} | awk '{ print $5 }'` EC=`echo ${JSTAT} | awk '{ print $6 }'` OC=`echo ${JSTAT} | awk '{ print $10 }'` aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Survivor area 0 usage capacity' \ --unit Kilobytes \ --value ${S0C} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Survivor area 1 usage capacity' \ --unit Kilobytes \ --value ${S1C} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Eden area usage capacity' \ --unit Kilobytes \ --value ${EC} \ --dimensions InstanceId=${INSTANCE_ID} aws cloudwatch put-metric-data \ --namespace "${NAMESPACE_NAME}" \ --metric-name 'Old area usage capacity' \ --unit Kilobytes \ --value ${OC} \ --dimensions InstanceId=${INSTANCE_ID}
同じクラス名のJavaプロセスを2つ以上同時に起動していて一意に特定できない場合の為に、Java起動引数に-Dprocess.name=hoge
等のダミー引数を付与した後、jps
コマンドに-v
オプションを追加する事でprocess.name=hoge
で対象プロセスを特定できるようにしています。
権限設定
作成した監視スクリプトを実行できるように実行権限を付与します。
# chmod +x /usr/local/cloudwatch/GC-Report/JVM-GC-Report.sh
cron設定
作成した監視スクリプトを定期実行する為に、crontabへ設定を追加します。
# CloudWatch JVM GC Report * * * * * /usr/local/cloudwatch/GC-Report/JVM-GC-Report.sh Bootstrap 'JVM GC' > /dev/null 2>&1
確認
実行して特にエラーが返ってこなければOKです。
# /usr/local/cloudwatch/GC-Report/JVM-GC-Report.sh Bootstrap 'JVM GC'
以下画像のようにCloudWatch メトリクス画面で指定したカスタム名前空間が追加されていると思います。
参考
JVMのメモリ使用量 - ふなWiki
put-metric-data — AWS CLI 1.16.140 Command Reference
CloudWatch のカスタムメトリクスで JVM の GC を監視する※スクリプトの大部分を参考にさせていただきました。
熊谷 悠司 (記事一覧)