GrowthForecastでサーバルームの室温監視

研究室のサーバールームの空調が弱くて,40コアくらいある計算サーバにフルで仕事させると室温が上がって,全サーバが落ちるみたいな事例が去年あったらしいので,室温のグラフ化とアラートメール送信をやれみたいなタスクが降ってきた.

そこで,サーバルームのうちのサーバ1台にUSB温度計を差して,定期的に温度を取得し,GrowthForecastに投げればいいと考えた. アラートメールのほうは温度を取得したのちに閾値を超えていたらsendmailでメール投げる感じ。

USB温度計として下記を使用する.Linuxから温度データを取得できることが重要.

Amazon.co.jp: サンコー USB温度計 AKIBA58: 家電・カメラ

必要なもの

  • Ubuntu 12.04
  • perl (GrowthForacastがPerlで書かれているので必要)
  • GrowthForecast (グラフ表示用のWebアプリケーション)
  • Temper (上記温度をGrowthForecastに登録してくれるスクリプト.アラートメール機能もある)

必要なパッケージとモジュールのインストール

$ sudo apt-get install build-essential libusb-0.1-4 libusb-dev build-dep sendmail git
$ curl -L http://cpanmin.us/ | sudo perl - App::cpanminus
$ sudo cpanm -n GrowthForecast HTTP::Tiny Email::Simple Email::Sender

GrowthFocastのインストール

以前の 研究室のサーバに流行りのGrowthForecastを導入してみた - ゆううきブログ を参照. perlbrew使ってたけど,perl-buildにした.

Temper

Temperをインストールする.TemperはUSBドライバAPIを叩いて上記USB温度計から温度情報を摂氏で取得して標準出力に出力してくれるもので,たぶん有志の人がつくってくれたやつ.

bitplane/temper · GitHub

今回はこれをforkして,出力される時間をJSTに直して,さらにGrorwthForecastのAPIを叩く処理とメール投げる処理を書いたスクリプトを入れておいた.

リポジトリの中身

  • temperコマンド: 温度情報を摂氏で取得して標準出力
  • temper_gfcast.pl: temperコマンドを実行して,GrowthForecastのAPIを叩いて温度を登録し,閾値を超えていたらアラートメールを投げる
  • temper_gfcast.conf: 閾値などの設定を行う
  • install.sh
$ git clone https://github.com/y-uuki/temper
$ cd temper
$ sudo ./install.sh

これで,temperコマンドとtemper_gfcast.plが/usr/local/binに入り,/etc/temper_gfcast/temper_gfcast.confが作成される.

まず, /etc/temper_gfcast/temper_gfcast.confを書き換える.

# Examle
#
# sender: root@example.com
# receiver: server-group@example.com
#
# graph_url: http://example.com/list/server-room/watch
#
# warning_threshold: 35
# critical_threshold: 45
#

sender: server-room@laboraoty
receiver: y_uuki@laboratory

graph_url: http://growthforecast.laboratory/list/server-room/temper

warning_threshold: 35
critical_threshold: 45

senderはメールの送信者,receiverはメールの受信者,graph_urlはGrowthForecast APIのendpointで, warnings_thresholdとcriticalthresholdにはメールアラートのための閾値を設定する.上記の場合,温度が35度以上ならばwarningメールを,45度以上ならばcriticalメールを送信する.

最後に,cronで一定時間ごとにtemper_gfcast.plを実行させておけばよい,

$ sudo crontab -e

cronに下記の一行を登録する. 5分ごとに温度監視することにした.

*/5 * * * * /opt/perl-5.14/bin/perl /usr/local/bin/temper_gfcast.pl >> /var/log/temper.log 2>&1

以上で,おわり.

temper_gfcast.pl

GrowthForecastのAPIはHTTP::Tinyで叩いて,アラートメールは Email::Simpleでメール内容を作成して,Email::Senderを介してsendmailで投げる感じ.

#!/usr/bin/env perl
use utf8;
use strict;
use warnings;

