読者です 読者をやめる 読者になる 読者になる

百日半狂乱

Shut the fuck up and write some code!!

awkで合計とか平均とか算出する、ついでにgnuplotでグラフ化してみる

例えばイベントの実行時間を記録したログファイルから、個々のイベントの平均実行時間を算出したいとする.

例えば以下のようなawkを使って統計を取ることができる.

ログファイルは以下のような感じのものを想定.

#event exec_time
foo 10
hoge 8.431
fuga 0.542
fuga 1.021
fuga 0.732
fuga 0.981
foo 30
bar 5.679
hoge 9.321
hoge 10.021
bar 6.091
foo 60
hoge 11.234
fuga 1.329

今回はついでに以下のようなグラフにしてデータの可視化も頑張ってみる.

f:id:doi-t:20131127013847p:plain

awkで取った統計データをgnuplotでグラフ化する

冒頭のawk*1をcalc_avgというシェル関数にして、ログファイル(sample.log)の各イベント毎に取った統計を1つのファイルにまとめてgnuplotで描画する.

ログファイル中のイベント名でgrepをかけて、イベント毎に統計を取るイメージ.

分かりやすさ重視のつもりだけど、コードの繰り返しが見苦しい場合、

grep "hoge" $1 | calc_avg >> $plotfile
grep "fuga" $1 | calc_avg >> $plotfile
grep "foo" $1 | calc_avg >> $plotfile
grep "bar" $1 | calc_avg >> $plotfile

は、

for log_msg in `cat $1 | awk '{ print $1 }' | sort | uniq`
do
    grep "$log_msg" "$1" | calc_avg >> $plotfile
done

とする方が良いかも.

これならイベントの種類が増えても大丈夫.

gnuplotのコードも工夫すれば繰り返し減らせるかもしれないけど、グラフはその場の用途に応じてすぐ変更がかかるから、あんまり苦労が報われない.

一応、

$ git clone https://gist.github.com/7660870.git
$ cd 7660870
$ ./awk_avg.bash sample.log

とするとgraph_pngというディレクトリが生成されてその中に、合計、回数、最大、最小、平均の各種グラフが出来上がります.

生成される統計データはこんな感じ*2

#label    sum count   max min avg
hoge    39.007    4  11.234    8.431 9.75175
fuga    4.605 5  1.329 0.542 0.921
foo 100    3  60 10 33.3333
bar 11.77 2  6.091 5.679 5.885

awkgnuplotのサンプルコードだと思ってもらえればこれ幸い*3

行志向スクリプトawk

とにかく何でもASCIIテキストで扱いたいので、フィルタプログラムはどんどん覚えていきたい.

その中でawkは便利さを感じていたけれど、なかなかどうして身近なコマンドになるまでに時間がかかった.

Wikipediaから引用

AWK のスクリプトは、パターンとアクションの組を並べた形になっている。実行を開始すると、まず BEGIN パターンのアクションを実行する。以降、入力を読み込んでは、レコードセパレータ(デフォルトでは行末)までを1レコードとし、フィールドセパレータに従ってフィールドに分割してから、レコードがパターンにマッチするかを調べ、パターンにマッチしたらそのパターンに対応するアクションを実行する。一致するパターンが複数ある時は、該当するアクションが上から順に全て実行される。これを入力が尽きるまで繰り返す。入力が尽きたら、END パターンのアクションを実行し、終了する。

パターンとかアクションとか言う前に意識すべきは処理の単位たるレコードとフィールだと思うので、結局重要なのは、

レコードセパレータ(デフォルトでは行末)までを1レコードとし、フィールドセパレータに従ってフィールドに分割

の部分じゃないのかなと思う.

個人的には言語設計が"行志向"であるということを何かで読んでから急にコマンドとの距離感が縮まった.

ただ、行志向と言ってもデフォルトを切り替えることができる.

awk デフォルト 組み込み変数
レコードセパレータ 改行 RS
フィールドセパレータ スペース FS

例えばこんな感じで各セパレータを切り替えられる.

$ echo 'hoge:fuga|foo:bar|' | awk 'BEGIN{FS=":"; RS="|"} { print $2 }'

この場合、"|"が改行と、":"がスペースと置き換わって、各レコードの第2フィールドにあたるfugaとbarがprintで表示される.

awkの組み込み変数の存在をはっきり認識すると急にやれることが増えた気がする.

組み込み関数とかもあるけどそこまで使い込めてない.

詳しくは、man awkを叩く.

gist記法

関係ないけど、1つのgistリポジトリに複数のファイルがある場合に、個々のファイルを指定してブログに表示したいんだけどできないんだろうか.

Github GistのPermalinkに合わせて[gist:7661278#file-calc_avg-awk]とかできたら良いのになぁ.

リンク切れやすいからやらないとか何かポリシーがあるのだろうか.

*1:ついでに合計、回数、最大、最小も算出している.OFSは出力フィールドのセパレータにあたる組み込み変数

*2:コピペしたらインデントが悲しいことになった.端末で見ると見栄えがいいはず...

*3:ログファイルのサンプルをでっち上げるのが難しすぎて悶えた