2009年3月7日

解答を書いておく -3- 部分解の2

request2


meminfo.awk が存在する、と言う前提からはじめる。

SLAB=$(( awk -f meminfo.awk /proc/meminfo >> $LOGFILE ) 2>&1 )

で $SLAB に現在の /proc/meminfo 中のSLABの値が取得できているとしよう。他に次のような値を bash スクリプトレベルで取得、定義できているとする。

# We only have 876Mbytes of memory space for kernel.
KERNELMEMORY=$( expr 876*1024 )
PREVIOUSSTATE="GREEN"

# 358809kbyte is 40% of 876*1024Kbytes
YELLOWBORDERSIZE=358809

# or define this.
# YELLOWLIMIT=0.4

# 627916kbyte is 70% of 876*1024Kbytes
REDBORDERSIZE=627916

# or define this.
# REDLIMIT=0.7

KERNELMEMORY はコメントにあるとおり。32bit モードの Linux は 876Mbyteしかカーネル用空間が無いのでこれが利用できる最大値。

PREVIOUSSTATEは「前回計算した際のステート」。ステートは GREEN, YELLOW, RED のどれかしかないとし、全部文字列で表すことにする。

YELLOWBORDERSIZE は Slab がこの値を超えていたらステートを YELLOW として出力する値。
YELLOWLIMIT は KERNELMEMORY サイズにこの値を乗じて得られた結果を YELLOWBORDERSIZE として用いる、という値。
もし、YELLOWLIMIT と YELLOWBORDERSIZE の両方が定義されていたらどちらか小さい方を採択する。
片方がそんざいしてもう一方が存在しないなら、存在する方が有効になる。どちらも存在しなければ YELLOWBORDERSIZE=358809 が定義されているものとして処理する。

REDBORDERSIZE は Slab がこの値を超えていたらステートを RED として出力する値。
REDLIMIT は KERNELMEMORY サイズにこの値を乗じて得られた結果を REDBORDERSIZE として用いる、という値。
もし、REDLIMIT と REDBORDERSIZE の両方が定義されていたらどちらか小さい方を採択する。
片方がそんざいしてもう一方が存在しないなら、存在する方が有効になる。どちらも存在しなければ REDBORDERSIZE=627916 が定義されているものとして処理する。

もし、YELLOWLIMIT や YELLOWBORDERSIZE が何らかの理由で REDLIMIT や REDBORDERSIZE よりも大きい場合は、自動的に REDLIMIT, REDBORDERSIZE と同じ値にまで下がったものとみなす。

何らかの理由でREDLIMIT, REDBORDERSIZE が KERNELMEMORY 以上の値になった場合は、REDBORDERSIZE=$KERNELMEMORY と見なす。これが意味のある制約かどうかは微妙だが…。

YELLOWLIMIT, YELLOWBORDERSIZE が 0以下の場合はデフォルト値が採択される。

REDLIMIT, REDBORDERSIZE が 0以下の場合はデフォルト値が採択される。



これで判るとおり、実は境界問題はかなり厄介な問題をはらんでいる。与える境界値が適切な場合は特に問題ないのだが、境界値が異常値の場合、妥当に動かすのは著しく困難を伴う。その最大の理由は、プログラマが妥当な状態を決めなくてはいけないからだ。

この問題の本当の難しいところは、条件判断ではない。

妥当ではない計算条件を与えられたときに、それをいかにして跳ね除けるか、という点だ。



さて、プログラムだ。浮動小数点演算を bash で実行するのは難しいので、awk で実装する。いくつかの定数については、awk側に埋め込む。残りの引数は gawk の -v オプションを用いて引き渡す。

