SystemtapでディスクI/Oの監視

はてぶでhttp://d.hatena.ne.jp/sh2/20100120を見かけたので、ちょっと調べてみました。id:sh2perlと組み合わせて処理していますが、定期的にサマリを表示させるのならstapだけでもいいですね。
ただ、カーネル内でfdからパス名に変換するのって、ちょっとスクリプト内で完結させるのは難しいかもしれません。(多分TOMOYOはこのあたりを処理しているはずなのでなんとかなると思うけど・・・proc以下をみるほうが簡単な気がしました*1

シェルスクリプトと組み合わせてチートさせてみたのが以下の実装です。特に新しい機能とかつけていません。

  • iotrace.sh
#!/bin/sh
//usr/bin/env stap $0 $1 | while read pid fd data; do if [ -z "$pid" ]; then echo; continue; fi; file=`readlink /proc/$pid/fd/$fd`; echo $1\($pid\) $data from $file; done; exit $?

# --- systemtap script ---
global rdbs, wrbs;

probe syscall.read, syscall.pread {
	if (execname() == @1)
		rdbs[pid(), fd] <<< count;
}

probe syscall.write, syscall.pwrite {
	if (execname() == @1)
		wrbs[pid(), fd] <<< count;
}

probe timer.s(5) {
	foreach ([pid+, fd] in rdbs) {
		printf("%d %d read %d B/s (avg %d bytes)\n", pid, fd,
			@sum(rdbs[pid, fd])/5, @avg(rdbs[pid, fd]));
	}
	foreach ([pid+, fd] in wrbs) {
		printf("%d %d write %d B/s (avg %d bytes)\n", pid, fd,
			@sum(wrbs[pid, fd])/5, @avg(wrbs[pid, fd]));
	}
	delete rdbs
	delete wrbs
	println("");
}

一行目がやたら長いのはご愛嬌:)。

$ sudo iotrace.sh firefox

とかすると、指定した名前のプロセスの出力がこんな風に出ます。

firefox(1974) read 435814 B/s (avg 4096 bytes) from socket:[17483]
firefox(1974) read 18022 B/s (avg 2048 bytes) from socket:[17596]
firefox(1974) read 33 B/s (avg 1 bytes) from pipe:[17500]
firefox(1974) read 204 B/s (avg 1024 bytes) from pipe:[17493]
firefox(1974) write 33 B/s (avg 1 bytes) from pipe:[17500]
firefox(1974) write 8842 B/s (avg 44210 bytes) from
firefox(1974) write 0 B/s (avg 1 bytes) from pipe:[17493]

firefox(1974) read 611942 B/s (avg 4096 bytes) from socket:[17483]
firefox(1974) read 9830 B/s (avg 2048 bytes) from socket:[17596]
firefox(1974) read 29 B/s (avg 1 bytes) from pipe:[17500]
firefox(1974) write 29 B/s (avg 1 bytes) from pipe:[17500]

簡単な説明

  • @Nを使うと、コマンドの引数を、文字列として取り込めます。$Nだと、スクリプト内の数値として使えます。
  • トレースデータはその場で出さず、statistics変数に溜め込み、平均値とか最大値を出せるようにしておくと、トレース出力も減らせて一石二鳥です。
  • シェルスクリプトに融合させてしまう*2http://d.hatena.ne.jp/mhiramat/20081219/1229658198でも触れたように、行の頭が//で始まっていれば、systemtap自体はその行を無視するので、シェルスクリプトと融合させるのはそんなに難しくないです。一行野郎はいやだ、という方には、行頭に//bin/true;とか書くのはどうでしょう(笑)。

systemtap公式のサンプルスクリプト

しかし本当に重要なのは、systemtapのサンプルスクリプトは、systemtap-testsuiteパッケージに含まれているという事実ではないでしょうか・・・。実のところ、/usr/share/systemtap/testsuite/systemtap.examples/以下に、上記のようなioのトレースサンプルがいくつも含まれていたりします。

$ ls /usr/share/systemtap/testsuite/systemtap.examples/io/
disktop.meta      iostat-scsi.stp  io_submit.stp  mbrwatch.meta  traceio.stp
disktop.stp       iostat-scsi.txt  iotime.meta    mbrwatch.stp   ttyspy.meta
ioblktime.meta    iostats.meta     iotime.stp     traceio2.meta  ttyspy.stp
ioblktime.stp     iostats.stp      iotop.meta     traceio2.stp
iostat-scsi.meta  io_submit.meta   iotop.stp      traceio.meta

他にも、ネットワークのサンプルとかが多数含まれているので、要チェックです。

*1:ごめん、本当は調べるのが面倒でした。

*2:これは向き不向きがあると思うけど