プロセスをツリー表示

ちょこちょことPerlを勉強中。
んで、プロセスのリストを親子関係で並び替えて表示するのを作ってみた。

pstree

#!/usr/bin/perl -w
use strict;
use warnings;

my @processes;
open my $FH, "/bin/ps -eo pid,ppid,user,etime,pcpu,pmem,args |"
    or die "ERROR: $!";
my $header = <$FH>;
while (<$FH>){
    chomp;
    s/^\s+//;
    my @F = split(/\s+/, $_, 7);
    my $p = {
        PID      => $F[0],
        PPID     => $F[1],
        USER     => $F[2],
        ELAPSED  => $F[3],
        PCPU     => $F[4],
        PMEM     => $F[5],
        COMMAND  => $F[6]
    };
    push @processes, $p;
}
close $FH or die "ERROR: $!";

print "Tree    " . $header;
&tree(0, 1, \@processes);

sub printLine {
    my ($p, $I) = @_;
    printf "%-7s %5d %5d %-8s %11s %4s %4s %s\n",
        "+" x $I,
        $p->{"PID"},
        $p->{"PPID"},
        $p->{"USER"},
        $p->{"ELAPSED"},
        $p->{"PCPU"},
        $p->{"PMEM"},
        $p->{"COMMAND"};
}

sub tree{
    my ($PPID,$I, $processes) = ($_[0],$_[1], $_[2]);
    foreach (@{$processes}){
        if ($_->{"PPID"} == $PPID){
            &printLine($_, $I);
            &tree($_->{"PID"}, $I+1, $processes);
        }
    }
}

親子関係がPIDとPPIDを睨めっこせずとも分かって良い感じ。
拙いコードだと思うのでツッコミ等があったらよろしくお願いします。

追記:2008-10-04

さっそくトラックバックを頂いた。

ありがとうございます。

my $cmd = "/bin/ps -Aeo " . (join ',', map { lc } @attrs) . " |";

とか

$p->{$attrs[$_]} = $F[$_] for 0 ... $#attrs;

とか

printf $format, "+" x $I, map { $p->{$_} } @attrs;

にそういう書き方があったのかっと感動。こういうのをサクッと書けるようになりたい。

  • サブルーチンを呼び出すときは &foo() っていう風にアンパサンド要りません。foo() でおk。

なにー、そうなのか。知らなかった。thx!

  • 基本的に関数を呼び出すときにカッコは必要だったり別に要らなかったりしますが、「組み込み関数はカッコを使わずに、そうでない関数はカッコをつけて呼び出す」という方向があります。

一応知っていて幾つかはそうしていたけど、徹底していなかった。気をつけます。

whileを使ったときの$_がグローバルになるとは知らなかったので勉強になった。物臭なので今まで$_で済ませていたが、これからはきちんとやります。

うん、やっぱり公開するといろいろ反応をもらえて勉強になるなぁ。精進精進。

追記:2008-10-04 その2

  • 個人的にプロセスの実行時間は重要要素なのでETIMEを追加
  • ヘッダ部分がずれているので修正と標準エラー出力に変更

なぜ標準エラーに出力するかというと、このスクリプトの使い方としてgrepでフィルタしてマッチした行の前後何行かを表示する事を想定しているのだけれど、

 $ ./pstree | grep -i command -C 2
 Tree      PID  PPID USER         ELAPSED %CPU %MEM COMMAND
 +++     19490 19486 teramako    04:01:28  0.0  0.1 zsh
 ++++    22794 19490 teramako       00:00  0.0  0.0 /usr/bin/perl -w ./pstree
 +++++   22796 22794 teramako       00:00  0.0  0.0 /bin/ps -Aeo pid,ppid,user,etime,pcpu,pmem,command
 ++++    22795 19490 teramako       00:00  0.0  0.0 grep --color -i command -C2
 +++     22056 19486 teramako    02:21:55  0.0  0.1 zsh
 ++++    22550 22056 teramako    01:15:54  0.2  0.1 vi haiku.js

とかした時にヘッダ部分にまでマッチすることを避けるためと/dev/null等に捨てない限りヘッダが常に表示されて気持ちが良いからだ。

修正部分含めもう一度アップ

pstree

#!/usr/bin/perl -w
use strict;
use warnings;
 
my @processes;
my @attrs = qw/PID PPID USER ETIME PCPU PMEM COMMAND/;
my $format = "%-7s %5d %5d %-8s %11s %4s %4s %s\n";
my $cmd = "/bin/ps -Aeo " . (join ',', map { lc } @attrs) . " |";
open my $FH, $cmd or die "ERROR: $!";
my $header = <$FH>;
while (my $line = <$FH>){
    chomp $line;
    $line =~ s/^\s+//;
    my @F = split /\s+/, $line, scalar @attrs;
    my $p;
# die unless @attrs == @F;
    $p->{$attrs[$_]} = $F[$_] for 0 ... $#attrs;
    push @processes, $p;
}
close $FH or die "ERROR: $!";
 
print STDERR "Tree    " . $header;
tree(0, 1, \@processes);
 
sub printLine {
    my ($p, $I) = @_;
    printf $format, "+" x $I, map { $p->{$_} } @attrs;
}
 
sub tree{
    my ($PPID, $I, $processes) = @_;
    for my $process (@{$processes}){
        if ($process->{"PPID"} == $PPID){
            printLine($process, $I);
            tree($process->{"PID"}, $I+1, $processes);
        }
    }
}