【Perl】fpingをPerlで実装してみる(その1)
投稿日: / 更新日:
この記事は2年以上前に書かれたものです。情報が古い可能性があります。
AnyEventという、非同期のイベント駆動プログラミングができるCPANモジュールがあります。Perl界隈ではもう何年も前からよく話題に上っていたのですが、私はこれまで全く使った事がなかったので、練習がてらこのモジュールを使用して、fping的なプログラムを実装してみました。
以下はサンプルコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#!/usr/bin/env perl # fping.pl use Modern::Perl; use Getopt::Lucid ":all"; use AnyEvent::FastPing; use AnyEvent::Socket; use List::MoreUtils "any"; our $VERSION = "0.01"; my $opts = Getopt::Lucid->getopt([ Param("count|C")->default(1)->valid(sub { $_ > 0 }), Param("interval|p")->default(1000), # in msec, also works as timeout Param("timeout|t")->default(500), # in msec, currently not in use Switch("quiet|q"), # currently not in use ])->validate; my $done = AnyEvent->condvar; my $pinger = AnyEvent::FastPing->new; my @hosts = @ARGV; my $count = 1; my $count_max = $opts->get_count; my $result_of = {}; # initialize results with values of failure $result_of->{$_} = [ map { "-" } $count..$count_max ] for @hosts; $pinger->interval(0); $pinger->max_rtt($opts->get_interval / 1000); add_hosts($pinger, \@hosts); $pinger->on_recv( sub { my ($ping_reply) = @_; for my $result (@$ping_reply) { my $host = format_address($result->[0]); my $value = $result->[1] * 1000; # in msec $result_of->{$host}->[$count - 1] = sprintf "%0.2f", $value; } } ); $pinger->on_idle( sub { if ($count++ < $count_max) { add_hosts($pinger, \@hosts); $pinger->start; } else { my $exit_code = 0; for my $host (sort keys $result_of) { # result values are in msec, or "-" if timed out say "$host : " . join " ", @{$result_of->{$host}}; $exit_code = 1 if any { $_ eq "-" } @{$result_of->{$host}}; } exit $exit_code; } } ); sub add_hosts { my ($pinger, $hosts) = @_; $pinger->add_hosts([ map { parse_ipv4 $_ } @$hosts ]); } $pinger->start; $done->wait; __END__ |
実は、AnyEventを使用してfpingを実装するためにおあつらえ向きのAnyEvent::FastPingというCPANモジュールが既にあったので、今回はこちらを使わせて頂きました(こちらも、AnyEventと同じ著者のモジュールです)。おかげで、約60行と非常に短いコード量で実装することができました(反面、あまりAnyEventの勉強にならなかったのが心残りですが…)。
コードの方ですが、AnyEventではイベント駆動のためのコールバック関数をあらかじめ登録しておく必要があります。今回は、on_recv
(pingの応答を受信した時)とon_idle
(全てのホストへpingを送信し、あらかじめ設定した時間が経過した時)という2つのイベントに対して、それぞれ無名サブルーチンのリファレンスを割り当てています。前者ではping応答の結果をホスト名をキーとしたハッシュへ格納し、後者ではping送信が指定回数に達したかどうかを判定し、結果の出力またはpingの再実行を行います。
なお、コマンドラインオプションは”-C”(ping回数)と”-p”(間隔)しか実装していませんが、基本的にはHinemosに同梱されているfpingコマンドと同様の使い方ができます。なおfpingと異なり、”-p”オプションは今のところ、1回毎のpingのタイムアウトを兼ねています。
以下は使用例です。なお、ping送信間隔は1000msec(デフォルト)です。
1 2 3 4 5 6 7 8 9 10 |
[root@node118 fping]# time ./fping.pl -C 3 -q 192.168.11.114 192.168.11.115 192.168.11.116 192.168.11.117 192.168.11.118 192.168.11.114 : 0.37 0.57 0.77 192.168.11.115 : - - - 192.168.11.116 : - - - 192.168.11.117 : - - - 192.168.11.118 : 0.17 0.18 0.29 real 0m3.071s user 0m0.051s sys 0m0.019s |
Perlの場合、実行フェーズの前にコンパイルフェーズが走るため、その分少し時間が長くかかりますが、それでも全体で3秒少々しかかかっておらず、全てのホストに対して非同期にpingを送信していることが分かると思います。
ちなみに、Hinemosのfpingで同じ事をすると、以下のようになります。こちらも、ping送信間隔は1000msec(デフォルト)です。
1 2 3 4 5 6 7 8 9 10 |
[root@node114 sbin]# time ./fping -C 3 -q 192.168.11.114 192.168.11.115 192.168.11.116 192.168.11.117 192.168.11.118 192.168.11.114 : 0.08 0.05 0.11 192.168.11.115 : - - - 192.168.11.116 : - - - 192.168.11.117 : - - - 192.168.11.118 : 0.70 0.43 0.31 real 0m2.667s user 0m0.002s sys 0m0.010s |
ネイティブなのでさすがに速いです。また、CPUの使用時間もごく僅かです。総実行時間(real)が3秒を切っている理由は分かりませんが、実際に使用する分には恐らく問題はないと思います。
オプション指定時の挙動をもう少し本家fpingに近づけることができれば、Hinemosが使用するfpingをこちらに差し替える事もできると思います(運用上、ほとんど意味はないですが…)。
以上、簡単ですがPerlに関する話題をご紹介しました。