前回の続きです
実装の話
本エントリでは ProcDump が コアダンプをどのように採取するかを調べていく.
(現状の) Linux ProcDump は gcore を薄くラップして扱うバイナリと理解した
ダンプの採取方法を調べる 🔍
ソースコードの量は大したことないので git clone してざっと斜め読みするといい. スレッドを積極的に使う設計は Windows な流儀なのかな?
CoreDumpWriter.c が肝 📖
ファイルの量も少ないの順番に眺めていって、CoreDumpWriter.c がコアダンプ採取の責務を負っているソースだと判別をつけた.
詳細はすっ飛ばして、以下の行を見れば gcore
を popen2()
で呼び出しているのが確認できる
int WriteCoreDumpInternal(struct CoreDumpWriter *self) { // ... // assemble the command if(sprintf(command, "gcore -o %s_%s_%s %d 2>&1", name, desc, date, pid) < 0){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed sprintf gcore command"); exit(-1); } // generate core dump for given process commandPipe = popen2(command, "r", &gcorePid); self->Config->gcorePid = gcorePid;
popen2 はシェルを fork(2) して pipe(2) で結果を受け取る関数である. Python や Ruby のインタフェースを真似た感じかな? (この関数の実装も同ファイルに載っているが、冗長なので省略する)
gcore とは何ですか?
gcore
の中身はシェルスクリプトで、 gdb をラップしたコマンドである. gdb をインストールすると付属してくるコマンド
gcore
を呼び出すと、結局は gdb
を呼びだすことになる. ProcDump 独自の実装でコアダンプを採取しているのかと思ったが、そんなことなかった 🙃 すでに gdb で出来ることを作り直すのは、大車輪の再実装になるもんね
プロセスをどのように見張っているのか?
前回のエントリでは CPU 使用率やメモリ(RSS) を閾値にしてコアダンプをとってみたが、どういった仕組みなのだろうか?
ProcDump プロセスを strace すると /proc/$pid/stats
を 1秒ごとに open(2), read(2) しているスレッドがトレースできる
# 💤 1秒ブロックすることを示す [pid 3073] futex(0x60b5bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 25, {1550324077, 477259191}, ffffffff) = -1 ETIMEDOUT (Connection timed out) 💤 [pid 3073] futex(0x60b590, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 3073] kill(3060, SIG_0) = 0 [pid 3073] open("/proc/3060/stat", O_RDONLY) = 3 👈 [pid 3073] fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 [pid 3073] read(3, "3060 (a.out) S 2854 3060 2854 34"..., 1024) = 301 [pid 3073] close(3) = 0 [pid 3073] kill(3060, SIG_0) = 0 [pid 3073] futex(0x60b5bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 27, {1550324078, 479654480}, ffffffff) = -1 ETIMEDOUT (Connection timed out) 💤 [pid 3073] futex(0x60b590, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 3073] kill(3060, SIG_0) = 0 [pid 3073] open("/proc/3060/stat", O_RDONLY) = 3 👈 [pid 3073] fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 [pid 3073] read(3, "3060 (a.out) S 2854 3060 2854 34"..., 1024) = 301 [pid 3073] close(3) = 0 [pid 3073] kill(3060, SIG_0) = 0 [pid 3073] futex(0x60b5bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 29, {1550324079, 503662811}, ffffffff) = -1 ETIMEDOUT (Connection timed out) 💤 [pid 3073] futex(0x60b590, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 3073] kill(3060, SIG_0) = 0 [pid 3073] open("/proc/3060/stat", O_RDONLY) = 3 👈 [pid 3073] fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 [pid 3073] read(3, "3060 (a.out) S 2854 3060 2854 34"..., 1024) = 301 [pid 3073] close(3) = 0 [pid 3073] kill(3060, SIG_0) = 0
ここらの実装は TriggerThreadProcs.c に書いてある
初見で「phtread を抽象化して扱ってるし、難しいのかな ...? 」と身構えてしまったが、詳細を読んでいくと思ったよりも素朴な実装 + 設計である.
感想
/proc/$pid/stat
や cgroup の値を閾値 最新のメトリクスだと PSI なんかを見張ったりすると、より精緻なトリガーを作れたりするのかなと思った
コアダンプ採取のコードは gcore
だった
- バックトレースだけ獲れりゃ十分なケースもあると思うので
gstack
に置き換えられるといいかなぁ - プロセスのメトリクスをとってコマンドをトリガーするコードと、コアダンプを採取するコードとを分離すると汎用的なツールに消化できそうだが ( それ、monit じゃね??? とか ... )