RRDtool チュートリアル (network traffic)
全体の流れ
- rrdtool create で、データを記録する .rrd ファイルを作成 (ここから)
- rrdtool update と、(例によって)値を取得するスクリプトで .rrd を更新 (ここから)
- rrdtool graph で、.rrd に記録されている値をグラフ化(画像ファイル作成) (ここから)
- HTTP 経由で、Web ブラウザでチェック
ま、MRTG とほとんど同じですな。
ただ、MRTG が、1・2・3 をワンアクションで処理するのに対し、RRDTool は個別に実行してやる必要がありますが。
インストール
# apt-get install rrdtool
説明不要ですね。
以降、例として、というか例によって「ifconfig で取得できるトラフィック」をグラフ化する手順を紹介。
- 取得するデータは"受信バイト数","送信バイト数"の2つ(*1)で、MRTG と違って、値の(差分や超過分の)計算は RRDTool にまかせる(*2)
- データの取得間隔は 20秒(*3)
- ログの保存期間は1週間(*4)
- 現在の値だけを取得し、単位時間の平均や最大・最小値のプロットは行わない(*5)
ということで。
$ rrdtool create eth0.rrd \ 第一引数に create / 第二引数に出力ファイル名
> --step 20 \ (*3) データ取得間隔を秒数で
> DS:rx:DERIVE:40:0:U \ (*1,2) 受信バイト数の指定
> DS:tx:DERIVE:40:0:U \ (*1,2) 送信バイト数の指定
> RRA:LAST:0.5:1:30240 (*4,5) 記録するのは現在の(差分計算後の)値のみ。
保存期間は30240(=60x60x24x7/20)ステップ
"--step
secount" は、データを記録する間隔を秒単位で。
DS(データソース?)は、":(コロン)"区切りで、5つのパラメタを指定します。
第1パラメタの"rx","tx"は、記録するデータを区別する識別子(まぁ、変数名みたいなモンだ)なので、自分がわかりやすいように、適当に。
第2パラメタの"DERIVE" はデータソースタイプの1つで、MRTG でいう「options に gauge / absolute を指定しない時」と同じ動作をします。早い話が、前回取得時の数値との差分を取り、その間の時間(stepに指定した秒数)で割った値を「そのときの値」としてログに記録。同等の動きをするもので"COUNTER"がありますが、カウンタリセット時の挙動が、今回の「ifconfig で取得できるトラフィック」には向かないので使いません。"COUNTER"は、カウンターリセットのタイミングを、数値が32bit/64bitのときを基点として計算している(らしい/未確認)ため、ifconfig のようにカウンタリセットのタイミングが不定(リブート時…よね)の場合は、"DERIVE"を使用。
ちなみに他のデータソースタイプには、MRTG と同様に"GAUGE(取得した値を、そのまま「そのときの値」とする)"と"ABSOLUTE(取得した値を、(前回との差分をとらずに)「そのときの値」とする)"もあります。メモリやディスクの使用率とかは、GAUGE を使用。注意点は、全部大文字であること。
第3パラメタの"40"は、「何秒分のデータが記録されなかったら unknown とみなすか」の秒数。40 (2 step分)の場合、1ステップ分記録されなかった場合、飛んだ部分はその前後の値で自動的に補足されます。例えば
- 時間 t の値: 10
- 時間 t+10 の値: 20
- 時間 t+20 の値: 28
- 時間 t+40 の値: 48
の場合は
- 時間 t として記録される値: unknown(それ以前に値がない場合)
- 時間 t+10 として記録される値: 10
- 時間 t+20 として記録される値: 8
- 時間 t+30 として記録される値: 10
- 時間 t+40 として記録される値: 10
という感じ。40 秒を超えて記録されなかった場合は、その間は unknown になります。ちなみに、unknown と 0 は別です。
残りの"0"と"U"は、それぞれ順番に最小値と最大値。この値より少ない/多い値は無視。"U"は、unknown で、制限なし。
RRA は、コロン区切りで4つのパラメタを指定。
"LAST"は統合関数の1種で、「そのときの値」をそのまま記録。このときは、同時に3つ目のパラメタ"1"も 1 に設定。LAST 以外の統合関数には、AVERAGE, MIN, MAX があり、第3パラメタで指定した数字のサンプル数毎に、平均・最小値・最大値を記録することもできます。LAST の値と別個に、単位時間の平均や最大・最小もグラフに描画したい場合は、これも指定する必要有り。なお、MRTG の出力のように、グラフの表示範囲のみの平均・最小・最大の取得は、特に指定しなくても RRDTool にて計算は可能。
第4パラメタは、ログに記録する最大ステップ数で、1週間は 60sec * 60min * 24hour * 7days / 20step で算出。
第2パラメタの"0.5"は、正直わからん(^-^; サンプルみても、全部 0.5 になってるんで、とりあえず。
LAST 以外に、追加で AVERAGE 等も記録する場合は、こんな感じ
$ rrdtool create eth0.rrd \
> --step 20 \
> DS:rx:DERIVE:40:0:U \
> DS:tx:DERIVE:40:0:U \
> RRA:LAST:0.5:1:30240 \
> RRA:AVERAGE:0.5:30:1008 \
> RRA:MIN:0.5:30:1008 \
> RRA:MAX:0.5:30:1008
これで、30ステップ(10分)毎の、平均・最小・最大値を、1008サンプル(6(1h)*24(1d)*7(1w)=1008)記録可能。
後々になって、どんなオプションで .rrd 作ったっけ〜ってことになりがちなので、上記コマンドをひたすら叩くよりも、シェルスクリプトにしておいて保存しておいた方が無難。というか、鉄則(!?)
記録するのは、送受信バイト数の蓄積値(タイプが DERIVE なんで)の計2つ。update する際の基本オプションは、以下の通り
$ rrdtool update eth0.rra timestamp:val1:val2
timestamp は、1970/1/1 09:00:00 からの秒数を指定。シェル上からなら、次のコマンド(って、Perl 実行してるんだけど)で取得可能。この数値を操作すれば、ログ更新時間と、データの時間がズレている場合も、(MRTG と違って)記録可能。更新時間がコマンド実行時の時間でよいなら、"N"という文字列を指定すれば良い。ただし、最終更新時間よりも古いタイムスタンプを指定して、データを記録することはできません。
$ perl -e 'print time'
val1, val2, ... は、create のときに指定した DS の順番で、数値を放り込めば良い。
MRTGの時とくらべても、値を切り出すだけなのでシェルで…と思ったけど、awk は使いこなせないので、相変わらず Perl にて(^-^;
要は
$ rrdtool update 946652400:0:0 2000/1/1 00:00:00 は rx:0 / tx:0
$ rrdtool update 946652420:100:50 2000/1/1 00:00:20 は rx:5 / tx:2.5
$ rrdtool update 946652440:150:80 2000/1/1 00:00:40 は rx:2.5 / tx:1.5
$ rrdtool update 946652460:210:120 2000/1/1 00:01:00 は rx:3 / tx:2
$ rrdtool update 946652480:280:170 2000/1/1 00:01:20 は rx:3.5 / tx:2.5
$ rrdtool update 946652500:390:220 2000/1/1 00:01:40 は rx:5.5 / tx:2.5
$ rrdtool update 946652520:460:270 2000/1/1 00:02:00 は rx:3.5 / tx:2.5
:
:
って感じで更新していきます。(DERIVEなので、前回との差分を間隔(sec)で割った値になります)
更新間隔は 20 秒だけど、20 秒に 1 回 cron から起動をかけるとログが大変なので、cron からは 5 分に一度実行し、プロセス内で 20 秒ごとに rrdtool を実行するようにしています。つまり、1回の下記プログラム実行で、5min(300sec) / 20sec の15回、rrdtool を実行します。
#!/usr/bin/perl
#
# update-ifconfig.pl
#
$RRD = "/usr/bin/rrdtool"; # rrdtoolのフルパス
$BASEDIR = "/work/dir"; # 基本の作業ディレクトリ
$RRA = $BASEDIR . "/eth0.rrd"; # eth0.rrd へのパス
$step = 20; # 更新間隔(create で指定した step の秒数)
$cron = 300; # 実行間隔(cron の起動間隔)
$count = $cron / $step; # 実行回数
$time = &get_time(); # 基点の時間の取得
$cmd = "LANG=C /sbin/ifconfig eth0 | grep bytes"; # 基本コマンド
# count 数だけ、ループにて実行する。次の cron の実行時に終了する
for ($i=0; $i<$count; $i++) {
# ifconfig, 送受信バイト数取得失敗時は unknown
$rx = $tx = "U";
# ifconfig の実行
if (open(CMD, "$cmd |")) {
$_ = <CMD>;
# 送受信バイト数の取得
if (/^\s*RX bytes:(\d+).*TX bytes:(\d+)/) {
$rx = $1;
$tx = $2;
}
close(CMD);
}
# rrdtool update の実行
$cmdline = "$RRD update $RRA $time:$rx:$tx";
`$cmdline`;
# 次回更新時まで sleep
sleep($step);
$time += $step;
}
# time 関数が返す秒数を、20秒間隔毎に補正するサブルーチン
sub get_time {
my $time = time();
my $mod = $time % $step;
if($mod < $step/2){
return $time - $mod;
} else {
return $time + ($step - $mod);
}
}
get_time サブルーチンに関しては、update に指定する秒数(もしくは、N 指定時の実行時間)が、1ステップの間隔の基点からズレている場合(例えば、更新したのがXX分19秒とか)、内部で20秒に補正することはない。その際、記録される値の更新時間は、あくまでXX分19秒であり、XX分20秒の値は前後(00秒と40秒)の値から算出される。
何が言いたいかというと、time 関数(とか、U)で実行時の時間まかせにすると、例えばディスク使用量のような整数値のデータや、CPU使用率みたいな合計すると必ず100になるデータで、ビミョーーにズレが生じる場合があるってこと。まぁ、ifconfig みたいに、時間で割ったりするなら、関係ないけどね(^-^;
もちろん、こんなややこしいことをせずに「実行時の時間を使用。1実行1更新。例外処理なんか知らん」なら、次のようなスクリプトでもオーケー(動かしてないけど…)。
#!/usr/bin/perl
$cmd = `/sbin/ifconfig eth0 | grep bytes`;
$rra = "/work/dir/eth0.rrd";
$time = time();
$cmd =~ /^\s*RX bytes:(\d+).*TX bytes:(\d+)/;
`/usr/bin/rrdtool update $rra $time:$1:$2`;
一番楽しくてツラい作業です。何がつらいって、「(プロットするラインについての)デフォルトの色」というものがないので、色彩のセンスが問われます(笑)。ついでに、オプションが多い。
まずは MRTG 風な基本系。
$ rrdtool graph eth0.png \
> --imgformat PNG \
> DEF:rx=eth0.rrd:rx:LAST \
> AREA:rx#00CF00:"incoming" \
> DEF:tx=eth0.rrd:tx:LAST \
> LINE1:tx#0000FF:"outgoing"
rrdtool の第一引数に"graph", 第二引数に出力する画像のファイル名を指定します。
"--imgformat"は、今回は MRTG チックなファイルを生成するために、PNG を指定。指定無しの場合は、GIF になります。ImageMagick みたいに、指定ファイル名の拡張子で自動判断、なんて機能は無いので、GIF 以外にするばあいは、明示的に指定する必要有り。
3, 4行目は、受信(rx)についてにグラフ描画に関するオプション。
「DEF:変数名=ファイル名:データソース名:統合関数名」で、変数名に一時的な変数名を指定して、.rrd からデータを取得します。まぁ、基本的にデータソース名と同じものを指定してやるのが、わかりやすくていいんだけど。
「AREA:rx#00CF00:"incoming"」は、実際にグラフを描画するオプション。AREA は、描画する値の領域を(0からその値まで)塗りつぶす。他には、"LINE"があり、(txのところで書いているように)LINE1, LINE2, LINE3とすることで、それぞれ1ドット、2ドット、3ドットのラインを描画します。次の"rx"は、DEF で定義した変数名で、.rrd における(create時に指定した)データソース名とは別物です。次の"#00CF00"は、rx に関する AREA で描画するグラフの色。指定方法は HTML の色見本などを参考に。最後の"incoming"は凡例。
また、AREA, LINE1-3 以外に"STACK"があり、これは「一つ前にプロットした値」に値を積み重ねて描画します。例えば、CPU 使用率みたいな「user と system と idle の全項目を足したら100」や、メモリの「use と cache と buffer と free を足したらメモリ搭載量」みたいに、足して描画するのに意味があるものに使います。前提として一つ目の値を AREA, LINE1-3 で描画している必要があり、それに続けて STACK で描画します。また、STACK を指定した場合のグラフへの値の塗り方は、直前の値の AREA, LINE1-3 に依存。
出力結果
ん、なんだか寂しいね。(というか、遊びまくってるね)
なので、グラフのタイトルや、MRTG と同じように、現在値・最大値・平均値などを表示させてみる。
$ rrdtool graph eth0.png \
> --imgformat PNG \
> --title "eth0 network traffic" \
> --vertical-label "Bytes" \
> DEF:rx=eth0.rrd:rx:LAST \
> AREA:valrx#00CF00:"incoming" \
> GPRINT:rx:LAST:"cur\: %6.2lf /" \
> GPRINT:rx:AVERAGE:"ave\: %6.2lf /" \
> GPRINT:rx:MIN:"min\: %6.2lf /" \
> GPRINT:rx:MAX:"max\: %6.2lf\n" \
> DEF:tx=eth0.rrd:tx:LAST \
> LINE1:tx#0000FF:"outgoing" \
> GPRINT:tx:LAST:"cur\: %6.2lf /" \
> GPRINT:tx:AVERAGE:"ave\: %6.2lf /" \
> GPRINT:tx:MIN:"min\: %6.2lf /" \
> GPRINT:tx:MAX:"max\: %6.2lf\n"
"--title"と"--vertical-label"は、下の出力サンプル見れば説明はいらないでしょう(手抜き)
"GPRINT"で、下のサンプルのように、数値(など)を画像内に描画できます。C だの Perl だのの言語がわかれば、書き方や、ダブルクォート・コロンなどの記号はエスケープする、ということはわかるでしょう。ちなみに私は、Perl の print 関数ばかり使うからよくわからん。
書き方は"GPRINT:変数名:統合関数名:フォーマット"で、「全部で6桁、小数以下2桁で出力」するように書いているつもり(全部で6桁でも、小数点を含むので、整数部分は3桁)。
うむ、だいぶ良くなってきた。でもまだちょっと、ゴチャゴチャしてるので、画像を大きくして、トラフィックの上りと下りを正と負で分けてみます。
$ rrdtool graph eth0.png \
> --imgformat PNG \
> --title "eth0 network traffic" \
> --vertical-label "Bytes" \
> --width 480 \
> --height 180 \
> DEF:rx=eth0.rrd:rx:LAST \
> AREA:rx#00CF00:"incoming" \
> GPRINT:rx:LAST:"cur\: %6.2lf /" \
> GPRINT:rx:AVERAGE:"ave\: %6.2lf /" \
> GPRINT:rx:MIN:"min\: %6.2lf /" \
> GPRINT:rx:MAX:"max\: %6.2lf\n" \
> DEF:tx=eth0.rrd:tx:LAST \
> CDEF:tx_minus=tx,-1,* \
> AREA:tx_minus#0000FF:"outgoing" \
> GPRINT:tx:LAST:"cur\: %6.2lf /" \
> GPRINT:tx:AVERAGE:"ave\: %6.2lf /" \
> GPRINT:tx:MIN:"min\: %6.2lf /" \
> GPRINT:tx:MAX:"max\: %6.2lf\n"
"--width", "--height" は、まんまです。ただし、画像ファイルそのもののの解像度ではなく、グラフエリアのみの大きさなので、タイトルや凡例の量により、画像ファイルの解像度はこの数字よりも大きくなります。
で、下のサンプルの「送信を負の数としてプロット」は、tx(送信)の値に、-1 をかけています(tx_minus)。これは CDEF の部分で、"CDEF:新しい変数名:計算式"で計算します。また、計算式の記述方法は通常の表記ではなく、後置記法(逆ポーランド記法)で記述します。(例: 2*3 → 2,3,*)この辺はググってくれ。
最後に、凡例の部分には -1 をかける前の値(tx)を表示して終いです。
うん、かなりよくなった(かな)。ナイスなオプションの組み合わせができたら、シェルスクリプトにして残しておきましょう。
まとめ
うまく動いたら、例によって
#!/bin/sh
/work/dir/update-ifconfig.pl (rrd 更新のスクリプト)
/work/dir/graph-eth0.sh (グラフ作成のスクリプト)
みたいなスクリプトを作成し、
0-55/5 * * * * /work/dir/exerrd.sh > /dev/null 2>&1
って感じで cron に突っ込んでおきましょう。