2010年7月アーカイブ

以前「StarmanやStarletでmod_statusっぽい情報を得る簡易版Plack::Middleware::ServerStatus」で書いていたPlack::Middleware::ServerStatus::LIteをCPANにリリースしました。

http://search.cpan.org/dist/Plack-Middleware-ServerStatus-LIte/

前回と大きく実装が異なっている箇所があります。1つはプロセスの状態を保持するためにParallel::Scoreboardを使うようになり、$0(PROGRAM_NAME)の書き換えを行わないようになったこと。2つ目はロケタッチを開発しているhideokiさんからの指摘で、アプリケーションが例外を起こしたときにステータスが戻らないという問題への対応をTry::Tinyを使って行っていることが大きな変更点です。

依存は増えましたが、実装は以下のようにシンプル

sub call {
    my ($self, $env) = @_;
    $self->set_state("A", $env);
    try {
        if( $self->path && $env->{PATH_INFO} eq $self->path ) {
            $self->_handle_server_status($env);
        }
        else {
            $self->app->($env);
        }
    } catch {
        die $_;
    } finally {
        $self->set_state("_");
    };
}

ただし、これだけだと一度もリクエストをさばいていないプロセスはステータス情報が全くなく、Parallel::Scoreboard->read_all()では取得できないので、子プロセスのPID収集のためにpsコマンドだけはそのまま使っています

psコマンドのオプションに方言があったり、Windowsで動かないとか問題はあると思いますので、パッチお待ちしています。

ServerStatus::Liteによるステータス情報は以下のようにとれます

 % curl http://server:port/server-status
 Uptime: 1234567789
 BusyWorkers: 2
 IdleWorkers: 3
 --
 pid status remote_addr host method uri protocol
 20060 A 127.0.0.1 localhost:10001 GET / HTTP/1.1
 20061 .
 20062 A 127.0.0.1 localhost:10001 GET /server-status HTTP/1.1
 20063 .
 20064 .
 

CloudForecastでは、 Apacheで使うHTTPのリソース定義をそのまま使うことができますょ。

Yokohama.pm#6 x Perl Casual#3でcloudforecastについて時間を頂いたので喋ってきました。 すてきな会場を貸して頂いたネイバージャパン株式会社さん、USTREAMを担当してくれた941さんありがとうございます。

発表資料はこちら。

もうすこし内容がんばって、リソース監視の面白さを伝えられるようにしたい。

レスポンスヘッダにExpiresやCache-Controlを追加することで、ブラウザのキャッシュを有効活用し、ダウンロードの時間をなくす事でウェブの高速化を実現できます。またサーバ側にとってもリクエスト数を減らす事ができ、負荷の削減にもなります

ApacheにはExpiresやCache-Controlを付加するmod_expiresというモジュールがありますが、Plackにはまだなかったので作ってみました。VarnishのようにWebサーバ機能を持たないリバースプロキシを使う場合には、便利なんじゃないかなぁと思います

CPANにリリース済みです http://search.cpan.org/dist/Plack-Middleware-Expires/

使い方

builder {
    enable 'Expires',
        content_type => [ 'text/css', 'application/javascript', qr!^image/! ],
        expires => 'access plus 3 months';
    enable 'Plack::Middleware::Static',
        path => qr{^/(favicon\.ico$|static/)}, root => './htdocs';
    $app;
}

content_typeでヘッダを付加するMIME typeを指定します。 pathを限定するためにPlack::Builder::Conditionals(github)と一緒に組み合わせたら便利かも

content_type => 'text/css'
content_type => qr!^image!
content_type => [ 'text/css', 'application/javascript', qr!^image/! ]

スカラ値、Regexp、もしくはそれらが入る配列のリファレンスを指定できます

expiresには、Apacheのmod_expiresと同じフォーマットでキャッシュの有効時間を入れます

expires => 'M3600' #最終更新時間 + 1時間(3600秒)
expires => 'A86400' # アクセス時間 + 1日
expires => 'modification plus 3 years 3 month 3 day' #最終更新時間 + 3年3ヶ月3日
expires => 'access plus 3 days' #アクセス時間 + 3日