CalculateStatus.awk
#! /bin/gawk -f
BEGIN {
KERNELMEMORY = 876 * 1024;
DefaultYELLOWBORDERSIZE = 358809;
DefaultREDBORDERSIZE = 627916;
GreenName = "GREEN";
YellowName = "YELLOW";
RedName = "RED";


# First, calucate PreYELLOWBORDERSIZE without thinking about RED.
if ( YELLOWLIMIT > 0 ) {
PreYELLOWBORDERSIZE = KERNELMEMORY * YELLOWLIMIT;
} else {
PreYELLOWBORDERSIZE = 0;
}
if ( YELLOWBORDERSIZE > 0 ) {
if ( PreYELLOWBORDERSIZE > 0 ) {
if ( PreYELLOWBORDERSIZE > YELLOWBORDERSIZE ) {
PreYELLOWBORDERSIZE = YELLOWBORDERSIZE;
}
} else {
PreYELLOWBORDERSIZE = YELLOWBORDERSIZE;
}
} else {
PreYELLOWBORDERSIZE = DefaultYELLOWBORDERSIZE;
}

# First, calucate PreREDBORDERSIZE without thinking about YELLOW.
if ( REDLIMIT > 0 ) {
PreREDBORDERSIZE = KERNELMEMORY * REDLIMIT;
} else {
PreREDBORDERSIZE = 0;
}
if ( REDBORDERSIZE > 0 ) {
if ( PreREDBORDERSIZE > 0 ) {
if ( PreREDBORDERSIZE > REDBORDERSIZE ) {
PreREDBORDERSIZE = REDBORDERSIZE;
}
} else {
PreREDBORDERSIZE = rEDBORDERSIZE;
}
} else {
PreREDBORDERSIZE = DefaultREDBORDERSIZE;
}

# OK. Re-calculate YELLOW according to RED.
if ( PreREDBORDERSIZE < PreYELLOWBORDERSIZE ) {
PreYELLOWBORDERSIZE = PreREDBORDERSIZE;
}

YELLOWBORDERSIZE = PreYELLOWBORDERSIZE;
REDBORDERSIZE = PreREDBORDERSIZE;

if ( SLAB > REDBORDERSIZE ) {
NewStatus = RedName;
} else if ( Slab > YELLOWBORDERSIZE ) {
NewStatus = YellowName;
} else {
NewStatus = GreenName;
}

# Ok. check old status in mind.
if ( PREVIOUSSTATE == RedName ) {
NewStatus = RedName;
} else if (( PREVIOUSSTATE == YellowName )&&( NewStatus = GreenName )) {
NewStatus = YellowName;
}

print NewStatus;
exit 0;
}

結果は stdout で出て行くので、bash 変数に取り込めばよい。

2009年3月1日

解答を書いておく -2- 部分解

まずは request3 から


request3 は比較的簡単だ。sed でも awk でもできる。

sed ならばこう:
sed 's/^/# /g' /proc/slabinfo


awk ならばこう:
awk '{ print "# " $0; }' /proc/slabinfo


awk の場合は正規表現を必要としない分早いかもしれない。でもそもそも sed の方がプログラムとして小さいのでこちらの方が早いかもしれない。どちらがいいのかはよく判らない。

つぎに request1


/proc/meminfo の各行から数字の部分を切り出すだけなら
awk '{ print $2; }' /proc/meminfo

だけで済む。しかしここには悪魔が潜んでいる。

難しいポイントは3つ。

  1. 時刻を出力する

  2. /proc/meminfo の中身の順序がカーネル依存である

  3. /proc/meminfo の Slab フィールドの値は、request2 でも使う



時刻を出力するには、最初に date 出力を getline で取得すればよい。
BEGIN {
"date +"%Y/%m/%d %H:%M:%S" | getline DATEVAL;
DATETAG="date";
}


/proc/meminfo の中身自体は適当なバージョンにあわせるとしよう。':' よりも左側の文字列を取ってくるには:
sed 's/^\(.*\):.*$/\1/g' /proc/meminfo