use Try::Tiny;
use HTTP::Tiny;
use Email::Sender::Simple 'sendmail';
use Email::Simple;

use constant {
    TEMPER_PATH => '/usr/local/bin/temper',
    CONF_PATH   => '/etc/temper_gfcast/temper_gfcast.conf',
    DEFAULT_WARN_THRESHOLD     => 35,
    DEFAULT_CRITICAL_THRESHOLD => 45,
};


main();
exit;

sub main {
    my $temp_value = current_temperature();

    # putput log
    my ($sec, $min, $hour, $day, $month, $year) = localtime(time);
    print sprintf("temperature:%d\ttime:%04d%02d%02d-%02d:%02d:%02d\n",
        $temp_value, $year + 1900, $month + 1, $day, $hour, $min, $sec);

    send_to_gfcast($temp_value);

    my $params = parse_conf(CONF_PATH);
    my $mail_from = $params->{sender} or die "Not found sender in conf file";
    my $mail_to   = $params->{receiver} or die "Not found receiver in conf file";
    my $graph_url = $params->{graph_url} or die "Not found graph_url in conf file";
    my $warn_threshold     = $params->{warning_threshold} || DEFAULT_WARN_THRESHOLD;
    my $critical_threshold = $params->{critical_threshold} || DEFAULT_CRITICAL_THRESHOLD;

    for ($warn_threshold, $critical_threshold) {
        die "threshold is invalid. $_ is not integer" if $_ !~ /\d+/;
    }

    return if $temp_value < $warn_threshold;
    # Warning or Critical process

    my $status = $critical_threshold <= $temp_value ? 'critical' : 'warning';

    my $email = create_alertmail($mail_from, $mail_to, $graph_url, $status, $temp_value);
    try {
        sendmail($email);
    } catch {
        die "Failed sending mail $_";
    };
}


sub current_temperature {
    my $output = readpipe(TEMPER_PATH);
    chomp $output;
    unless ($output =~ /,([\d|.]+)/) {
        die "Not Found temperature";
    }
    return int($1);
}


sub parse_conf {
    my $conf_path = shift;

    unless (-f $conf_path) {
        die "file:$conf_path is not found";
    }

    open(my $conf_fh, "<", $conf_path)
        or die "failed to open file:$conf_path";

    my $params = {};
    while (my $line = <$conf_fh>) {
       chomp $line;
       next if $line =~ /^\s*$/; # empty line
       next if $line =~ /^\s*#/; # for commentout

       my ($name, $value) = split(/:\s/, $line);
       $value =~ s/(.+?)#?/$1/;
       $params->{$name} = $value;
    }

    return $params;
}


sub send_to_gfcast {
    my $temp_value = shift;

    my $http = HTTP::Tiny->new;
    my $response = $http->post_form('http://localhost/api/server-room/watch/temperature', {
        number => int($temp_value),
        mode   => 'gauge',
        color  => '#333399'
    });

    if ($response->{status} =~ /^(4\d\d|5\d\d)/) {
       die "$response->{status}: $response->{reason}\n" . $response->{content};
    }
}


sub create_alertmail {
    my ($from, $to, $graph_url, $status, $temp_value) = @_;

    my $body = <<"EOS";
Server Room Temperature

Status: $status
Temperature: '$temp_value' degrees

Graph: $graph_url
EOS

    my $email = Email::Simple->create(
        header => [
            From    => "\"Temperature Alert\" <$from>",
            To      => "<$to>",
            Subject => "Server room tempature $status",
        ],
        body => $body,
        attributes => {
            content_type => 'text/html',
        },
    );
    return $email;
}

参考

Kyoto.pm 04 Hackathonに参加しました

"Kyoto.pm 04 Hackathon"に参加しました.

畳部屋でかつ,人間をだめにするクッションの上で開発してました. とても快適でした.

作ったのはぼくの研究活動を快適にするための Net::Signaletというモジュールです.

y-uuki/Net-Signalet · GitHub