などと書けます。最終更新時間はLast-Modifiedヘッダからとるため、Last-Modifiedヘッダが存在しないときには機能が働きません。

また、Expiresが有効となるのは、レスポンスのステータスが200系や300系の時だけで、エラー時には動きません。また既にあるExpiresヘッダを上書きしません。

CloudForecastや先日のNoPasteではPlack::Builderを使って、デフォルトの機能としてPlack::Middleware::ReverseProxyを有効にしている。

my @frontproxy = map { s/\s//g } split(/,/, $ENV{FRONT_PROXY} || "");
foreach my $ip ( @frontproxy ) {
    my $netip = Net::IP->new($ip) or die;
    push @frontproxies, $netip;
}
buider {
    enable_if {
        my $addr = $_[0]->{REMOTE_ADDR};
        my $netip;
        if ( defined $addr && ($netip = Net::IP->new($addr)) ) {
            for my $proxy ( @frontproxies ) {
                my $overlaps = $proxy->overlaps($netip);
                return 1 if ( $overlaps == $IP_B_IN_A_OVERLAP || $overlaps == $IP_IDENTICAL );
            }
        }
        return;
    } "Plack::Middleware::ReverseProxy";
    $app;
}

このように環境変数FRONTPROXYにReverse ProxyのIP帯域(アドレス)をいれ、Plack::Builderのenableifを使って、REMOTE_ADDRがその帯域にあれば、Middlewareを有効にするような方法をとっている。

このようなenable_ifはなんども書きそうなので、モジュールにしておいた方がいいんじゃないかと思い、書いてみた。

github: http://github.com/kazeburo/Plack-Builder-Conditionals

使い方は

use Plack::Builder;
use Plack::Builder::Conditionals;
# exports "match_if, addr, path, method, header, any, all"

builder {
    enable match_if addr(['192.168.0.0/24','127.0.0.1']),
        "Plack::Middleware::ReverseProxy";
    $app
 }

な、感じ。コードの見通しが良くなりました。

Plack::Builder::Conditionalsを利用するには、enableのあとに、matchif をおきます。matchifは内部的にPlack::Middleware::Conditionalを使っていてあとに続く条件によってMiddlewareを有効にするかの判断をします。

条件でとして、REMOTEADDRの検証を行う「addr」、PATHINFOをみる「path」、REQUEST_METHODの「method」、そして任意のヘッダを確認するための「header」のメソッドが用意されています。これに加えて複合的な条件の実現するための「all」と「any」もあります。

# REMOTE_ADDRの帯域を調べる
addr([qw!192.168.0.0/24 127.0.0.1!]);

# PATH_INFO
path('/')
path(qr!^/(\w+)/!)

#REQUEST_METHOD
method('GET')
method(qr!/^(get|head)$/!)

# 任意のヘッダ
header('User-Agent',qr/iphone/i)
header('If-Modified-Since') #ヘッダがあれば真

# REQUEST_METHODがGETかつ、PATH_INFOが/static以下
all( method('GET'), path(qr!^/static!) )

# PATH_INFOが/static以下もしくは、/favicon.ico
any( path(qr!^/static!), path('/favicon.ico') )

このように任意の条件を組み合わせても使えます。また、否定の条件の場合は

addr('!','127.0.0.1'); #localhostではない
path('!', qr!^/private($|/)!) #/private以下ではない
method('!','GET')
header('!', 'User-Agent',qr/MSIE/)

最初の引数として、「!」を渡します。

非常に簡単なメソッド名を利用しているため、アプリケーションによっては重なってしまうことも考えられます。その際はimport時に メソッド名にprefixを追加することができます。

use Plack::Builder::Conditionals -prefx => 'c'; #prefixに「c」追加
# exports "c_match_if, c_addr, c_path, c_method, c_header, c_any, c_all"

乗り物好きな息子に、トミカのバスを買ってみた。

R0013211.jpg

「三菱ふそうエアロスター 路線バス」、どうみても都営バス。ちゃんと東京都交通局許諾済みとパッケージに書いてあります。電車ほどはずっと握りしめたままではないけど、気にってくれたみたい。

ヨドバシカメラで260円だった。しかし安いな

CloudForecastでリソース監視をする際の情報取得方法、グラフの設定を行うモジュールの作り方の紹介です。前のエントリーで紹介したgearman-starter.plのスコアボードによるステータス情報を取得してグラフにしてみます。

まず、gearman-starter.plのステータスを再確認します。telnetでみるとこんな感じです。

% telnet localhost 7005
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
System: gearman_servers: 127.0.0.1:7004 class: MyWorker
Uptime: 20
BusyWorkers: 0
IdleWorkers: 10
--
pid       Status Counter Comment
1630           . 0
1631           . 0
1632           . 0
1633           . 0
1634           . 0
1635           . 0
1636           . 0
1637           . 0
1638           . 0
1639           . 0


前回より若干情報が増えてます。このうち、BusyWorkersとIdleWorkersの値がグラフにできそうです。BusyとIdleの組み合わせは、Apacheの監視と同じです。Apacheのグラフは以下のイメージ。

apache-graph.png

gearman-starterのグラフはstarterの機能に(max|min)spareworkersがないので上限値の移動はしませんが、これとほぼ同じになります。次に、モジュールの作成です。

cloudforecastに独自にリソース定義モジュールを追加する時は、site-libディレクトリを作成し、その中にファイルを入れるのがおすすめです。各種のscriptで既にライブラリパスに追加されてたり、ファイル更新を検知しての自動再起動モードの対象にも入っています。

% cd path/to/cloudforecast
% mkdir -p site-lib/CloudForecast/Data


このディレクトリの中にモジュールを入れます。名前はGearmanstarter.pmとします。ソース全文はgistにあげました。

package CloudForecast::Data::Gearmanstarter;

use CloudForecast::Data -base;
use IO::Socket::INET;


パッケージ宣言をし、CloudForecast::Dataを「-base」引数付きで読み込みます。

rrds map { [$_,'GAUGE'] } qw/busy idle/;


グラフやリソース取得の設定はDSLっぽく書いていきます。まず「rrds」ではデータを保存していくRRDtoolのスキーマを設定します。

rrds ['名前','データソースタイプ'], [], [], ..;
rrds '名前', 'データソースタイプ';


どちらのフォーマットでも記述可能です。複数のステータスを保存する場合には、rrdsを複数回書くこともできます。「データソースタイプ」はいくつかの種類があります。詳しくはITmediaの記事RRDtoolsの公式のドキュメントが参考になります。よく利用するタイプとして、

  • GAUGE - その瞬間の値。グラフではそのままの値が利用される
  • COUNTER - カウンターのように時刻が進むと増え続けていく値。グラフでは差分をとる

この2種類を覚えておけば大丈夫です。一度作成されたスキーマは変更できないので注意してください

graphs 'status' => 'worker status';


これは、グラフの設定です。グラフを区別するためのキーワードやラベルです。

graphs 'key', 'label';
graphs 'foo', 'foo usage';


一つのリソース定義で複数のグラフを作成する事もできます。この場合は複数回graphsを書きます。

いよいよ、データを取得する部分。

fetcher {
    my $c = shift;

    my $host = $c->address;
    my $port = $c->args->[0] || 9000;

    my $sock = IO::Socket::INET->new(
        PeerAddr => $host,
        PeerPort => $port,
        Proto    => 'tcp',
        Timeout  => 5,
    );

    die "could not connecet to $host:$port" unless $sock;
    my $raw_stats;
    $sock->sysread( $raw_stats, 8192 );

    my $busy = -1;
    my $idle = -1;
    foreach my $line ( split /[\r\n]+/, $raw_stats ) {
        if ( $line =~ /^Busy.+: (\d+)/ ) {
            $busy = $1;
        }
        if ( $line =~ /^Idle.+: (\d+)/ ) {
            $idle = $1;
        }
    }
    return [$busy,$idle];
};


普通のperlのコードで書けます。

fetcher sub { };


IO::Socketでgearman-starterの管理ポートに接続をし、ステータスをパースしています。最後に、取得したデータを、rrdsに登録した順序で配列のリファレンスで返します。$cはCloudForecast::Data::Gearmanstarterのオプジェクトで、以下のメソッドも利用できます。

  • $c->hostname - サーバ一覧で設定したサーバのホスト名
  • $c->address - サーバ一覧で設定したIPアドレス
  • $c->detail - サーバ一覧で設定したコメント
  • $c->args->[0,,,] - リソース定義のオプション。配列のリファレンス
  • $c->component() - 共通で使えるデータ取得ライブラリ

argsは、監視項目設定の値です。監視項目で

- modue_name:option1:option2:..


と書いた場合にコロンで区切った値が配列として入ってきます。Gearmanstarterでは、管理ポートの番号を渡す事にします。

- gearmanstarter:9001


最後にグラフの設定です。DATA以下に@@ graphs_keyで区切って書いていきます。

@@ status
DEF:my1=<%RRD%>:busy:AVERAGE
DEF:my2=<%RRD%>:idle:AVERAGE
AREA:my1#00C000:Busy  
GPRINT:my1:LAST:Cur\: %4.1lf
GPRINT:my1:AVERAGE:Ave\: %4.1lf
GPRINT:my1:MAX:Max\: %4.1lf
GPRINT:my1:MIN:Min\: %4.1lf\c
STACK:my2#0000C0:Idle  
GPRINT:my2:LAST:Cur\: %4.1lf
GPRINT:my2:AVERAGE:Ave\: %4.1lf
GPRINT:my2:MAX:Max\: %4.1lf
GPRINT:my2:MIN:Min\: %4.1lf\c


これはApacheの定義モジュールと全く同じです。<%RRD%>と書かれた部分がrrdtoolのデータサイズファイルのパスに自動で置き換わります。グラフの細かいオプションはほかのリソース定義やドキュメントを参考にしてください

これでモジュールが完成しました。あとはCloudForecastの監視項目のカスタマイズ方法で書きました、監視項目設定ファイルに追加すればすぐに使えます。

リソース監視定義モジュールでは、ここで説明したデータの取得とグラフ化だけではなく、システムの情報を取得、保存し、Web画面に表示するカスタマイズも可能です。これは別途解説します。

あとは、テストをどうやって書くか悩み中

CloudForecastっていうリソース監視のツール/フレームワーク作ったにおいて、CloudForecastのインストールとCPUやメモリー、トラフィックなど基本的な監視ができるところまで記事にしましたが、今回はその監視項目のカスタマイズの方法を紹介します。

前回記事の最後では、監視対象となるサーバはlocalhostだけが登録されている状態です。server_list.yamlは以下のようになっています。

--- #HOME
servers:
  - config: basic.yaml
    hosts:
      - 127.0.0.1 server1 my great server

configで指定しているが監視項目の設定ファイルです。cloudforecast.yamlのhost_config_dirで指定されたディレクトリ内に保存されています。監視項目のカスタマイズにあたり、新規にhost_config_dirを作成します。

% cd path/to/cloudforecast
$ mkdir my_host_config

ここにbasic.yamlをコピーします。ついでに名前も変えます。

$ cp host_config/basic.yaml my_host_config/localhost.yaml

設定ファイルの名前は自由に決められますが、あとでわかるものがいいでしょう。そして、cloudforecast.yamlのhost_config_dirと、server_list.yamlのconfigを変更します。

---
config:
  gearman_enable: 0
  (略)
  host_config_dir: my_host_config @ココ@
component_config:
  (略)


--- #HOME
servers:
  - config: localhost.yaml @ココ@
    hosts:
      - 127.0.0.1 server1 my great server

これで準備が整ったので、localhost.yaml を変更します。

localhostでは、Apacheが80ポートで起動して、memcachedを11212ポートで利用しているので、これらを追加します。

 ---
 component_config:
 resources:
   - traffic:eth0
   - basic
   - http:80:/server-status?auto @ポート80のApache@
   - memcached:11212 @ポート11212のmemcached@

resourcesのところに追加して行きます。http:80とmemcached:11212が足した監視項目です。

 - modue_name:option1:option2:..

module_nameは一文字目を大文字にして、CloudForecast::Data以下のリソース定義モジュールが読み込まれます。また「:」で区切ったオプション部分もモジュールに渡されます。Apacheの監視ではCloudForecast::Data::Httpがよばれ、「80」と「/server-status?auto」が渡されます。どのようにオプションが使われるのかは、リソース定義モジュールによって変わります。Httpモジュールでは1つ目がポート、2つ目がserver-stautsを取得するURLとなります。ドキュメントはまだ整備中^^

蛇足ですがserver-stautsはApacheであれば

<Location /server-status>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from localhost
</Location>

のように設定できます。

CloudForecastにはHttpやmemcachedの他にも、標準でDisk使用量、DiskIO数、MySQL、MySQL InnoDB、Nignx、Squidなどのモジュールが含まれています。また新たにリソース監視定義モジュールを作成し追加することもできます。

tokuhiromのgearman の worker process にスコアボードをつけてみる (git) のスコアボードをネットワーク越しに確認できるようにしてみた。

git: http://github.com/kazeburo/gearman-starter.pl


具体的にはworkerのloopに入る前に指定したportをlistenするプロセスをforkして、そいつにアクセスすることで、ステータス(スコアボード)を取得できる。

ステータスを表示するために—port と —listen の2つのオプションを追加した。起動は

% perl -Ieg/lib ./gearman-starter.pl -s 127.0.0.1:7004 --scoreboard-dir /tmp/test --listen localhost --port 7005 MyWorker

な感じ。telnetでport 7005にアクセスすると、すぐにステータスが返ってくる

起動直後

% telnet localhost 7005
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
BusyWorkers: 0
IdleWorkers: 10
--
pid       Status Counter Comment
15785          . 0
15786          . 0
15787          . 0
15788          . 0
15789          . 0
15790          . 0
15791          . 0
15792          . 0
15793          . 0
15794          . 0

Connection closed by foreign host.

clientを動かすと、

BusyWorkers: 1
IdleWorkers: 9
--
pid       Status Counter Comment
15785          _ 2
15786          _ 4
15787          . 0
15788          . 0
15789          . 0
15790          A 1 127.0.0.1:7004//H:vs1-1:94425
15791          . 0
15792          . 0
15793          . 0
15794          . 0

のように変化する。pidが「15790」がActive(Busy)になってるのがわかる。

これで監視ができるゥ。

あと、Gearman::Worker->workは内部で無限ループする。ループを止めるにはstop_ifのcallbackを渡す必要があるので注意

社内にNoPaste的なものがなくてカッとなって作っていたらsinatraライクなフレームワークを作っていた。何を言っているか(ry

nonopaste.png

NoNoPasteソースコード: http://github.com/kazeburo/NoNoPaste

元々、CloudForecastには、tokuhiromのMojaMojaやyusukebeのHitagiからコピペをしつつ作ったフレームワークがあり、NoPaste的なものを作成するにあたりCloudForecastからWAF部分だけを切り出して作り直した。

今回のWAFのコードはまだNoPasteのパッケージ内にある。名前はShirahata。

Shirataha.pm: http://github.com/kazeburo/NoNoPaste/blob/master/lib/Shirahata.pm

特徴は

  1. sinatraライクなURLの組み立て
  2. typesterさんのText::MicroTemplate::DataSectionExを利用してDATAにテンプレートを書きつつ、継承などをサポート
  3. 組み込みfillinformサポート
  4. Modelはナシ

あとはCatalyst風な”$c”も特徴かな。利用イメージとしてはCloudForecastのWeb画面のようなちょっとした管理ページ系かな

以下は簡単なサンプル

package MyApp;

use Shirahata -base;

get '/' => sub {
    my ( $self, $c )  = @_;
    # $c->req, $c->res, $c->stash, $c->args
    $c->render('index')
}

__DATA__
@@ base.mt
<html>
<head>
<title>My App</title>
</head>
<body>
<h1><a href="<?= $c->req->uri_for('/') ?>">MyApp</a></h1>
? block content => sub { }
</body>
</html>

@@ index.mt
? extends 'base'
? block content => sub {
<h2>Content</h2>
? fillinform( $c->req, sub {
<form method="post" action="/submit" id="submitf">
<label for="nick">nick</label>
<input type="text" id="nick" name="nick" value="" size="21" />
<input type="submit" id="submitb" value="POST" />
</form>
? })
? } #block content

呼び出すためのpsgi。

use strict;
use warnings;
use Cwd qw/realpath/;
use File::Basename qw/dirname/;
use MyApp;

my $root_dir = dirname( realpath(__FILE__) );
my $web = MyApp->new($root_dir);
$web->psgi;



Xslateも試してみていて、DATAをtempdirに書き出して、Xslateに読み込ませるとか考えたんだけど、一時ディレクトリの掃除が大変なのでやめた。DataSectionのXslate版はgfx曰く「絶賛考え中です!」なので楽しみにしている

CloudForecastにbackportするかどうか考え中

1ヶ月前ほどにBlogに書いたサーバリソースの監視ツール CloudForecastは弊社でも導入前提で試験をしつつ、さまざまなアップデートを行っているのでその紹介です

大きく変わったのはWeb画面の見た目です。

サーバ一覧

sample-list2.png

各サーバのグラフページ

sample-server2.png

多くのサーバを監視するにあたり、通常は見ないサーバに関してはグループ名をクリックすることで畳んでしまうことができます。見たくないサーバはこれで桶。また、各サーバのグラフページでは、デフォルトでMonthlyとYearlyのグラフの表示をやめています。通常ブラウザの画面には入りきらないと思いますし、参照することも24時間、1週間のグラフに比べて少ないだろうという判断です。表示速度も高速化される期待もあります。月間年間のグラフを参照したい時はワンクリックで表示できるので不便はないと思います

加えて、日時指定でグラフを表示することができます。PickerにはAny+TIme DatePicker/Timer PickerというJavaScriptライブラリを利用し、障害が起きたり負荷の高まった時間帯だけを切り出することも可能です。ただし、日付の古いデータに関してはRRDToolで数値が丸められていくのでどれだけ意味のあるグラフになるのかは疑問があったりします。

システム情報

もう一つ付け加えた機能として、リソース情報の取得時にシステムの設定情報などを抜き出して保存し、Web画面に表示することができるようになりました。上の例で行けば、MySQLのグラフにMySQLのthreadcacheやmaxconnectionがどのような値に設定されているかを一緒に表示しています。InnoDBではbufferpoolやflushmethodなどを表示するため、チューニングのガイドとしても役に立つかもしれません。

このシステム情報を取得、保存、表示するために、SQLiteに依存するようになりました。RRDToolとYAMLだけのNoSQLシステムから、SQLも利用するようになりました

今後の展開とか

社内でも利用し始めていることもあり、まだまだ改善、変更していくと思われます。今は100台程度のサーバを監視していますが、数百台となった場合や、このWeb画面を参照する開発者が増えていく中でも利用しやすいように工夫していきます。ただし、設定ファイルのフォーマットやrrdtoolの定義に関しては変更を行わないようにします。今までに貯めたリソースデータが無駄にならないことが必須条件だと考えています。この部分では安心してCloudForecastを利用して頂けると良いかと思います。 その上で今後の展開ですが、一覧ページの使い勝手の向上、メモ機能、ドキュメントとかドキュメントとかドキュメントを充実して行きたいところです

最近、弊社でもいくつかのサービスでStarmanが動き始めてます。リソース監視厨としてStarmanStarletといったPreforkなPlackサーバにおいてもApacheのmod_status同様、使用されているプロセス数、アイドル中のプロセス数を当然知りたいわけです。CloudForecastでグラフにしたいわけです。

すでにcho45氏がその機能を実現しています。cho45++です。ただ、ステータス表示を行うMiddlewareの他にステータス情報の変更を行うためにStarmanやStarletの本体に手を入れており、若干使いにくいという印象を持っていました。そこでMiddlewareだけで、Middlewareのできる範囲でステータスを変更・表示するPlack::Middleware::ServerStatus::Liteを書いてみました。ソースコードはgithubにpushしてあります。

http://github.com/kazeburo/Plack-Middleware-ServerStatus-Lite

仕組みは簡単です。cho45氏の実装をまねて $0 ($PROGRAM_NAME)を変更し、それをpsコマンド経由で収集し、表示します。

$0を変更する実装は以下の様な感じ

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

    $0 = sprintf("server-status-lite[%s] %s",getppid, "A"); #Active

    $res = $self->app->($env);

    $0 = sprintf("server-status-lite[%s] %s",getppid, "_"); #Idle

    return $res;
}

このようにappを実行する前後でステータスをActive(Busy) or Idleで変更しているだけです。Apacheのmod_statusやcho45氏のhackではリクエストの読み込み、書き込み、KeepAliveなどの様々なステータスがとれるのに対してServerStatus::Liteでは2種類の状態しかとることができません。ただ、実際のStarmanやStarletのProduction環境では、リバースプロキシーを全面に設置するため読み込み、買い込みは十分に早くでき、またKeepAliveは無効にしていると思われるので、この簡単なステータスでも十分に焼くに立つのではないかと予想しています。

このように書き換えたステータスをpsコマンドの結果から収集して、Active(Busy)のプロセス数、Idleのプロセス数を数えます。必要なプロセスをすべて調査するために「親プロセスのPIDが同じ」であるプロセスを探しています。

以下は実際にStarlet(max-process=10)でアプリケーションを起動してabでアクセスしている状態のpsの結果(一部)

 PPID   PID COMMAND
20337  1022  \_ /bin/zsh
 1022 20582  |   \_ /usr/bin/perl /usr/local/bin/plackup -r -Ilib -p 5003 -s Starlet -a nonopaste.psgi
20582 20583  |       \_ /usr/bin/perl /usr/local/bin/plackup -r -Ilib -p 5003 -s Starlet -a nonopaste.psgi
20583 20803  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 20804  |           \_ server-status-lite[20583] A 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 20806  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 20808  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 21153  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 21156  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 21158  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 21163  |           \_ server-status-lite[20583] _ 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 21165  |           \_ server-status-lite[20583] A 127.0.0.1 localhost:5003 GET / HTTP/1.0
20583 21168  |           \_ /usr/bin/perl /usr/local/bin/plackup -r -Ilib -p 5003 -s Starlet -a nonopaste.psgi

最も左側の項目が親プロセスのPIDで、ここが20583になっているプロセスがPreforkされているプロセスです。6行目と13行目のプロセスが動いているプロセス。一番下のプロセスはまだ一度もアクセスを処理していないので、元のプログラム名のままとなっています。このようなプロセスはIdleのプロセスとカウントしています

このステータス情報はApacheのmod_status同様http経由で取得可能です。おしゃれなHTMLに整形されていたりしませんが以下のように取得可能です

 % curl -v http://localhost:5003/server-status
 (略)
 < HTTP/1.0 200 OK
 < Date: Thu, 01 Jul 2010 15:38:49 GMT
 < Server: Plack::Handler::Starlet
 < Content-Type: text/plain
 < Content-Length: 30
 < 
 BusyWorkers: 3
 IdleWorkers: 7

このMiddlewareを有効にするには、Plack::Builderを利用して

builder {
    enable "Plack::Middleware::ServerStatus::Lite",
        path => '/server-status',
        allow => [ '127.0.0.1', '192.168.0.0/16' ];
    $app;
};

のように設定します。pathはhttpで取得するする場合のURI。allowはアクセス制御用。何も書かないと一切アクセスできません。

このMiddlewareは新サービスで使ってもらえる予定で、リソース監視も当然行う予定になっています。あとはpsgi.multiprocessのflagぐらいは確認した方がいいかなぁと思っています。 ただpsgi.multiprocessがTRUEなmod_perl1で動かすとどうやらセグフォルするようですが。