戻る

RRDtool チュートリアル (network traffic)

時代が MRTG から RRDtool へ移行していっているようなので便乗して(嘘) 例によって、snmp を使わずに(いい加減に使おうぜ…)、RRDtool でサーバのリソース統計情報を管理してみます。

MRTG と比較しても「高速」「グラフが自由」というのがカナリ大きいです。設定は「かなり面倒」という記述は多いけど、覚えてしまえば HotSaNIC などの FE を使わずとも自由にグラフ作れます。というか、MRTG 同様、なんでもグラフ化できます^-^
でもって、ログに記録している値に対して、再計算を行ってプロットとかもできるし、負の数も扱えるのがナイスです。
また、MRTG が(設定によって変更はできるが)「5分に1回、そのときの値を記録」だったのに対し、rrdtool は「5分に1回、その間(5min)の15秒毎の値を記録」なんてことも可能です。(言い換えれば、5分に1度の実行で、20セッション分の値を記録するってコト / 実際には、20回連続で実行するんだけど)

いや、、、rrdtool そのものの使い方を覚えたかったんで…

とても嬉しいマニュアル日本語訳: http://cc.ii2.cc/~fors/sysworks/data/rrdtool/

全体の流れ

ま、MRTG とほとんど同じですな。
ただ、MRTG が、1・2・3 をワンアクションで処理するのに対し、RRDTool は個別に実行してやる必要がありますが。

インストール

# apt-get install rrdtool
説明不要ですね。

以降、例として、というか例によって「ifconfig で取得できるトラフィック」をグラフ化する手順を紹介。

create … ログファイルの作成

ということで。
$ 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ステップ分記録されなかった場合、飛んだ部分はその前後の値で自動的に補足されます。例えば
の場合は という感じ。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 作ったっけ〜ってことになりがちなので、上記コマンドをひたすら叩くよりも、シェルスクリプトにしておいて保存しておいた方が無難。というか、鉄則(!?)

update … ログの更新

記録するのは、送受信バイト数の蓄積値(タイプが 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`;

graph … グラフの作成

一番楽しくてツラい作業です。何がつらいって、「(プロットするラインについての)デフォルトの色」というものがないので、色彩のセンスが問われます(笑)。ついでに、オプションが多い。

まずは 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 に突っ込んでおきましょう。