親プロセスのパンくずリスト的な表示

今いるシェルなり何なりで、親プロセスを手繰って行きたい時がたまにある。
“ps —forest” や “pstree -hp” でも良いんだけど、もう少しシンプルで軽いのが欲しかったので、親プロセスをパンくずリスト風に表示するプログラムをawkで書いてみた。


使い方

スクリプトを実行すると、その実行環境のプロセスから親プロセスを手繰ってパンくずリスト風に表示する。
[root@localhost ~]# ./pbreadcrumbs
init(1) > sshd(1488) > sshd(32639) > bash(32642)
以下に示すようにpid 32642は現在のシェル環境。
[root@localhost ~]# echo $$
32642
例えば、Ansibleとか、何かの延長で呼び出しても良い。
うまく使うと、プロセスコールのコールツリー/バックトレース的に使うこともできる。
[ansibleman@controller ~]$ ansible -i inventory *.64 -m script -a ./pbreadcrumbs

192.168.170.64 | success >> {
    "changed": true,
    "rc": 0,
    "stderr": "",
    "stdout": "init(1) > sshd(1488) > sshd(33216)\n"
}

コード

pbreadcrumbsのコードを示す。とてもシンプル。
#!/bin/awk -f
BEGIN {
        cur_pid = PROCINFO["ppid"]      # Find the caller's process id.

        n = 0
        do {
                statfile = sprintf("/proc/%d/stat",cur_pid)
                getline < statfile      # Now, $2 is name, and $4 is ppid.
                err = close(statfile)   # gawk extension
                if (err)
                        break

                name = gensub("[()]","","g",$2)
                stack[n++] = sprintf("%s(%d)",name,cur_pid);
                cur_pid = $4

        } while(name!="init" && n<10)

        for (i = n-1; i>0; i--)
                printf "%s > ",stack[i]
        printf "%s\n",stack[0]
}
getline で /proc/<pid>/stat を読み、プロセス名と親プロセスのpidを取得して、親へ遡るループを回る。途中で運悪く存在しないプロセスに行き当たってしまった場合、errが非0になるので、そこでループを終了している。最後のブロックは逆順でのパンくずリストの表示。

性能

ps や pstree は、基本的に全プロセスの一覧を最初に取得し、その後にソートなりツリー構築するので、プロセスが多いと重たい処理になる。メモリフットプリントも大きい。
例えば、3000プロセスを生成してpstreeを実行してみる。
[root@localhost ~]# time pstree -h > /dev/null
real    0m0.255s
user    0m0.117s
sys     0m0.137s
同じ環境で pbreadcrumbs を実行してみる。
[root@localhost ~]# time pbreadcrumbs
init(1) > sshd(1488) > sshd(32639) > bash(32642)
real    0m0.003s
user    0m0.002s
sys     0m0.000s
pstreeはCで、pbreadcrumbsはスクリプト言語だけれど、後者は数回しかループを回らないから軽い。

プロセスを増やすテストプログラム(おまけ)

性能テストで使ったシェルスクリプト ./manyproc.sh を参考に示す。
モダンに? inotify-tools パッケージを使って書いてみた。
#!/bin/bash

# You must set sysctl limit to allow $MAXFORK. For example,
#   sysctl -w fs.inotify.max_user_instances=3500

MAXFORK=3000
STOPFILE=./DELETE_TO_STOP

touch $STOPFILE

for ((i=0;i<$MAXFORK;i++))
do
        # inotify-tools required.
        inotifywait -e delete_self $STOPFILE >/dev/null 2>&1 </dev/null &
done

echo "inotifywait forked $i times."
echo "delete $STOPFILE to quit the forked processes"
実行方法:
[root@localhost ~]# sysctl -w fs.inotify.max_user_instances=3500
fs.inotify.max_user_instances = 3500
[root@localhost ~]# ./manyproc.sh
inotifywait forked 3000 times.
delete ./DELETE_TO_STOP to quit the forked processes
[root@localhost ~]# pgrep inotify | wc -l
3000
止め方:
[root@localhost ~]# rm -f DELETE_TO_STOP
inotifyはとっても便利です。

コメントを投稿