- CentOS 7.2.1511
- bash 4.2.46
- coreutils 8.22
例えば、急にものすごいアクセス数が増えていて、1秒間のリアルタイムなログを集計したいときとか。
次のようにすれば Apache のアクセスログから IP アドレスで集計した結果を得られます。
cat access_log | awk '{print $1}' | sort | uniq -c | sort -rn
リアルタイムに書き込まれたログを表示するといえば tail -f
ですが、次のようにしても、
tail -n0 -f access_log | awk '{print $1}' | sort | uniq -c | sort -rn
tail
を止めるために Ctrl+C するとパイプ先のプロセスまで一緒に死んでしまいます。
timeout を使う → 失敗
timeout
を付けて tail
を実行してみます。
timeout 1 tail -n0 -f access_log | awk '{print $1}' | sort | uniq -c | sort -rn
だめでした。タイムアウトしたときにパイプ先のプロセスまで死んでいます。
timeout
はタイムアウトしたときにプロセスグループを皆殺しにしていました。
言い換えると、上の書き方でパイプ先にまでタイムアウトを掛けることができているということですね。
timeout --foreground を使う
man timeout
によると --foreground
をつけると大丈夫そうなのでやってみました。
timeout --foreground 1 tail -n0 -f access_log | awk '{print $1}' | sort | uniq -c | sort -rn
成功です、開始から1秒で tail
だけ死んで集計結果が表示されました。
timeout と setsid を使う
CentOS 6 の timeout
だと --foreground
がありませんでした・・
timeout
がプロセスグループ皆殺しにするのをどうにかすればいいだけなので、パイプ先で setsid
してプロセスグループが変わるようにしてみました。
timeout 1 tail -n0 -f access_log | setsid sh -c "awk '{print \$1}' | sort | uniq -c | sort -rn"
成功です。
timeout と timeout を使う
次のようにパイプ先で timeout
しても大丈夫でした。
timeout 1 tail -n0 -f access_log | timeout 2 sh -c "awk '{print \$1}' | sort | uniq -c | sort -rn"
timeout
はプロセスグループを皆殺しにするために setpgid(0, 0)
でプロセスグループリーダーになるようです。
なので↑のようにすると先頭の timeout
とパイプ先の timeout
でプロセスグループが変わり、先頭の timeout
がパイプ先の timeout
を殺せなくなります。
例えば、次のようにすると3つの sleep
は別々のプロセスグループになりますし、Ctrl+C でも死ぬのは最初の sleep
だけになります。
timeout 30 sleep 30 | timeout 30 sleep 30 | timeout 30 sleep 30
pstree -agp 1318
# bash,1318,1318
# ├─timeout,17993,17993 30 sleep 30
# │ └─sleep,17998,17993 30
# ├─timeout,17994,17994 30 sleep 30
# │ └─sleep,17997,17994 30
# └─timeout,17995,17995 30 sleep 30
# └─sleep,17996,17995 30
--foreground
をつけるとこの動きが変わり、3つの sleep
が全部同じプロセスグループになりました。
timeout --foreground 30 sleep 30 | timeout --foreground 30 sleep 30 | timeout --foreground 30 sleep 30
pstree -agp 1318
# bash,1318,1318
# ├─timeout,18008,18008 --foreground 30 sleep 30
# │ └─sleep,18013,18008 30
# ├─timeout,18009,18008 --foreground 30 sleep 30
# │ └─sleep,18011,18008 30
# └─timeout,18010,18008 --foreground 30 sleep 30
# └─sleep,18012,18008 30
この場合は timeout
は直接の子プロセスだけ殺します。