とやればよい。結果として
MemTotal
MemFree
Buffers
Cached
SwapCached
:
のような出力が得られる。これをベースに、<各エントリ名>TAG という変数と、<各エントリ名>VALという変数を作ろう。こんな感じだ:
for i in $(sed 's/^\(.*\):.*$/\1/g' /proc/meminfo ); do
echo "/^${i}:/ { ${i}VAL = \$2; ${i}TAG=\"${i}\"; }"
done

結果はこんな感じになる。
/^MemTotal:/ { MemTotalVAL = $2; MemTotalTAG="MemTotal"; }
/^MemFree:/ { MemFreeVAL = $2; MemFreeTAG="MemFree"; }
/^Buffers:/ { BuffersVAL = $2; BuffersTAG="Buffers"; }
/^Cached:/ { CachedVAL = $2; CachedTAG="Cached"; }
/^SwapCached:/ { SwapCachedVAL = $2; SwapCachedTAG="SwapCached"; }
:

各行が awk スクリプトにおける「対応する値をとってくるためのスクリプト」になっている。
同様にして、出力するコードを吐かせる。
for i in $(sed 's/^\(.*\):.*$/\1/g' meminfo ); do
echo " printf(\"\\\"\" ${i}VAL \"\\\"\\t\" );"
done
結果はこんな感じ。
 printf("\"" MemTotalVAL "\"\t" );
printf("\"" MemFreeVAL "\"\t" );
printf("\"" BuffersVAL "\"\t" );
printf("\"" CachedVAL "\"\t" );
printf("\"" SwapCachedVAL "\"\t" );
:
あと、「VAL」の部分を「TAG」に変えたものも用意する。

TAGをいつ出力するのか、その制御は bash 側からすることにしよう。PRINTTAGという変数を用意して、これが 0 だったら TAG を出さない。それ以外だったら出す。

printf の最後の要素だけは \t を \n に直す。また、date を出力するコードを加える。

以上を全部行うとこうなる:
BEGIN {
"date +'%Y/%m/%d %H:%M:%S'" | getline DATEVAL;
DATETAG="date";
}

/^MemTotal:/ { MemTotalVAL = $2; MemTotalTAG="MemTotal"; }
/^MemFree:/ { MemFreeVAL = $2; MemFreeTAG="MemFree"; }
/^Buffers:/ { BuffersVAL = $2; BuffersTAG="Buffers"; }
/^Cached:/ { CachedVAL = $2; CachedTAG="Cached"; }
/^SwapCached:/ { SwapCachedVAL = $2; SwapCachedTAG="SwapCached"; }
/^Active:/ { ActiveVAL = $2; ActiveTAG="Active"; }
/^Inactive:/ { InactiveVAL = $2; InactiveTAG="Inactive"; }
/^HighTotal:/ { HighTotalVAL = $2; HighTotalTAG="HighTotal"; }
/^HighFree:/ { HighFreeVAL = $2; HighFreeTAG="HighFree"; }
/^LowTotal:/ { LowTotalVAL = $2; LowTotalTAG="LowTotal"; }
/^LowFree:/ { LowFreeVAL = $2; LowFreeTAG="LowFree"; }
/^SwapTotal:/ { SwapTotalVAL = $2; SwapTotalTAG="SwapTotal"; }
/^SwapFree:/ { SwapFreeVAL = $2; SwapFreeTAG="SwapFree"; }
/^Dirty:/ { DirtyVAL = $2; DirtyTAG="Dirty"; }
/^Writeback:/ { WritebackVAL = $2; WritebackTAG="Writeback"; }
/^Mapped:/ { MappedVAL = $2; MappedTAG="Mapped"; }
/^Slab:/ { SlabVAL = $2; SlabTAG="Slab"; }
/^CommitLimit:/ { CommitLimitVAL = $2; CommitLimitTAG="CommitLimit"; }
/^Committed_AS:/ { Committed_ASVAL = $2; Committed_ASTAG="Committed_AS"; }
/^PageTables:/ { PageTablesVAL = $2; PageTablesTAG="PageTables"; }
/^VmallocTotal:/ { VmallocTotalVAL = $2; VmallocTotalTAG="VmallocTotal"; }
/^VmallocUsed:/ { VmallocUsedVAL = $2; VmallocUsedTAG="VmallocUsed"; }
/^VmallocChunk:/ { VmallocChunkVAL = $2; VmallocChunkTAG="VmallocChunk"; }
/^HugePages_Total:/ { HugePages_TotalVAL = $2; HugePages_TotalTAG="HugePages_Total"; }
/^HugePages_Free:/ { HugePages_FreeVAL = $2; HugePages_FreeTAG="HugePages_Free"; }
/^Hugepagesize:/ { HugepagesizeVAL = $2; HugepagesizeTAG="Hugepagesize"; }

