Real-time Monitor with Sinatra

あるとき、とある現場で、任意のホスト上の何かリソースの現在値を、リアルタイムでモニタリングしたい旨の相談を受けたので、これは “real-time web” の出番だと閃いて、そのキャッチ・コピーを掲げる Perl の Web フレームワーク Mojolicious でやってみようと取り掛かることにしました。と、書き出したところですが、しまいには Ruby の Sinatra で実装したものを紹介するだけのところで話は終わります。

ツールはいずれにしても、まずロジックがありきです。──仕組はホスト上のあるコマンド、ここではサンプルとしてロード・アベレージ( uptime コマンド)にしましたがなんでもいいです、その現在値をリアルタイムに=1秒とかのスパンで定期的に取得して、都度クライアントにプッシュするような具合でしょう。そんんふうにざっとした想像はできたものの、しかし実際に組み立てたことはこれまでありませんでしたから、じぶんにとってはちょうど手頃な課題ではありました。

冒頭に述べた通り、 Mojolicious が “real-time web” を売り文句にしていることを覚えていたので、手始めに最初は Mojolicious のドキュメント を頼りに開いて、ストリーミングというキーワードを探しました。そして、サーバ・プッシュとしてのファースト・ステップはさほど難なく、形を見ることができました。──そこにあるサンプルをコピペしただけでしたから:

EventSource web service

ただこのときじぶんは、サーバからのプッシュだから WebSocket を使うのかな? と頼りなく考えていましたが、 Server-Sent Events なる技術を用いて、 HTTP だけで「プッシュ通知」を実現できるとのことを初めて知ることとなります。冒頭の一文を引用します:

This specification defines an API for opening an HTTP connection for receiving push notifications from a server in the form of DOM events.

「 HTTP 接続したサーバからプッシュ通知を DOM イベントとして受けるための仕様」とでも読みましょうか。今回は、仕組みが簡単そうに見えたこれを試してみることにしたのですが、その仕様は見た目どおりシンプルで、理解し易いものでした。

その取り巻く技術がいったいどんなものかについては、上述 Mojolicious のたった数行のサンプルにヒントがすべてが詰まっているので、ここでは冗長に述べることはしませんが、アプリケーション実装者としてのポイントとしては、クライアント・サイドである HTML 5 の EventSource オブジェクトにあるように思います。と、言っても難しいことはありませんで、通知が来るので、それを受けたときに駆動するイベントハンドラでデータを使うだけ、でした。そしてサーバ・サイド側の要点としては、 content-typetext/event-stream であること。

Mojolicious のサンプルでは、サーバ上でログファイルに行が追加された時にイベントが発行されて、その通知をクライアントの EventSource のインスタンスが受け、そのイベント・ハンドラがデータ、ここではログの行を、ページに表示するだけです。それはそれでリアルタイムなログ・モニタとなりそうですが、今回の件では、ロード・アベレージのリアルタイムな値をモニタしたいので、このサンプルを元に、改造していきます。

改造の要点は、定期的に=延々とループしながらロード・アベレージ( uptime コマンドの結果)を取得するサービスが別に必要だというところです。クライアントからのリクエストが来るたびにそのサービスを起動しても同じ結果が得られるかもしれませんが、そうすると、リクエストの数だけ uptime を別々に実行してしまい、無駄です。ホスト上で同じタイミングで uptime したところで結果は同じでしょう。それどころかむしろ負荷が上がってしまって高めの値が出てしまう? そういった懸念もありました。そこで、値を取得するサービスはただひとつだけ動かし、そのサービスが1秒ごとに更新し続ける結果を、任意のタイミングでやってくるすべてのリクエストが参照し(それはすなわちその時の最新値である)、それをクライアントへレスポンスする、といった実装にしようと思い至ります。

──ところでずいぶん長らく書いてきてしまいましたが、 Sinatra はいつ出てくるのかというと、そろそろです。

さて Perl で別プロセスを生もうと思った時、そのデータを、リクエストのプロセスから参照するにはどうするかという技術的な厄介に気づきます。プロセス間通信? ファイルを経由する? そういう面倒な… ことをするくらいならば、 Ruby の Thread を用いるのが手軽で良かろうと思うのです。(本当を言うと、 Mojolicious にも適当なやり方があるのかもしれませんが、すぐにはわからなかったのです。)

そうして作ったプロトタイプがこれです。 Gist に置きました。

https://gist.github.com/hiroaki/ad55b38be8701f25eb93

動作デモのページを用意すれば、より良いのはわかっているつもりですが、メンテナンス対象が増えてしまうのが嫌なので、作りませんでした。興味ありましたら、コードを動かしてみてください。動作させるためには、 sinatrathin 、ふたつの gem が必要です。──とはいえ、やはり絵があるとインパクトがあって、この記事にも幾ばくかの華やぎを添えるのも事実です。から、戯れですが動画に撮ったので、デモの代わりに YouTube に置いたその動画を貼っておきたいと思います。

http://www.youtube.com/embed/HdBtD-t4xg4?t=3m30s

さて、 Mojolicious で基本的な技術概要を得てダラダラと述べておきながら、結局 Sinatra で実装し、しかもそれについての解説はまったくしていません。一体何のつもりなのか、と訝しがられるのも無理はありません。でも、にわかに覚えたてのじぶんの解説などは、あまりためになりそうもありませんから、止しておこうと思います。くわしくは Sinatra の README ドキュメント、イントロダクションを見ていただくのが、よいかと思います。

Sinatra: README (Japanese) # ストリーミングレスポンス(Streaming Responses)

そもそもは、これほど簡単にサーバ・プッシュができるんだ、ということを紹介してみようと思ったのが、この記事を書く動機になったことでした。なので、そこへ到達できたところで、この記事は〆たいと思います。おわり。