Linux 版の Sysinternals ProcDump を試す (2)

前回の続きです

hiboma.hatenadiary.jp

実装の話

本エントリでは ProcDump が コアダンプをどのように採取するかを調べていく.

(現状の) Linux ProcDump は gcore を薄くラップして扱うバイナリと理解した

ダンプの採取方法を調べる 🔍

github.com

ソースコードの量は大したことないので git clone してざっと斜め読みするといい. スレッドを積極的に使う設計は Windows な流儀なのかな?

CoreDumpWriter.c が肝 📖

ファイルの量も少ないの順番に眺めていって、CoreDumpWriter.c がコアダンプ採取の責務を負っているソースだと判別をつけた.

詳細はすっ飛ばして、以下の行を見れば gcorepopen2() で呼び出しているのが確認できる

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) で結果を受け取る関数である. PythonRuby のインタフェースを真似た感じかな? (この関数の実装も同ファイルに載っているが、冗長なので省略する)

gcore とは何ですか?

gcore の中身はシェルスクリプトで、 gdb をラップしたコマンドである. gdb をインストールすると付属してくるコマンド

🔗 gcore のソースを載せた gist

gcore を呼び出すと、結局は gdb を呼びだすことになる. ProcDump 独自の実装でコアダンプを採取しているのかと思ったが、そんなことなかった 🙃 すでに gdb で出来ることを作り直すのは、大車輪の再実装になるもんね

プロセスをどのように見張っているのか?

前回のエントリでは CPU 使用率やメモリ(RSS) を閾値にしてコアダンプをとってみたが、どういった仕組みなのだろうか?

f:id:hiboma:20190217121748g:plain

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 に書いてある

github.com

初見で「phtread を抽象化して扱ってるし、難しいのかな ...? 」と身構えてしまったが、詳細を読んでいくと思ったよりも素朴な実装 + 設計である.

感想

/proc/$pid/stat や cgroup の値を閾値 最新のメトリクスだと PSI なんかを見張ったりすると、より精緻なトリガーを作れたりするのかなと思った

udzura.hatenablog.jp

コアダンプ採取のコードは gcore だった

  • バックトレースだけ獲れりゃ十分なケースもあると思うので gstack に置き換えられるといいかなぁ
  • プロセスのメトリクスをとってコマンドをトリガーするコードと、コアダンプを採取するコードとを分離すると汎用的なツールに消化できそうだが ( それ、monit じゃね??? とか ... )