END {
if ( PRINTTAG != 0 ) {
printf("\"" DATETAG "\"\t" );
printf("\"" MemTotalTAG "\"\t" );
printf("\"" MemFreeTAG "\"\t" );
printf("\"" BuffersTAG "\"\t" );
printf("\"" CachedTAG "\"\t" );
printf("\"" SwapCachedTAG "\"\t" );
printf("\"" ActiveTAG "\"\t" );
printf("\"" InactiveTAG "\"\t" );
printf("\"" HighTotalTAG "\"\t" );
printf("\"" HighFreeTAG "\"\t" );
printf("\"" LowTotalTAG "\"\t" );
printf("\"" LowFreeTAG "\"\t" );
printf("\"" SwapTotalTAG "\"\t" );
printf("\"" SwapFreeTAG "\"\t" );
printf("\"" DirtyTAG "\"\t" );
printf("\"" WritebackTAG "\"\t" );
printf("\"" MappedTAG "\"\t" );
printf("\"" SlabTAG "\"\t" );
printf("\"" CommitLimitTAG "\"\t" );
printf("\"" Committed_ASTAG "\"\t" );
printf("\"" PageTablesTAG "\"\t" );
printf("\"" VmallocTotalTAG "\"\t" );
printf("\"" VmallocUsedTAG "\"\t" );
printf("\"" VmallocChunkTAG "\"\t" );
printf("\"" HugePages_TotalTAG "\"\t" );
printf("\"" HugePages_FreeTAG "\"\t" );
printf("\"" HugepagesizeTAG "\"\n" );
}

printf("\"" DATEVAL "\"\t" );
printf("\"" MemTotalVAL "\"\t" );
printf("\"" MemFreeVAL "\"\t" );
printf("\"" BuffersVAL "\"\t" );
printf("\"" CachedVAL "\"\t" );
printf("\"" SwapCachedVAL "\"\t" );
printf("\"" ActiveVAL "\"\t" );
printf("\"" InactiveVAL "\"\t" );
printf("\"" HighTotalVAL "\"\t" );
printf("\"" HighFreeVAL "\"\t" );
printf("\"" LowTotalVAL "\"\t" );
printf("\"" LowFreeVAL "\"\t" );
printf("\"" SwapTotalVAL "\"\t" );
printf("\"" SwapFreeVAL "\"\t" );
printf("\"" DirtyVAL "\"\t" );
printf("\"" WritebackVAL "\"\t" );
printf("\"" MappedVAL "\"\t" );
printf("\"" SlabVAL "\"\t" );
printf("\"" CommitLimitVAL "\"\t" );
printf("\"" Committed_ASVAL "\"\t" );
printf("\"" PageTablesVAL "\"\t" );
printf("\"" VmallocTotalVAL "\"\t" );
printf("\"" VmallocUsedVAL "\"\t" );
printf("\"" VmallocChunkVAL "\"\t" );
printf("\"" HugePages_TotalVAL "\"\t" );
printf("\"" HugePages_FreeVAL "\"\t" );
printf("\"" HugepagesizeVAL "\"\n" );
}