サーバクライアントモデルのベンチマークアプリケーションでサーバサイドのプロファイルを取りたい.ただし,普通にやるとサーバ側はクライアントと通信していない間は遊んでしまっているので,端末Aでサーバを起動し端末Bでクライアントを起動する間のプロファイルには意味がない.(通信終了後についても同様) そこで,クライアントの通信開始から終了までの期間のみプロファイルしたい.

というときに使います. アイデアは,サーバクライアントそれぞれに対して親プロセスとなるSupervisor的なプロセスを用意し,それぞれのSupervisorが独自のシグナルを送受信することにより,サーバの起動終了タイミングをクライアントの起動終了と同期させます.

f:id:y_uuki:20130402222858j:plain

カーネルに詳しいid:shohex さんがいらしていたので懇親会でもっとカーネルの話聞きたいみたいな感じでしたが,Perlとはぜんぜん関係ないみたいな感じだったので,また別の機会にカーネルの話聞きたいですね.

id:kiyotune さんには,"Twitterでこっそりブロックしてすぐにブロック解除する.フォローがはずれた理由はTwitterのバグ."というソリューションを教えて頂きました.

懇親会終わった後,id:kfly8 さんとラーメン食べてました. すがりに行こうとしたけどもう閉まってたから,天天有行きました. 東京の業界情報をいろいろ教えて頂きました. MF社に遊びにいきます.

id:shiba_yu36 先生,おつかれさまでした. 次回は東京のすごい人を呼ぶ感じらしいです.

#kyotopm 04 Hackathonを開催して、Cinnamonの並列化に取り組んでいました - $shibayu36->blog;

Kansai.pmでORMのパフォーマンスチェッカの構想についてLTしてきた

Kansai.pm #15に参加してきた.  

JPA派遣講師として来ていただいた@yusukebeさんの話が今回の目玉だった.

BoketeのValidationの話聞いて,そういえばPerlのValidationを真面目に考えたことなかったなと思って,FormのValidationにはForm::Validator::Lite,ModelのValidationにはData::Validatorというところだけ覚えておいてあとは自分で試してみる.

id:shiba_yu36 さんが作ってるCinnamon、普通に便利そうだし,Capもよくわからずに適当に使ってる感があるからCapにこだわってないし使ってみたい. 次回のKyoto.pmを4月か5月くらいにやるみたいな感じらしい.参加します.

LT枠が空いていてやりますといっていたけれど,前日までネタを作ってなくて,とりあえずプロファイラ便利ツールみたいなのに興味あったから,前日から作り始めた分と構想についてしゃべった.

単純なテーブルに対して単純なクエリ発行したときに,DBIからそのままSQL叩く場合とTengのRowクラスでラップしない場合とで実行時間はそんなに差がないということがわかったりした. ちなみにRowクラスでラップした場合の実行時間は,ラップしない場合と比較して2倍ほど遅かった.

Perl,一般的にはもうそんなに流行らんだろみたいなテンションだけど,今日は普通に盛り上がってたし,Perlコミュニティ力感じた.

yusukebeさん、普通に気さくな感じだったし普通にしゃべっていただいた.

あと,Mixiの@goccy54さんが1人で作ったというコピペ検出器やばかった. goccy/p5-Compiler-Tools-CopyPasteDetector · GitHub
Perl5の言語処理系つくれるとかやばすぎるし,Mixi社の技術力の高さを垣間見た.
Mixiの中の話もいろいろ教えていただいて参考になった.

今日こういう雰囲気で激しい感じだった.

Plack::Middlewareの書き方

前回,Plack::Middleware書いたので,Plack::Middlewareの書き方をメモしておく.

Plack::Middlewareモジュールに最低限必要なのは基本的に2つしかない. Plack::Middlewareを継承することcallメソッドを実装しておくだけ.

callメソッドの返り値はPSGI形式のレスポンスである.

リクエストがとんでくると,builderブロック内でenableした順番にそれぞれのcallが発行される.

package Plack::Middleware::MyMy;
use strict;
use warnings;

