このチュートリアルは、Alex van den Bogaerdtによって書かれました。
# そして、私(藤繁 航:fjshg@photonway.net)によって翻訳されました :-)
このドキュメントへの追加も歓迎します。
[1] 訳注: 日本語訳で何か疑問や追加した方がよい点があれば、私(藤繁 航: fjshg@photonway.net)までご連絡下さい。適切に対応するつもりです。
もし、以下の私の説明中でよくわからないことに遭遇したら、Steve Rader 氏の書いたrpntutorial manpage 2を読んで下さい。RPN(逆ポーランド記法)がどのように機能するかの理解を助けてくれるでしょう。
[2] 訳注: このrpntutorial manpageはまだ翻訳されていません。
DEF:var_name_1=some.rrd:ds_name:CF CDEF:var_name_2=RPN_expression
まず、ファイル "some.rrd" 中のデータソース "ds_name" から評価関数 "CF" で評価された3(保存されている)データを収集する "var_name_1" を定義します。
ここで、iflnOctects SNMP カウンタがファイル mrtg.rrd のデータソース "in" に保存されていると仮定します。このとき、以下のDEFは、データソースの平均を取る変数を定義します:
DEF:inbytes=mrtg.rrd:in:AVERAGE
(データベースに保存されているバイト毎秒の代わりに)ビット毎秒で表示したいならば、変数 "inbyte" による計算を定義し(それゆえの "CDEF" です)、inbytes の DEF の代わりの変数 "inbits" を使わなくてはなりません。
CDEF:inbits=inbytes,8,*
これは、inbytes に 8 を掛けて inbits を得るための指示です。これがどのように機能するかは後で説明します。これにより、グラフ化やデータの出力機能において、inbytes を使うであろう場所で inbits を使うこともできます。
CDEF における変数の名前(ここではinbits)と DEF における変数の名前(ここではinbytes)を同じにしてはならないことに注意して下さい4。
[3] 訳注: ここでいう評価関数 CF は、実際には、AVERAGE / MAX / MIN / LASTになります。
[4] 訳注: つまり、DEF と CDEF であっても同じ変数名を使うことはできないということです。すなわち
DEF:inbytes=mrtg.rrd:in:AVERAGE CDEF:inbytes=inbytes,8,*という表現はできません。DEFとCDEFでも別々の名前を使う必要があります。
上で挙げた 8 の掛け算は、以下のような流れになります:
実際の数字を使って、例を示します。変数 inbytes の値が 10 であると仮定すると、スタックは以下のようになります:
1. | | 2. | 10 | 3. | 10 | 8 | 4. | 10 | 8 | × | 5. | 80 | 6. | |
スタックを処理する過程(ステップ5)で、一つの値がスタックから得られます(ステップ4からであり、スタックの右側から取り出されます)。これは掛け算であり、入力として二つの数字をスタックの右から取り出します。掛け算の結果は、スタックに戻されます(この場合では80です)。掛け算では計算の順序は問題になりません5が、引き算や割り算のような他の演算では順番によって結果が変わります6。一般的には、次のような順序になります:
y = A - B --> y=minus(A,B) --> CDEF:y=A,B,-
[5] 訳注: つまり、10×8 = 8×10 ということです。
[6] 訳注: つまり、10−8 != 8−10 ということです。
これは直観的ではありません(少なくとも、ほとんどの人はこのようには考えないでしょう)。関数 f(A, B) の記法と対比して考えると、変数(あるいは引数)の順番をそのままに "f" の位置を逆転させたものです。
すでにいくつかの RRD ファイルを持っていて、それらの RRD にあるカウンタの足し算をしたいとしましょう。これらは、例えばあなたがモニタしている一つ一つの WAN リンクのカウンタです。
以下のようなファイルを持っていたとします:
router1.rrd with link1in link2in router2.rrd with link1in link2in router3.rrd with link1in link2in
router2.rrd 中の link2in を除いたこれらのカウンタ全ての合計を取りたいとしましょう。以下のようにする必要があります:
(この例で、"router1.rrd:link1in" は、RRDファイル router1.rrd の中にあるデータソース link1in を意味します)
router1.rrd:link1in
router1.rrd:link2in
router2.rrd:link1in
router3.rrd:link1in
router3.rrd:link2in
-------------------- +
(足し算の結果)
数学的な関数を使うと、以下のように書けます:
add(router1.rrd:link1in , router1.rrd:link2in , router2.rrd:link1in , router3.rrd:link1in , router3.rrd:link2in)
RRDtoolと RPN では、まず入力を定義します:
DEF:a=router1.rrd:link1in:AVERAGE DEF:b=router1.rrd:link2in:AVERAGE DEF:c=router2.rrd:link1in:AVERAGE DEF:d=router3.rrd:link1in:AVERAGE DEF:e=router3.rrd:link2in:AVERAGE
ここで、数学的な関数は add(a,b,c,d,e) となります。
RPN においては、二個より多い数字を合計する演算子は存在しませんので、(二個ずつに分けて)何回か足し算をする必要があります。a に b を足し、その結果に c を足し、その結果に d を足し、その結果に e を足します。
スタックに a を積む: a スタックは、a の値を持ちます スタックに b を積み、足します: b,+ スタックは、a+b の結果を持ちます スタックに c を積み、足します: c,+ スタックは、a+b+c の結果を持ちます スタックに d を積み、足します: d,+ スタックは、a+b+c+d の結果を持ちます スタックに e を積み、足します: e,+ スタックは、a+b+c+d+e の結果を持ちます
ここで結局何が計算されたのかは、このように書くことができます:
( ( ( (a+b) + c) + d) + e)
RPN では、CDEF:result=a,b,+,c,+,d,+,e,+ となります。
これは、正確なものですが、人間にとってもっとわかりやすくする余地があります。a と b を足しそれから c を足して計算結果を得たのか、初めに b と c を足しそれから a を足し計算結果を得たのかは、問題にはなりません。そのため、RPN では、CDEF:result=a,b,c,d,e,+,+,+,+ のように書き換えてることができ、異なる評価の仕方がなされます。
変数 a の値をスタックに積む: a 変数 b の値をスタックに積む: a b 変数 c の値をスタックに積む: a b c 変数 d の値をスタックに積む: a b c d 変数 e の値をスタックに積む: a b c d e "+" 演算子をスタックに積み: a b c d e + 計算する: a b c P (ここでは、P=d+e です) "+"演算子をスタックに積み: a b c P + 計算する: a b Q (ここでは、Q=c+P です) "+"演算子をスタックに積み a b Q + 計算する: a R (ここでは、R=b+Q です) "+"演算子をスタックに積み a R + 計算する: S (ここでは、S=a+R です)
ここまで、RPN 記法 の a,b,c,d,e,+,+,+,+,+ は ((((d+e)+c)+b)+a) と評価され、a,b,+,c,+,d,+,e,+ と同じ結果が得られることを見てきました。Steve Rader 氏によれば、これは加算における交換法則と呼ばれています。そのこと自体はすぐに忘れてしまっても構いませんが、これがどういったことを示しているのかは覚えていて下さい。
次に、掛け算を含むような表記を見てみましょう。
普通の数学で、result = a+b×c を考えます。この場合、計算する順番をあなたが選ぶことはできず、まず掛け算をしてから、その結果に a を足す必要があります。b と c の位置を入れ替えても構いませんが、a と b の位置を取り換えることはできません。
この式を RPN に変換するときには、上記のことを考慮しなければなりません。この場合、"b×c の結果に a を足せ" ということで、容易に RPN で result=a,b,c,*,+ と書けます。他にも result=b,c,*,a,+ で同じ結果が得られます。
通常の記法においては、a×(b+c) のような数式を目にしますが、これも RPN に変換しましょう。この括弧は、まず先に b と c を足して、それからその結果に c を掛けるよう指示しています。RPN への変換は簡単なことで、result=a,b,c,+,* となります。前の段落での(RPNによる)数式の一つによく似ており、掛け算と足し算の記号の位置が違うだけだということに注意して下さい。
RPN において問題に遭遇したり、RRDtool が不満を漏らしたり(エラーがでたり)する際には、紙切れにスタックの状態を書いて何か起こっているかを見てみるとよいでしょう。マニュアルを手元に用意して、RRDtool の真似をして下さい7。何が起きているかを知るために、全て手で計算してみて下さい。私は、このことがほとんどの問題を解決すると確信しています。もし手計算していないのならば、あなたはなにがしかの問題8に遭遇するでしょう。
[7] 訳注: つまり、RRDtool になったつもりで自分でスタックに変数と演算子を積み、上の段落のように計算してみて下さい。
[8] 訳注: 原文だと、problems と複数形です。遭遇するであろう問題は一つではないかもしれません。
CDP を計算する際、(heartbeatとは)違うメカニズムによって CDP が妥当であるかどうかが決定されます。不明値である PDP が多すぎる場合には、CDP もそのまま不明値になります。この割合はパラメータ xff で決定されます。一つの不明値によるカウンタへのアップデートは、結果として二つの不明値である PDP を生じさせることに注意して下さい。もし、一つの CDP に対してただ一つの不明値である PDP しか許容しないならば、それだけで CDP が不明値になってしまいます!
1秒間に1ずつ増加するカウンタを仮定し、毎分これを取得するとしましょう。
カウンタの値 変化の割合の計算結果 10000 10060 1; (10060-10000)/60 == 1 10120 1; (10120-10060)/60 == 1 unknown unknown; 最後の値が不明 10240 unknown; その前の値が不明 10300 1; (10300-10240)/60 == 1
この CDP を最後から五個の値で計算すると、二つの不明値である PDP と三つの値のある PDP とが得られます。xff が広く使われる値である 0.5 にセットされていた場合、CDP は 1 という値12を持ちます。 もし、xff が 0.2 にセットされていたならば、結果として CDP は不明値になります13。
あなたは、ハートビートや一つの CDP に対する PDP の数(steps)と xff 係数14を、適切な数値に決定しなければなりません。ここまでの説明からおわかりの通り、これらの数値はあなたの RRA の挙動を定義します。
[9] 訳注: 原文では "The unknown value" でした。以後、この unknown value を「不明値」と訳します。
[10] 訳注: CDP は、Conslidated Data Point の略です。以前の RRDtool Tutorial で出てきた評価関数 (Consolidated Function; CF) が、PDP から CDP をどう計算するかを指示します。
[11] 訳注: RRDファイルを作成するときの初期値として不明値がデフォルトの値として格納されるということです。
[12] 訳注: 2(unknownであるPDPの数) / 5(PDPの数) = 0.4 < 0.5(xff)
[13] 訳注: 2(unknownであるPDPの数) / 5(PDPの数) 0.4 > 0.2(=xff)
[14] 訳注: xff については、ちょっとわかりにくい概念なので説明します。PDP から CDP を計算するとき、不明値である PDP の数を p とします。PDP の総数を steps とします。このとき、 p / steps > xff であるなら、CDP は問答無用で不明値となるということです。よって、六個の PDP から CDP を計算するとき、xff=0.5 であるなら、三つまで PDP が不明値であっても、CDP は妥当 (valid) となります。もし、xff=0.4 であるなら、三つの PDP によって CDP は不明値として扱われます。
誰かが一年以上に渡ってデータを集めていたとしましょう。デバイスに新しい部品がインストールされ、新しい RRD が作成されて、古いデータベースのカウンタと新しいデータベースのカウンタを足し合わせるようにスクリプトが変更されました。このとき、結果は期待に背くものになります。不思議なことに、大量の統計データが消えてしまうのです……。もちろんデータが消失したわけではなく、古いデータベースからの値 (既知の値で数字) と新しいデータベースからの値 (不明値) が足し算されたために、その結果もまた不明値になってしまうというわけです。
この例では、不明値のデータを 0 に変更するために CDEF を使うのは、理に適っています。デバイスのカウンタは不明値でしたが(まだ新しい部品がインストールされていなかった)、しかしあなたはデータレートがずっと 0 でなくてはならない理由を知っています(理由は同じでまだインストールされていなかったからです)。
この変更をするいくつかの例が後で出てきます。
RRDtool には、無限大を表現する (グラフ化するのでは なく ) 能力があります。グラフの描画領域の最大値 (正の無限大) か最小値 (負の無限大) でプロットを止め、実際にどこまで大きいかまたは小さいかまでには関与しません。
RRDtool において無限大は、具体的な縦軸の次元に関係なく AREA を描画するために主に用いられます。AREA を無限大の高さで描画し、そのグラフで視覚化できる中での最大の範囲を示すのです。これは無限大の近似としてはよい方法であり、ちょっとしたテクニックとして使えます。下にある例を見てみて下さい。
単純に router.rrd と router2.rrd からのカウンタを足し算すると、既知の値 (router.rrdからの値) と不明値 (router2.rrdからの値) を足し合わせることになり、これまでの統計の大部分に問題が生じます。この問題を解決するには、いくつかの方法があります:
どちらの方法にも利点と欠点とがあります。最初の方法は面倒であり、この手法を使いたいのであれば自分自身で何とかしなければなりません15。(不明値の代わりに) 0 で埋められたデータベースを作ることは不可能であり、あなた自身がデータベースに 0 を入れていかなければなりません。二番目の方法については、次に記述します。
ここでやりたいのは、"もし値が不明値であるなら、それを 0 に置き換えろ" ということです。これは、以下の擬似コードで書くことができます: if (値が不明値) then (0) else (値)。 rrdtool の graph の manページ (man rrggraph) を読めば、"UN" 関数16が 0 か 1を返す関数であることがわかるでしょう。また、"IF" 関数も 0 か 1 を入力として取ることにも気付くでしょう。
まず初めに "IF" 関数を見て下さい。これは、スタックから三つの値を取ります。最初の引数は判断条件です。もし (判断条件の) 評価結果が真であるなら、二番目の引数がスタックに戻されます。そうでないなら、三番目の引数がスタックに戻されます。一つの CDEF でこれらの二つの関数を組み合せたときに何が起きるのかを決定するため、"UN" 関数を必要とします。
"IF" 関数において、ありえる二つの流れを書き留めましょう。
if 真 return a if 偽 return b
x が真か偽であるかどうかの条件であるとすると、RPN においては result=x,a,b,IF となります。
ここで "x" の中身を決めなければなりません。ここでは "(値が不明値である)" でした。RPN では result=value,UN となります。
これらを結合すると、result=value,UN,a,b,IF となり、"a" と "b" を適切に埋めて完成です:
CDEF:result=value,UN,0,value,IF
この例の私の説明が難しいのであれば、Steve Rader 氏の RPN のガイドを読むと良いかもしれません。
この RPN 表記を確かめたいのならば、RRDtoolの挙動を真似してみましょう:
既知の値なら、この表現は以下のように評価されます: CDEF:result=value,UN,0,value,IF (value,UN) は真でなく、0 になります CDEF:result=0,0,value,IF "IF" は三番目の引数を戻します CDEF:result=value 既知の値が戻されます 不明値であるなら、このようになります: CDEF:result=value,UN,0,value,IF (value,UN) は真となり、1 になります CDEF:result=1,0,value,IF "IF" は 1 を評価し、二番目の引数を戻します CDEF:result=0 0 が戻されます
もちろん、0 の代わりに他の値を使いたいなら、その (0でない) 他の値を用いることができます。
最終的には、全ての不明値が RRD からなくなったときに、不明値を正しく表示するためのこのルールを削除したくなることでしょう17。
[15] 訳注: 具体的には、router.rrd を作った時刻を t とすると、この t と同じかそれより前の時刻から router2.rrd が始まるようにするということです。RRD を作成するときの rrdtool create コマンドの -s オプションで t より前の時刻を指定し、それからシェルスクリプトなりで 0 でアップデートしていくというやりかたです。
[16] 訳注: "UN" 関数の説明の日本語訳: スタックから値を一つ取り出します(Popする)。 このとき、この値が 不明値 であれば、スタックに 1 を、そうでないなら、 0 を戻します(pushする)。
[17] 訳注: つまり、不明値だらけの新しい RRD もラウンドロビンが一周すれば不明値がなくなるので、このルールが必要なくなるという意味です。
これは可能です。サンプルが取得された時刻と既知の時間とを比較すればいいのです。デバイスを1999年9月17日(金曜日)の00:35:58(中央ヨーロッパ時間・夏時間)から監視し始めたとします。この時間を1970年01月01日からの経過秒数に変換すると、937521357 になります。この時刻よりも後で記録された不明値を処理する際にはその不明値をそのままにし、この時刻よりも前に記録されている不明値を処理する際には 0 に変換したいでしょう。このようにして、他のルータのカウンタとの足し算のときに、効果的に不明値を無視することができます。
1999年9月17日(金曜日)00:35:58(中央ヨーロッパ時間・夏時間) を 937521357 に変換するには、例えば GNU date コマンド18を用います。
date -d "19990917 00:35:57" +%s
データベースをダンプして、どこから既知の値が始まっているかを知るという方法もあります。他にもいくつかやり方がありますので、どれかを使って下さい。
さて、サンプルが取得された時間によって不明値に対して異なる処理を行う魔法を作らなければなりません。これは三つのステップから構成されます:
手順1は:
if (真) return 元々の値
書き換えると以下のようになります:
if (真) return "a"
if (偽) return "b"
ステップ1においては、真か偽であるかの計算が必要です。カレント19であるサンプルのタイムスタンプを返す関数が存在します。その関数は意外にも "TIME" と呼ばれています。この時刻を定数 (ここでは937521357) と比較するために "GT" が必要です。"GT" の出力は真か偽かであり、これは "IF" への入力にぴったりです。必要なのは "if (time > 937521357) then (return a) else (return b)" だったというわけです。
この処理は、既に一つ上の章で説明されているので、手短に進めましょう:
if (x) then a else b x は "time>937521357" を表現しています a は元々の値を表現しています b は以前の例をそのまま表現しています time>937521357 --> TIME,937521357,GT if (x) then a else b --> x,a,b,IF x を代入 --> TIME,937521357,GT,a,b,IF a を代入 --> TIME,937521357,GT,value,b,IF b を代入 --> TIME,937521357,GT,value,value,UN,0,value,IF,IF
最終的には、CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF となります。
これは非常に複雑に見えますが、ここまで組み立てるのが難しすぎるということはないことがわかるはずです。
[18] 訳注: 現在の FreeBSD-4.9 では、date -j 199909170035.57 +%s とすることで同じことができます(注意: 特に root で作業している人は、絶対に -j オプションを忘れないように)
[19] 訳注: RRDtool は、RRD からデータを取りだすときに、タイムスタンプ付きで記録されたサンプルを順次追いかけます。丁度この計算をしているサンプルがカレントのサンプルになります。
ここでは、二つの選択肢があります。
それぞれを擬似コードで表わすと以下のようになります:
二番目の方法は、RRDtool のグラフ描画における厳格なオプションを使用することで実現できます。しかしながら、これは同じ結果にはなりません。例えば、オートスケーリング21が使われたグラフがそうです。また、この数値を描画の最大値に使うならば、これらの最大値は 100kb/s にセットされるでしょう。
ここで、再び "IF" と "GT" を使います。"if (x) then (y) else (z)" は、" CDEF:result=x,y,z,IF" と書き換えられます。そして x と y と z の中身を見ていきましょう。x は "100kb/sより大きい数値" であり、これは "number,100000,GT" と表せます22(k (キロ) とは 1000 のことであり、私たちは b/s で計測しています)。"z" はどちらの選択肢を表現した擬似コードにおいても "number" で、"y" は不明値を示す "UNKN" か 100kb/s を示す "100000" です。
この二つの CDEF 表現は以下のようになります:
CDEF:result=number,100000,GT,UNKN,number,IF
CDEF:result=number,100000,GT,100000,number,IF
[20] 訳注: いったい何故まずいのかというと,ひとつに詳細が見えにくくなるからです.一見は百聞にしかず.下の図を見てみてください.
データをそのままプロット / スケーリングを調整しない場合:
10.0 M **
7.5 M **
5.0 M **
2.5 M **
0.0 M ---******--*---***---*--------**--
0 2 4 6 8 10 12 14 16 18 20 22 (時刻)
100.0kを超えるデータをカット / スケーリングを調整する場合:
100.0 k ******
75.0 k ****** *
50.0 k ******* *** * *
25.0 k ********* **** ** **
0.0 k -***********************----*****-
0 2 4 6 8 10 12 14 16 18 20 22 (時刻)
データをいじらない場合では,8時以降でのピークの詳細がまるで分かりません.データをいじることで,8時以降の3つのピークの周辺とそのトラフィック量の詳細がわかるようになりました.
[21] 訳注: RRDtool のグラフ描画機能において、グラフの下限値と上限値を自動的に設定する機能のことです。つまり、グラフの上限値と下限値を自動的に設定する機能を止めて手動で設定するため、やや見栄えの違うグラフになります。
[22] 訳注: "number" は "記録された数値" です。以下、変数の意味を示す簡単な一般名詞と変数名とに同じ英単語 (や合成語) が用いられている場合、訳さずそのままにします。
まず、(カレントの時刻における) タイムスタンプを始まりと終わりの日付で (二回) 比較する必要があります。この比較は難しくありません:
TIME,begintime,GE
TIME,endtime,LE
これらの CDEF の部品は、偽の代わりに 0 かあるいは真の代わりに 1 を生成します。これらが両方 0 (または1) であるかの確認はいくつかの IF ステートメントを使うことで可能ですが、Wataru Satoh 氏の指摘の通り、論理和 "*" 関数や論理積 "+" 関数を用いることができます。
"*" 演算子は、二つの被演算子23のうちどちらか一つが 0 であるならば 0 (偽) になります。 "+" 演算子は、両方の被演算子23が 0 (偽) であったときにのみ 0 (偽) になります。注意: 0 ではない *あらゆる数字* が "真" と判断されます。これは、例えば "-1,1,+" が ("真 or 真" となり) 偽 になってしまうことを意味します。言い換えれば、"+" 演算子は正の数字 (か 0) しかないことが確かな場合にのみ使うべきだということです。
これらを CDEF にまとめましょう:
DEF:ds0=router1.rrd:AVERAGE
CDEF:ds0modified=TIME,begintime,GE,TIME,endtime,LE,*,ds0,UNKN,IF
これは、両方の比較が真であるときに ds0 の値を返します。他の書き方もあります:
DEF:ds0=router1.rrd:AVERAGE
CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF
これは、どちらかが真であるならば不明値を返します。
[23] 被演算子とは日常では使わない言葉ですが、演算の対象となる値のことです。たとえば、 "1+2" という演算では "+" が演算子で、1 と 2 が非演算子です。
このような状況では、正しく返事をしないサーバがあるということを警告し、得られた合計値は捨てるということが望まれるでしょう。
これは例えば以下のようになります:
DEF:users1=location1.rrd:onlineTS1:LAST
DEF:users2=location1.rrd:onlineTS2:LAST
DEF:users3=location2.rrd:onlineTS1:LAST
DEF:users4=location2.rrd:onlineTS2:LAST
CDEF:allusers=users1,users2,users3,users4,+,+,+
この allusers をプロットすると、user1 から user4 の中に一つある不明値が、グラフ上に隙間として現れてしまいます。隙間ではなく、明るい赤のラインで表示する25ように変更しましょう。
一切問題がなければ (不明値がないならば) 不明値を返し、不明値が存在するならば無限大を返す、追加の CDEF を定義します。
CDEF:wrongdata=allusers,UN,INF,UNKN,IF
"allusers,UN" は、真か偽のどちらかに評価されます。これは IF 関数における (x) の部分であり、allusers が不明値であるかどうかをチェックします。IF 関数の (y) の部分には "INF" (無限大を意味します) がセットされます。IF 関数の (z) の部分は "UNKN" を返します。
つまり、"もし allusers が不明値であるなら、INF を返し、そうでないなら UNKN を返せ" という論理です。
ここで、この "wrongdata" を明るい赤で表示するために AREA が使えます。wrongdata が不明値 (なぜなら allusers が既知の値であって不明値ではないから) であれば、赤い AREA は描画されません。値が INF (なぜなら allusers が不明値であるから) なら、グラフ上の特定の時刻のところ27だけが赤い AREA で補完されて表示されます。
AREA:allusers#0000FF:combined user count AREA:wrongdata#FF0000:unknown data
[24] 訳注: ログイン or ログオンしているユーザだと思われます
[25] 訳注: 目的は警告ですので、目立つ表示の例として、鮮やかな赤が使われています。
[26] 訳注: つまり allusers が不明値であるような時間帯のことです
一つ前の例において、スタックを用いれば値を足し合わせる必要がありません。それゆえに、四つの値の間に関係は存在せず、テストのために一つの値を得ることもなくなります27。ここで、ある特定の時刻に user3 が不明値になったとしましょう。user1 がプロットされ、user2 が user1 の上に積み重ねられます。user3 は不明値なので、何も起こりません。user4 は user2 の上に積み重ねられます。とにかく新しい CDEF を足し、それらを使って "普通の" グラフに上書きしていきます。
DEF:users1=location1.rrd:onlineTS1:LAST DEF:users2=location1.rrd:onlineTS2:LAST DEF:users3=location2.rrd:onlineTS1:LAST DEF:users4=location2.rrd:onlineTS2:LAST CDEF:allusers=users1,users2,users3,users4,+,+,+ CDEF:wrongdata=allusers,UN,INF,UNKN,IF AREA:users1#0000FF:users at ts1 STACK:users2#00FF00:users at ts2 STACK:users3#00FFFF:users at ts3 STACK:users4#FFFF00:users at ts4 AREA:wrongdata#FF0000:unknown data
user1 から user4 の中に不明値が存在したとすると、"wrongdata" の AREA は塗り潰されます。なぜならば、この AREA は X 軸から始まって無限大の高さを持っており、STACK の部分が上書きされるからです。
二つの CDEF行 を("allusers" を使わずに) 一つにまとめることもできます。しかし、二つの CDEF を用いるよい理由があるのです:
一つの CDEF にまとめるには、二番目の CDEF の "allusers" に一番目の CDEF の右辺を代入します。
CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF
このように二行を一行にしてしまうと、次の GRPINT でこれら28を使うことはできません。
COMMENT:"Total number of users seen" GPRINT:allusers:MAX:"Maximum: %6.0lf" GPRINT:allusers:MIN:"Minimum: %6.0lf" GPRINT:allusers:AVERAGE:"Average: %6.0lf" GPRINT:allusers:LAST:"Current: %6.0lf\n"
[27] 訳注: CDEF:allusers の行が、必ずしも必要ではなくなるということです。
[28] 訳注: allusers のことです。allusers を定義しなければ、GRPINT で allusers は使えません。必ずしも allusers として定義する必要がないだけです。
rrdtool graph demo.gif --title="Demo Graph" \
DEF:cel=demo.rrd:exhaust:AVERAGE \
CDEF:far=cel,32,-,0.55555,* \
LINE2:cel#00a000:"D. Celsius" \
LINE2:far#ff0000:"D. Fahrenheit\c"
この例は、"demo.rrd" データベースから "exhaust" と呼ばれるデータソースを得て、その値を "cel" という変数に入れます。この CDEF は、以下のように評価させるために使われます:
CDEF:far=cel,32,-,0.5555,*
1. 変数 "cel" をスタックに入れます
2. 32 をスタックに入れます
3. 演算子 "引き算" をスタックに入れ、これを処理します
この時点で、スタックには "cel" から 32 を引いた数字が入っています
4. 0.5555 をスタックに入れます
5. 演算子 "掛け算" をスタックに入れ、これを処理します
6. 結果の値は、 "(cel-32)*0.55555" になります
摂氏を華氏に変換する関数を作るのならば、0.55555 は厳密に正確な値ではありませんので、"5/9*(cel-32)" とすべきです。ただし、この目的においては十分に正しく29、計算が簡単になります。
[29] 訳注: 5/9 = 0.55555555… (循環小数) なので、小数点以下第五位までの 0.55555 では厳密には等しくありませんが、実用上は問題のない近似だということです。
rrdtool graph demo.gif --title="Demo Graph" \
DEF:idat1=interface1.rrd:ds0:AVERAGE \
DEF:idat2=interface2.rrd:ds0:AVERAGE \
DEF:odat1=interface1.rrd:ds1:AVERAGE \
DEF:odat2=interface2.rrd:ds1:AVERAGE \
CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \
CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \
AREA:agginput#00cc00:Input Aggregate \
LINE1:aggoutput#0000FF:Output Aggregate
この二つの CDEF は、幾つかの関数で構成されています。これらが何をしているのかを見るために、CDEF を分割して考えていきます。最初の CDEF から始めましょう:30
idat1,UN --> a 0 --> b idat1 --> c if (a) then (b) else (c)
「"idat1" と " UN" が等しい」ということが真であるなら、この結果は "0" になります。そうでなければ、"idat1" の元の値がスタックに戻されます。ここで、この答を "d" と呼ぶことにしましょう。スタックにある次の五個のアイテム31に対しても、この処理が繰り返されて同じように進み、答 "h" を返します。結果として、スタックは "d,h" となります。この式は簡単に書くと "d,h,+,8,*" となり、"d" と "h" を足してその結果に 8 を掛けるということが分かりやすくなりました。
(この "d,h,+,8,*" の) 最終的な結果は、"idat1" と "idat2" を足したものですが、単純な足し算ではなくその処理過程で不明値を効果的に除去しています。bytes/s を bits/s に変換するため、その結果にさらに 8 が掛けられています32。
[30] 訳注: この解説は、原文では全体で一つの段落で、一行の中で延々と遷移が説明されています。そのまま改行もなしに続けてしまうと混乱の元になりそうでしたので、上のように分割して訳しました。
[31] 訳注: 最初の CDEF こと CDEF:agginput のアイテム、すなわち右辺の 13 ある要素のうち、これまでに先に現れる "IF" 関数の範疇である idat1 から IF までの五つを見てきましたが、続いては次に現れる "IF" 関数について、つまり idat2 から IF までの五つが (同様に) 処理されます。
[32] 訳注: 二番目の CDEF である、odat1 と odat2 に対する CDEF:aggoutput についても全く同様です。
rrdtool graph example.png --title="INF demo" \
DEF:val1=some.rrd:ds0:AVERAGE \
DEF:val2=some.rrd:ds1:AVERAGE \
DEF:val3=some.rrd:ds2:AVERAGE \
DEF:val4=other.rrd:ds0:AVERAGE \
CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \
CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \
AREA:background#F0F0F0 \
AREA:val1#0000FF:Value1 \
STACK:val2#00C000:Value2 \
STACK:val3#FFFF00:Value3 \
STACK:val4#FFC000:Value4 \
AREA:wipeout#FF0000:Unknown
この例は、二通りの無限大の使い方を示しています。CDEFの "裏で" 何が起こっているかを見るために、ちょっとした工夫をしています。
"val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"
このRPNは、"val4" の値を入力として取り、"POP" を使ってスタックからすぐに取り出してしまいます。スタックはしたがって空ですが、他方でこのサンプルが得られた時刻を知ることができます。その時間は、"TIME" 関数によってスタックに置かれます。
"TIME,7200,%" は、7200 (これは秒が単位なので二時間を意味します) を法とした、時刻の剰余計算を行います。スタックに置かれる結果は、0 から 7199 までの範囲の数値になります。
剰余を知らない人々のために説明すると、これは整数(この整数が法です)で割り算をした余りのことです。16 割る 3 は、商が 5 で余りが 1 です。"16,3,%" は、したがって 1 を返します。
話を元に戻して、スタックに置かれた "TIME,7200,%" の結果を "a" と呼びましょう。RPN の最初の方は "a,3600,LE" となります。これは、"a" が "3600" 以下であるかどうかを確かめています。時間全体の半分においてのみ、これは真になります33。RPN の残りの部分に進みますが、時刻に応じて "INF" か "UNKN" を返すような単純な "IF" 関数が一つあるだけです。これが、変数 "background" に返されます。
二つめの CDEF については、このドキュメントの最初の方で説明しましたので、ここでは繰り返しません。
ここで、二つの異なる層を描くことができます。まず始めに、background 34として、不明値 (何も表示されません) もしくは無限大 (グラフの正の部分が全て塗り潰されます) が描画されます。次に、このbackground の上に、データを描いていきます。データは background を覆うように上書き (上塗り) されます。val1 から val4 までのうち、一つが不明値である場合には、最終的に積み重なった三本の棒が表示されます。四つの変数全てが妥当である場合にのみデータは正しいのですから、これは望まれない結果です。これが、二つ目の CDEF を使う理由です。二つ目のCDEFは、データの上に AREA で上書きして、この (正しくない) データを見えなくしてくれます。
データに負の値が含まれている場合には、グラフの残り半分、すなわち (正方向だけでなく) 負方向の領域も上書きする必要があります。これは比較的簡単で、"wipeout" の前に負の符号を付ければいいです:
CDEF:wipeout2=wipeout,-1,*
[33] 訳注: 7200 (つまり二時間) で割った余りが 3600 以下かどうか、ですので、二時間のうちの半分(最初の一時間)では真、残りの半分(後の一時間)では偽となり、これはすなわち二時間のうちの前半か後半かということを見ています。
[34] 訳注: この background は、いわゆる背景という意味ですが、変数 background のことを示しています。
Gonzalo Augusto Arana Tagle <garana@uolsinectis.com.ar>による
複雑なデータフィルタリングを考えてみましょう:
メディアン フィルタ: ショットノイズをフィルタリング
DEF:var=database.rrd:traffic:AVERAGE
CDEF:prev1=PREV(var)
CDEF:prev2=PREV(prev1)
CDEF:prev3=PREV(prev2)
CDEF:median=prev1,prev2,prev3,+,+,3,/
LINE3:median#000077:filtered
LINE1:prev2#007700:'raw data'
派生物:
DEF:var=database.rrd:traffic:AVERAGE
CDEF:prev1=PREV(var)
CDEF:time=TIME
CDEF:prevtime=PREV(time)
CDEF:derivate=var,prev1,-,time,prevtime,-,/
LINE3:derivate#000077:derivate
LINE1:var#007700:'raw data'
このドキュメントは、私自身35の疑問やメーリングリストにおける人々の質問を踏まえて作られています。このドキュメントに誤りや理解が困難な箇所があったら、私に知らせて下さい。ドキュメントに加えるべきことがあると思ったら、私にメールして下さい: <alex@ergens.op.het.net>
[35] 訳注: ストレートに翻訳していますので、原著者である Alex van den Bogaerdt 氏のことです。
忘れないで下さい: フィードバック1がなければ、何も変更されないのです!
RRDtoolのmanpage
また、翻訳した本ドキュメントのチェックをして下さった、私の友人である 松浦 匡 氏にもこの場を借りて御礼を申し上げます。本チュートリアルの日本語がこなれているのは、彼のおかげです。