このスクリプト全体を、meminfo1.awk として保存する。で、/proc/meminfo を食わせてみるとこんな感じになる。
$ gawk -v PRINTTAG=1 -f meminfo1.awk /proc/meminfo 
"date" "MemTotal" "MemFree" "Buffers" "Cached" "SwapCached" "Active" "Inactive" "HighTotal" "HighFree" "LowTotal" "LowFree" "SwapTotal" "SwapFree" "Dirty" "Writeback" "Mapped" "Slab" "CommitLimit" "Committed_AS" "PageTables" "VmallocTotal" "VmallocUsed" "VmallocChunk" "HugePages_Total" "HugePages_Free" "Hugepagesize"
"2009/03/01 23:00:00" "1001008" "200708" "43400" "395740" "0" "557556" "178624" "97216" "140" "903792" "200568" "2096472" "2096472" "224" "0" "356492" "47820" "2596976" "689048" "6192" "114680" "4560" "107264" "0" "0" "2048"
$ gawk -v PRINTTAG=0 -f meminfo1.awk /proc/meminfo
"2009/03/01 23:00:37" "1001008" "200708" "43400" "395740" "0" "557556" "178624" "97216" "140" "903792" "200568" "2096472" "2096472" "224" "0" "356492" "47820" "2596976" "689048" "6192" "114680" "4560" "107264" "0" "0" "2048"
$ gawk -f meminfo1.awk /proc/meminfo
"2009/03/01 23:00:37" "1001008" "200708" "43400" "395740" "0" "557556" "178624" "97216" "140" "903792" "200568" "2096472" "2096472" "224" "0" "356492" "47820" "2596976" "689048" "6192" "114680" "4560" "107264" "0" "0" "2048"

-v を使って PRINTTAG 変数をスクリプト外部から制御した。最後の -v を指定しない例は、ようするに「デフォルトの挙動」と言う奴になる。

やれこれで一安心…ではない。Slab の値だけ、別途別パスで外に出してやる必要がある。幸い、stderr という文明の利器があるのでこれを使う。
    print SlabVAL > "/dev/stderr";

これを END {} セクションの最後に加えるのだ。そうすると Slabの数字の部分だけが stderr で取り出せる。

request1に対する最終 meminfo.awk スクリプトはこうなる:
BEGIN {
"date +'%Y/%m/%d %H:%M:%S'" | getline DATEVAL;
DATETAG="date";
}

/^MemTotal:/ { MemTotalVAL = $2; MemTotalTAG="MemTotal"; }
/^MemFree:/ { MemFreeVAL = $2; MemFreeTAG="MemFree"; }
/^Buffers:/ { BuffersVAL = $2; BuffersTAG="Buffers"; }
/^Cached:/ { CachedVAL = $2; CachedTAG="Cached"; }
/^SwapCached:/ { SwapCachedVAL = $2; SwapCachedTAG="SwapCached"; }
/^Active:/ { ActiveVAL = $2; ActiveTAG="Active"; }
/^Inactive:/ { InactiveVAL = $2; InactiveTAG="Inactive"; }
/^HighTotal:/ { HighTotalVAL = $2; HighTotalTAG="HighTotal"; }
/^HighFree:/ { HighFreeVAL = $2; HighFreeTAG="HighFree"; }
/^LowTotal:/ { LowTotalVAL = $2; LowTotalTAG="LowTotal"; }
/^LowFree:/ { LowFreeVAL = $2; LowFreeTAG="LowFree"; }
/^SwapTotal:/ { SwapTotalVAL = $2; SwapTotalTAG="SwapTotal"; }
/^SwapFree:/ { SwapFreeVAL = $2; SwapFreeTAG="SwapFree"; }
/^Dirty:/ { DirtyVAL = $2; DirtyTAG="Dirty"; }
/^Writeback:/ { WritebackVAL = $2; WritebackTAG="Writeback"; }
/^Mapped:/ { MappedVAL = $2; MappedTAG="Mapped"; }
/^Slab:/ { SlabVAL = $2; SlabTAG="Slab"; }
/^CommitLimit:/ { CommitLimitVAL = $2; CommitLimitTAG="CommitLimit"; }
/^Committed_AS:/ { Committed_ASVAL = $2; Committed_ASTAG="Committed_AS"; }
/^PageTables:/ { PageTablesVAL = $2; PageTablesTAG="PageTables"; }
/^VmallocTotal:/ { VmallocTotalVAL = $2; VmallocTotalTAG="VmallocTotal"; }
/^VmallocUsed:/ { VmallocUsedVAL = $2; VmallocUsedTAG="VmallocUsed"; }
/^VmallocChunk:/ { VmallocChunkVAL = $2; VmallocChunkTAG="VmallocChunk"; }
/^HugePages_Total:/ { HugePages_TotalVAL = $2; HugePages_TotalTAG="HugePages_Total"; }
/^HugePages_Free:/ { HugePages_FreeVAL = $2; HugePages_FreeTAG="HugePages_Free"; }
/^Hugepagesize:/ { HugepagesizeVAL = $2; HugepagesizeTAG="Hugepagesize"; }