use parent 'Plack::Middleware';

sub call {
    my ($self, $env) = @_;

    return $self->app->($env); # 次にenableしたMiddlewareのcallを呼び出す
}

$self->appは次のenableしたMiddlewareオブジェクトである.
$self->app->($env)で次のMiddlewareのcallを実行する.
$envがどんどん次のMiddlewareに引き渡されていくイメージ.

call以外にprepare_appというメソッドにより,サーバ起動時に一度だけ実行される前処理を書ける. リクエストのたびに実行させたくないような処理はここで実行させて結果を$selfに突っ込んでおくとcallが呼ばれたときに使いまわせる.

package Plack::Middleware::MyMy;
use strict;
use warnings;

use parent 'Plack::Middleware';

sub prepare_app {
    my $self = shift;
    $self->{foo} = Foo->new;
}

sub call {
    my ($self, $env) = @_;
    $self->{foo}; #=> "Fooオブジェクト"
    return $self->app->($env); # 次にenableした
}

.psgiファイル内でenableするときに第2引数以降にMiddlewareへ渡すパラメータを指定できる. 以下ではstateとstoreキーにそれぞれオブジェクトをセットしている.

    enable "Plack::Middleware::Session",
        state => Plack::Session::State::Cookie->new(
            session_key => 's',
            expires => undef,
        ),
        store => Plack::Session::Store::File->new(
            dir          => config->root->subdir('session'),
            serializer   => sub { $MessagePack->pack(+shift) },
            deserializer => sub { eval { $MessagePack->unpack(+shift) } || +{} },
        );

これらのパラメータはそのままMiddlewareオブジェクトのメンバとなる. Middlewareモジュール内でアクセッサを定義し,アクセッサ経由でパラメータを参照してることが多い.

package Plack::Middleware::Session;
use strict;
use warnings;

use parent 'Plack::Middleware';

use Plack::Util::Accessor qw(state store);

sub call {
    my ($self, $env) = @_;
    $self->state;
}
...

基本的にcallだけ書けばよい感じなので,わかりやすいけど,返り値を何にすればよいとか$self->appとは何かを気にしだすとソースを読まなければわからない感じだった. Middlewareの仕組み自体はそんなに難しくないと思うけど,subrefをネストして生成して,遅延実行していく感じのコードなのでちゃんと実行経路を辿らないと??って感じだった.

$self->app->($env)が一番謎で,次にenableされたMiddlewareオブジェクトのcallを実行するということがわかったのが収穫だった.

※ ソース読んでいっただけなので,何か間違いがあるかもしれない

Plack::Middleware::OAuth::Lite的なものを書いてる

TwitterとかGitHubユーザに対して自前のWebアプリケーションでOAuth認証するためのPlack::Middlewareを書いてる.

同様の動作をするモジュールとしてすでに Plack::Middleware::OAuth があったけど,Session周りでよくわからないエラーがでてよくわからなかったから,自分で書き始めた.

(よくわからないエラーというのは,$env->{psgix.session}と$env->{psgix.session.options}がundefになってて,Plack::Session->new($env)したときに,これらがHASHリファレンスじゃないと怒られる感じだった.

結局,Middlewareの読み込み順が間違ってて,Plack::Middleware::Sessionを先に書いてないだけだった.
Plack::Middleware::OAuthはPlack::Middleware::Sessionまたは他のSessionストレージの使用が前提になっている気がする.

builder {
   
    enable "Plack::Middleware::Session", # 先に書く
        ...

    enable 'Plack::Middleware::OAuth',
       ...

    $app
};

)

Plack::Middleware::OAuth::Liteは,各プロバイダのエンドポイントなどの設定を内部に持っていなくて,enable時に設定を書くようになっている.

あと,OAuth2.0には対応してない. OAuth2.0はシグネチャのためのハッシュ値計算みたいなのをやらなくて良いから外部モジュール使わずに手で書けばいい気がする.

OAuth認証まわりはOAuth::Lite::Consumerを使ってる.

