自作プログラムを監視しよう (その1)
こちらの記事は、2019年4月16日にRetrieva TECH BLOGにて掲載された記事を再掲載したものとなります。
はじめまして
はじめまして、レトリバのエンジニアの高田です。 レトリバには2018年の4月に入社しました。主に C++ で Linux 上で動くサーバーサイド プログラム の 開発をおこなっています。 今回、会社の技術Blogをリサーチャー/エンジニア陣が順番で執筆し公開していくことになりました。 あまりBlogを書いてない私は、なにをどう書くのが良いのか、易しすぎず 難し過ぎず 広すぎず 専門的すぎず…… などを考え始めると 何も書けなくなりそうなので、 サーバーサイドプログラム開発の初心者だった頃の自分が読んだとしたら嬉しいと思える内容で行こうかと思います。
ここ数回の題材は「監視」でいこうと思います。
先日、O'REILLY「入門監視」という本を購入しました。アプリケーションのモニタリングについてよくまとめられいます。 私も我流で試行錯誤しながらこんな感じかなという知見は有ったのですが、体系だって書かれた本からは得られるものが多かった様に思います。お薦めです。
O'Reilly Japan - 入門 監視 (oreilly.co.jp)
プログラムが意図した通りに動いているかの確認、動作状況の見える化、を目的として監視を行います。 私が開発する場合のサーバサイドプログラムでは、以下に示す三視点での監視を行えるようにしています。
監視の三視点
1 外側からの監視
2 内側から
2.1 時系列的な監視
2.2 現時点での詳細な監視
外側と内側は、試作しているサーバサイドプログラムからみての外側と内側を意図しています。 外側とは、OSの機能を用いて、サーバプログラムのリソースの使用状況を見ます。 内側とは、プログラムでの実装によって出力するロギング、メトリクスを意図します。
今回は「1 外側からの監視」について記します。
1.外側からの監視
「外側からの監視」では、監視対象プロセスの1分毎の、使用メモリ、CPU使用率、スレッド数、ファイルディスクリプタ数 の情報を1行で、ロギングファイルに記していきます。
出力結果ファイルの抜粋を示します。
/var/log/proc_watch/hogeOne.log
date VSS(KB) RSS(KB) cpu(%) Thread(cnt) fd(cnt)
19/03/14 20:54:01 0
19/03/14 20:55:01 0
19/03/14 20:56:02 189472 8640 0.2 2 4
19/03/14 20:57:01 189472 8680 0.1 2 4
19/03/14 20:58:01 189472 8680 0.1 2 4
ファイル最上行には、項目名を記しています。
20時54分と55分の時点では、監視対象のサーバプログラムを起動させていません。
対象プログラムが意図せずに停止している場合も同じ表示になります。
各項目の意味
項目を左側から順に説明します。
時刻
年/月/日 時:分:秒 の並びで、出力時刻を示します。
使用メモリ
監視対象プログラムが 確保したメモリ量(VSS) と 実際に使用中のメモリ量(RSS) を示します。
この値が意図せず増加している場合には、メモリリークを疑えます。
CPU使用率
1コアCPUの場合で 最大値は100です。
マルチコアCPUの場合には 100を超える事もあります。
スレッド数
監視対象プログラムで稼働させているスレッド数です。
この値が意図せず増加している場合には、スレッドの終了処理の不適切な実装を疑えます。
ファイルディスクリプタ数
監視対象プログラムが開いているファイルディスクリプタ数です。
この値が意図せず増加している場合には、close 処理の抜けを疑えます。
/proc/(pid)/fd を 見ると、開いてるファイルへの一覧が確認できます。
ヒートランで よくお世話になってます
特に開発中、プログラムが動き始めたばかりの頃には、夜に仕掛けて帰り、翌朝に見て「あぁ!」という気づきを得る事はよくあります。
複数のプログラムを連動させる場合に、どのプログラムに問題がありそうかの切り分けにも有用です。
実現方法
Linux に標準的に入っている機能を組み合わせて実現できます。 リソースの使用状況を取得するShellScriptを、cron で1分置きに起動させ、出力ファイルはlogrotateで1日1回 世代交代させます。 実際に動かしてるスクリプトを見て頂く方が、理解が速いですよね。
ShellScript (1)
/path/to/sh/proc_watch.sh
#!/bin/bash -x
if [ $# -lt 2 ]; then
echo "Need 2 argument."
echo "$0 'ProcessName' 'outPathFName'"
exit 1
fi
# 引数1: 監視対象 名
ProcName=$1
# 引数2: 出力先ファイル名
OutPathFName=$2
ProcNum=`/usr/sbin/pidof -s ${ProcName}`
Date=`date '+%y/%m/%d %H:%M:%S'`
# ファイルの先頭行に 項目名を表示
if [ ! -e ${OutPathFName} ] || [ ! -s ${OutPathFName} ]; then
echo "date VSS(KB) RSS(KB) cpu(%) Thread(cnt) fd(cnt)" > ${OutPathFName}
fi
# 使用メモリ、CPU使用率、Thread数
PsO=`ps --no-headers -p ${ProcNum} -wo "vsz rsz %cpu nlwp"`
# ファイルディスクリプタ数
FdCnt=`ls -1 /proc/${ProcNum}/fd/ | wc -l`
# ファイルへ追記
echo "${Date} ${PsO} ${FdCnt}" >> ${OutPathFName}
このスクリプトで、監視対象のプログラムの使用リソースの情報を取得し、ファイルに追記します。
監視対象とするプログラム名や、数は、環境によって変わるので、後述する別のスクリプトで行うようにしています。
ShellScript (2)
/path/to/sh/watch_list.sh
#!/bin/bash
# Add the process you want to monitor.
/path/to/sh/proc_watch.sh hogeOne /var/log/proc_watch/hogeOne.log
#/path/to/sh/proc_watch.sh hogeTwo /var/log/proc_watch/hogeTwo.log
#/path/to/sh/proc_watch.sh hoge3 /var/log/proc_watch/hoge3.log
cron から呼び出すスクリプトです。
このスクリプトに、監視対象のプログラム名(プロセス名)と 出力先のパス・ファイル名 を列挙します。
cron への登録
/etc/cron.d/proc_watch
# for process resouce chekking.
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=""
* * * * * root /path/to/sh/watch_list.sh
毎分1回 監視用のシェルスクリプトを呼びだします。
logrotate 設定
/etc/logrotate.d/proc_watch
/var/log/proc_watch/*.log {
daily
rotate 6
missingok
notifempty
copytruncate
dateext
dateformat %Y-%m-%d
}
1日1回、監視の出力ファイルを世代交代させます。
手元の環境では 1日あたり だいたい64KB位です。
6世代残す設定になっています。
今どきのサーバ用のPCであれば問題ない容量かと思いますが、必要に応じて調整してください。
いかがでしょうか?
私としては、「外側からの監視」は、用意が容易なわりに、有用な情報が得られ、コストパフォーマンスが良いように思えています。
次回
次回は、「内側からの監視」について書きたいと思っています。 ここまで読んで頂きありがとうございました。 それではまた。