END {
if ( PRINTTAG != 0 ) {
printf("\"" DATETAG "\"\t" );
printf("\"" MemTotalTAG "\"\t" );
printf("\"" MemFreeTAG "\"\t" );
printf("\"" BuffersTAG "\"\t" );
printf("\"" CachedTAG "\"\t" );
printf("\"" SwapCachedTAG "\"\t" );
printf("\"" ActiveTAG "\"\t" );
printf("\"" InactiveTAG "\"\t" );
printf("\"" HighTotalTAG "\"\t" );
printf("\"" HighFreeTAG "\"\t" );
printf("\"" LowTotalTAG "\"\t" );
printf("\"" LowFreeTAG "\"\t" );
printf("\"" SwapTotalTAG "\"\t" );
printf("\"" SwapFreeTAG "\"\t" );
printf("\"" DirtyTAG "\"\t" );
printf("\"" WritebackTAG "\"\t" );
printf("\"" MappedTAG "\"\t" );
printf("\"" SlabTAG "\"\t" );
printf("\"" CommitLimitTAG "\"\t" );
printf("\"" Committed_ASTAG "\"\t" );
printf("\"" PageTablesTAG "\"\t" );
printf("\"" VmallocTotalTAG "\"\t" );
printf("\"" VmallocUsedTAG "\"\t" );
printf("\"" VmallocChunkTAG "\"\t" );
printf("\"" HugePages_TotalTAG "\"\t" );
printf("\"" HugePages_FreeTAG "\"\t" );
printf("\"" HugepagesizeTAG "\"\n" );
}

printf("\"" DATEVAL "\"\t" );
printf("\"" MemTotalVAL "\"\t" );
printf("\"" MemFreeVAL "\"\t" );
printf("\"" BuffersVAL "\"\t" );
printf("\"" CachedVAL "\"\t" );
printf("\"" SwapCachedVAL "\"\t" );
printf("\"" ActiveVAL "\"\t" );
printf("\"" InactiveVAL "\"\t" );
printf("\"" HighTotalVAL "\"\t" );
printf("\"" HighFreeVAL "\"\t" );
printf("\"" LowTotalVAL "\"\t" );
printf("\"" LowFreeVAL "\"\t" );
printf("\"" SwapTotalVAL "\"\t" );
printf("\"" SwapFreeVAL "\"\t" );
printf("\"" DirtyVAL "\"\t" );
printf("\"" WritebackVAL "\"\t" );
printf("\"" MappedVAL "\"\t" );
printf("\"" SlabVAL "\"\t" );
printf("\"" CommitLimitVAL "\"\t" );
printf("\"" Committed_ASVAL "\"\t" );
printf("\"" PageTablesVAL "\"\t" );
printf("\"" VmallocTotalVAL "\"\t" );
printf("\"" VmallocUsedVAL "\"\t" );
printf("\"" VmallocChunkVAL "\"\t" );
printf("\"" HugePages_TotalVAL "\"\t" );
printf("\"" HugePages_FreeVAL "\"\t" );
printf("\"" HugepagesizeVAL "\"\n" );

print SlabVAL > "/dev/stderr";
}