書いてみたはいいけど,Plack::Middleware::OAuthのほうがあきらかによいインタフェースだということがわかってよかった. ただ,Plack::Middleware::OAuthのコールバックルーチンに渡される第一引数$selfにはredirectとかrenderとかが生えていて,これらは自前で実装してるようなので,この辺うまくPlack::Requestを使えなかったんだろうか.

Plack::Middleware::OAuthは内部でいろいろよしなにやってくれる感じで自由度が低めに感じたので,外からいろいろ設定を渡せるようにしたけど,なんだか微妙な感じになった感がある.

Plack::Middleware::OAtuhを使いましょう.

psgiファイルの設定

    enable "Plack::Middleware::Session";

    enable 'Plack::Middleware::OAuth::Lite',
        on_success => sub {
            my ($res, $provider_name, $token, $user_info, $location) = @_;
            $res->redirect("/auth/signup/$provider_name");
            return $res;
        },
        on_error => sub {
            my ($res, $error) = @_;
            $res->redirect("/oauth/error?reason=".$error);
            return $res;
        },
        providers => {
            'twitter' => {
                consumer_key       => 'XXXXXX',
                consumer_secret    => XXXXXX',
                site               => 'http://api.twitter.com/',
          request_token_path => 'https://api.twitter.com/oauth/request_token',
                access_token_path  => 'https://api.twitter.com/oauth/access_token',
          authorize_path     => 'https://api.twitter.com/oauth/authorize',
                login_path         => '/auth/twitter',
                scope              => '',
                user_info_uri      => qq{https://api.twitter.com/1.1/account/verify_credentials.json},
                user_info_uri_method => 'GET',
            },
            hatena => {
                consumer_key       => 'XXXXXX',
                consumer_secret    => 'XXXXXX',
                site                  => qq{https://www.hatena.com},
                request_token_path => qq{https://www.hatena.com/oauth/initiate},
                access_token_path  => qq{https://www.hatena.com/oauth/token},
                authorize_path     => qq{https://www.hatena.ne.jp/oauth/authorize},
                login_path         => '/auth/hatena',
                scope              => 'read_public',
                user_info_uri      => qq{http://n.hatena.com/applications/my.json},
                user_info_uri_method => 'POST',
        },

        };
package Plack::Middleware::OAuth::Lite;
use utf8;
use strict;
use warnings;

our $VERSION = '0.01';

use parent 'Plack::Middleware';
use Plack::Util;
use Plack::Util::Accessor qw(providers on_success on_error flash_success_message);
use Plack::Request;
use Plack::Session;

use Carp ();
use JSON::XS qw(decode_json);
use OAuth::Lite::Consumer;
use Try::Tiny;

our $CONFIG_KEYS = [qw(
    consumer_key
    consumer_secret
    site
    request_token_path
    access_token_path
    authorize_path
    login_path
    scope
    user_info_uri
)];

sub prepare_app {
    my ($self) = shift;
    my $p = $self->providers || Carp::croak "require providers";

    $self->{client_info}     ||= {}; # {provider_name => 'OAuth::Lire::Consumer instance'}
    $self->{login_path_info} ||= {}; # {provider_name => 'path'}
    $self->{scope_info}      ||= {}; # {provider_name => 'read,write'}
    $self->{user_info}       ||= {}; # {provider_name => {uri => 'user_info', method => 'HTTP Method', user_name_key => 'key name'}
    for my $provider_name (keys %$p) {
        my $config = $p->{$provider_name};
        check_config($config);

        my $lc_name = lc($provider_name);
        $self->{client_info}{$lc_name} = OAuth::Lite::Consumer->new(
            consumer_key       => $config->{consumer_key},
            consumer_secret    => $config->{consumer_secret},
            site               => $config->{site},
            request_token_path => $config->{request_token_path},
            access_token_path  => $config->{access_token_path},
            authorize_path     => $config->{authorize_path},
        );
        $self->{login_path_info}{$lc_name} = $config->{login_path};
        $self->{scope_info}{$lc_name} = $config->{scope};
        $self->{user_info}{$lc_name} = {
            uri      => $config->{user_info_uri},
            method   => $config->{user_info_uri_method} || 'GET',
        };
    }
}

sub check_config {
    my ($config) = @_;
    for my $expected (@$CONFIG_KEYS) {
        unless (grep {$expected eq $_} keys %$config) {
            Carp::croak "require $expected";
        }
    }
}

sub call {
    my ($self, $env) = @_;

    my $session = Plack::Session->new($env);

    $self->{handlers} //= do {
        my $handlers = {};
        while (my ($provider_name, $login_path) = each %{$self->{login_path_info}}) {
            $login_path =~ s!(.+)/$!$1!; # add
            my $callback_path = "$login_path/callback";
            my $consumer = $self->{client_info}{$provider_name};

            $handlers->{$login_path} = sub {
                my ($env) = @_;
                my $req = Plack::Request->new($env);
                my $res = $req->new_response(200);
                my $request_token = $consumer->get_request_token(
                    callback_url => _callback_uri($req->base, $callback_path),
                    scope        => $self->{scope_info}->{$provider_name} || undef,
                ) or die $consumer->errstr;

                $session->set($provider_name.'oauth_request_token' => {%$request_token});
                $session->set($provider_name.'oauth_location'      => $req->param('location'));
                $res->redirect($consumer->url_to_authorize(token => $request_token));

                return $res->finalize;
            };

            $handlers->{$callback_path} = sub {
                my ($env) = @_;
                my $req = Plack::Request->new($env);
                my $res = $req->new_response(200);

                if ($req->param('denied')) {
                    return $res->redirect('/');
                }

                my $verifier = $req->param('oauth_verifier')
                    || die "No oauth verifier";

                my $access_token = $consumer->get_access_token(
                    token    => (bless $session->get($provider_name.'oauth_request_token'), 'OAuth::Lite::Token'),
                    verifier => $verifier,
                ) or die $consumer->errstr;

                $session->remove($provider_name.'oauth_request_token');
                $session->set($provider_name.'oauth_access_token', {%$access_token});

                {
                    my $u = $self->{user_info}{$provider_name};
                    my $u_res = $consumer->request(
                        method => $u->{method}, url => $u->{uri}, token  => $access_token,
                    );
                    $u_res->is_success or die "failed getting user info";

                    my $user_info = eval { decode_json($u_res->decoded_content || $res->content) };
                    $session->set($provider_name.'oauth_user_info', $user_info);

                    my $location = $session->get($provider_name.'oauth_location') || "/";
                    $res = $self->on_success->($res, $provider_name, $access_token, $user_info, {
                        location => $location,
                    });
                }
                return $res->finalize;
            };
        }
        $handlers;
    };

    return $self->_run($env, $self->{handlers});
}

sub _run {
    my ($self, $env, $handlers) = @_;

    my $app = $handlers->{$env->{PATH_INFO}};
    return $self->app->($env) unless $app;

    my $res;
    try {
      $res = $app->($env);
    } catch {
        my $req = Plack::Request->new($env);
        my $res = $req->new_response(200);
        $_ =~ /(.*)\sat/;
        $res = $self->on_error->($res, $1);
        return $res->finalize;
    }
    $res;
}

sub _callback_uri {
    my ($base_uri, $callback_path) = @_;
    $callback_path =~ s!^/?(.+)!$1!; # remove head '/'
    $base_uri . $callback_path;
}

1;
__END__

Perl屋さんに便利なVim Pluginを2つ書いた

この記事は Vim Advent Calendar 2012 の22日目の記事です.

21日目は @AmaiSaeta さんの 「このVim plugin達に感謝しなければ年を越せない!私が今年使い倒した2012年のベストを全部ご紹介! 」 でした.



最近,Vim Scriptを書き始めていて, 今回は,Perl的に便利なVim Pluginを2つ書いたのでご紹介します.

unite-perl-module.vim

GitHub y-uuki/unite-perl-module.vim

unite-perl-module.vim はClass::Acceccor::Liteなどのモジュール名をUniteのインタフェースで検索し,選択したモジュール名をカーソル位置に書き込むPluginです.

Linda_pp先生のunite-ruby-require.vimを参考にして作りました.

機能は以下の2つです.

  • perl/global: 標準モジュールおよびcpanmなどでインストールしたモジュールの検索
  • perl/loccal: プロジェクト内のモジュールおよびcartonやlocal::libでインストールしたモジュールの検索

インストール

unite.vimをインストールしてください.

NeoBundleなどで本プラグインをインストールしてください.

NeoBundle "y-uuki/unite-perl-module.vim"

使い方

:Unite perl/global

f:id:y_uuki:20121222212104p:plain

:Unite perl/local

f:id:y_uuki:20121222212057p:plain


注意事項

VimのカレントパスがHOMEなどのディレクトリ以下に大量のファイルがあるところでは,perl/globalの実行時間が遅くなってしまう問題があります. これは,perl/global内で使用している cpan -l コマンドが,カレントディレクトリ以下のモジュールを再帰的に探しに行ってしまっているためだと思います.

perl-local-lib-path.vim

GitHub y-uuki/perl-local-lib-path.vim

プロジェクト内のモジュールやcartonでインストールしたモジュールにはVimのpathが通っていないので,gfでファイル間移動ができないという問題があります.

perl-local-lib-pathは,現在のプロジェクト内のモジュールやcartonでインストールしたモジュールに自動でpathを通します.


インストール

依存するプラグインは特にありません. NeoBundleなどで本プラグインをインストールしてください.

NeoBundle "y-uuki/perl-local-lib-path.vim"

使い方

vimrcに以下のような設定を書きます.

g:perl_local_lib_path = "vendor/lib"    "" 任意
autocmd FileType perl PerlLocalLibPath

g:perl_local_lib_path にはプロジェクトルート・ディレクトリ(.gitがあるディレクトリなど)からのモジュールディレクトリへの相対パスを指定することができます. 何も指定しなくても,デフォルトで'lib','local/lib/perl5'と'extlib'をモジュールディレクトリとして判定します.

これで,project-root/local/lib/perl5/Plack/Request.pmのようなcartonで管理されたモジュールにもジャンプできるようになります.

参考

紹介した2つのプラグインではどちらも自動でプロジェクトルートの判定を行なっています. プロジェクトルートと同じディレクトリに".git", ".gitmodules","Makefile.PL","Build.PL"があればプロジェクトルートとして判定しています. このあたりは,id:antipopさんのProject::Libsを参考にしました.

carton周りのVimの話は@taka84u9 さんの

を参考にしました.

明日の担当は @mfumi2さんです.
よろしくお願いします.

Perlにおける括弧なし関数とその引数と||の結合強度

Perlで何気なく

use File::Which qw(which);

my $cmd = which "foobar" || croak "FAIELD";

とか書いてたら,which "foobar"の返り値はundefなのにも関わらず,croakに引っかからなかった. あれ?とか思ってたら単に,括弧なし関数の右からみた結合強度よりも||のほうが強かっただけだった. つまり,まず"foobar"を見て評価値が真なのでその時点でcroak文は評価されないことになる.

以下優先順位の降順

左結合 || //
非結合 .. ...
右結合 ?:
右結合 = += -= *= などの代入演算子
左結合 , =>
非結合 リスト演算子 (右方向に対して)
右結合 not
左結合 and
左結合 or xor
perlop - Perl の演算子と優先順位 - perldoc.jp

ここで引数"foobar"からみたwhichは,リスト演算子(右方向に対して)扱いになる. したがって,||の代わりにリスト演算子よりも結合が弱いorを使うか,もしくはwhich("foobar")のように括弧をつければよい.

何も特別なことはないけれど,PerlやRubyでぼんやり書いてしまいそうなので気をつけたい.