STDOUT はログファイルに出力させる。その後 STDERR を stdout にリダイレクトして、変数に食わせてやる。実行する側のシェルはこんな感じ:
SLAB=$(( awk -f meminfo.awk /proc/meminfo >> $LOGFILE ) 2>&1 )

これで SLAB 変数には Slab: の数字のフィールドが入り、かつ全部の出力が $LOGFILE に出力される。

解答を書いておく -1- 課題

仕事で、ある人にスクリプト作成を依頼している。
内容的には単純なのだが、ちょっとややこしい部分があって、論理的に考えないと答が出ないようになっている。半分、演習問題のようなテーマだ。

順調に遅延しているのはいいのだが、私自身他に3件も受け持っているので、
「いざ、もう間に合いません」
と言うときになってからスクリプトを書き始めると、ちょっと間に合わなかったりする。なので、ここに模範解答の形で答を書いておこうか、と思う。いざとなったらここを参照すればよいわけだ。

他の人の演習問題にもなったらな、と思うので、問題も全部公開しよう。秘密といえるような部分は一切無い。



課題


request1


/proc/meminfo の内容を定期的に参照して欲しい。/proc/meminfo のデータは1項目一行形式になっているので、これを1行の TSV に直して欲しい。最初の一回だけ、どの項目がどこにあるのかを示すラベルもつけて欲しい。また、最初の要素に、/proc/meminfo の情報をいつ取得したのか、date の情報を "YYYY/MM/DD hh:mm:ss" の形式で保存して欲しい。

request2


/proc/meminfo には Slab という項目がある。ここの値がある閾値を超えたら、定期的に参照するその参照頻度を上げて欲しい。ようするにデータを取る間隔を短くして欲しいのだ。問題は、いったん間隔を短くしたら、二度と長くしないで欲しい、と言うことと、「閾値」は2つ、Yellow と Red があるので、それぞれに応じて間隔を短くして欲しい。また、この閾値はあとで簡単に変更できるように、スクリプトの上のほうの行で名前で宣言して欲しい。もちろん、その際の「間隔」もそれぞれ名前で宣言しておいて欲しい。

request3


Yellow の閾値を超えたら /proc/slabinfo のデータも取ってきて欲しい。こちらは、/proc/slabinfo のファイルの情報の各行の前に "# " (シャープがきて、その次に空白が1つ) をつけた形で、/proc/meminfo のデータを保存しているログファイルと同じ所に保存して欲しい。

課題の詳細


request1


/proc/meminfo を読んでくると次のようなフォーマットで値が出力される。
MemTotal:      1001008 kB
MemFree: 200708 kB
Buffers: 43400 kB
Cached: 395740 kB
SwapCached: 0 kB
Active: 557556 kB
Inactive: 178624 kB
HighTotal: 97216 kB
HighFree: 140 kB
LowTotal: 903792 kB
LowFree: 200568 kB
SwapTotal: 2096472 kB
SwapFree: 2096472 kB
Dirty: 224 kB
Writeback: 0 kB
Mapped: 356492 kB
Slab: 47820 kB
CommitLimit: 2596976 kB
Committed_AS: 689048 kB
PageTables: 6192 kB
VmallocTotal: 114680 kB
VmallocUsed: 4560 kB
VmallocChunk: 107264 kB
HugePages_Total: 0
HugePages_Free: 0
Hugepagesize: 2048 kB

request 1 はようするにこれを

"1001008"<tab>"200708"<tab>"43400"....

のように1行に直して欲しい、という事だ。ただし、<tab> の部分は実際にはタブ文字コード (awk とかだと "\t")で分けて欲しい。また、数字は " で囲ってもらえると助かる。

また、最初の1回だけラベルの部分も欲しい。これはつまり
"MemTotal"<tab>"MemFree"<tab>"Buffers"....

のような行を出して欲しい、と言うこと。

さらに、一番最初にデータを取得した情報が欲しい、とある。これはようするに
date +"%Y/%m/%d %H:%M:%S"

の出力を取り込んで、最初に表示して欲しい、という事だ。当然ラベル行も実際には:
"date"<tab>"MemTotal"<tab>"MemFree"<tab>"Buffers"....

でなくては困る。

request2


/proc/meminfo のSlab:の項目が 358809 未満を Green,
358809 以上 627916 未満を Yellow,
627916 以上を Red
という状態とする。これは32bit Linux Kernel のメモリ領域が 876Mbyte しかなく、それぞれ 40%、70% の所に閾値を用意した、という事だ。実際には Slab が Yellow になることすらめったに無いはずで、Red に突入したら、間違いなく oom-killer が近々発動するだろう。その傾向を見たいのだ。

状態が Green の間は 300秒(5分)間隔で request1 を実施して欲しい。
Yellow になったら 60秒(1分)間隔で request1 と request3 を実施して欲しい。
Red になったら 30秒間隔で request1 と request3 を実施して欲しい。

いったん Yellow になったら、Slab の値が改善しても Green には戻らないで欲しい。同様に Red になったら、Yellow, Green には戻ってはいけない。なので、「今回の Slab の値だけで、次にデータ取得するまでの間隔を決める」のでは困る。

request3


cat /proc/slabinfo を実施するとこんな感じの出力が得られる。
slabinfo - version: 1.1
kmem_cache 60 78 100 2 2 1
blkdev_requests 5120 5120 96 128 128 1
mnt_cache 20 40 96 1 1 1
inode_cache 7005 14792 480 1598 1849 1
dentry_cache 5469 5880 128 183 196 1
filp 726 760 96 19 19 1
buffer_head 67131 71240 96 1776 1781 1
vm_area_struct 1204 1652 64 23 28 1

これの頭に "# " をつけて
# slabinfo - version: 1.1
# kmem_cache 60 78 100 2 2 1
# blkdev_requests 5120 5120 96 128 128 1
# mnt_cache 20 40 96 1 1 1
# inode_cache 7005 14792 480 1598 1849 1
# dentry_cache 5469 5880 128 183 196 1
# filp 726 760 96 19 19 1
# buffer_head 67131 71240 96 1776 1781 1
# vm_area_struct 1204 1652 64 23 28 1

としてほしいわけだ。これは、/proc/meminfo の次の行から出力して欲しい。だから:
"2009/03/01 10:48:58"<tab>"1001008"<tab>"200708"<tab>"43400"....
# slabinfo - version: 1.1
# kmem_cache 60 78 100 2 2 1
# blkdev_requests 5120 5120 96 128 128 1
# mnt_cache 20 40 96 1 1 1
# inode_cache 7005 14792 480 1598 1849 1
# dentry_cache 5469 5880 128 183 196 1
# filp 726 760 96 19 19 1
# buffer_head 67131 71240 96 1776 1781 1
# vm_area_struct 1204 1652 64 23 28 1

:

のような出力が延々続くログファイルが欲しい、という事になる。

背景


oom-killer が発動したシステムがあった。
/var/log/messages に残る oom-killer の行動痕跡によると、slab が異常に大きい。
そこで、slab の状態を記録すると同時に、自分たちが何をやっていたのかを記録する。

slab がでかくなったタイミングで何をやっていたのか知れば、問題点がわかるに違いない。そのための監視用ログシステムが欲しい、と言うわけ。

参考資料


スクリプト言語であれば何を使っても良かったのだが、とりあえず bash と sed と awk ということなので、参考資料は次の通り。