@kyanny's blog

To be a cross-functional developer

fluentd のソースを読む (1)

fluentd のソースを読み始めました。単なる趣味です。

経緯: fluentd の exec buffered output plugin を試してみたらflush_interval を短くしても USR1 シグナルを送ってもバッファが flush されず command が実行されなくて悩んでいたらTimeSlicedOutput はスライスの時間が経過しないとバッファから出てこないと教えてもらい、ソースを読んでちゃんと理解したいと思ったため、です。

とはいえ plugin/out_exec.rb をいきなり読んでもさっぱりわからなかったので、順を追って読んでみることにします。

まずは bin/fluentd を実行すると fluend が起動するところまで。

  • bin/fluend は fluend/command/fluentd.rb を実行してるだけ
  • fluent/command/fluentd.rb は fluent/log.rb, fluent/env.rb, fluent/version.rbfluent/supervisor.rb を読み込んで Fluent::Supervisor#start を実行する
  • fluent/log.rb は自前の便利ロガーっぽいもの
  • fluent/env.rb は設定ファイルのパスとかポート番号とかの初期値を定数に入れてるだけ
  • fluent/version.rb はバージョン番号だけ
  • fluent/supervisor.rb が fluentd の本体部分
    • Fluent::Supervisor#initialize はコマンドラインオプションを保持してロガーを作ってる
    • Fluent::Supervisor#start は fluent/load.rb を読んで必要ならばデーモン化したあとメインループに入る
    • fluent/load.rb は依存するサードパーティのライブラリなどを一括して require してる。こういうスタイルのコードは初めて読んだけど require が分散してないのはある程度の規模のプログラムになるとわかりやすくていいかもしれない。 cool.io など使ってるのでイベント駆動で動いてるのかなーなど眺めてるだけで楽しい。
    • install_supervisor_signal_handlers はシグナルをトラップしてる。実際はこのあと読む supervise メソッドのなかで fork してて (子をメインプロセスと呼ぶ) メインプロセスにシグナルを送り直して、 INT TERM の場合は終了フラグ @finished を立てるので start のなかの until @finished メインループを抜ける。つまり子を殺して親も死ぬのできれいにシャットダウンする。
    • supervise メソッドは基本的に fork してるだけ。必要ならばデーモン化する。なぜか Process.daemon を呼ばず自前で STDIN STDOUT STDERR を閉じたりしてる。子プロセスは main_process を実行して、親は子が終了するまで待つ (Process.waitpid)
    • main_process は渡されたブロックを実行する。このブロックは supervise メソッドに渡されたもので、 start のなかに実際のブロックのコードがある。いろいろメソッドを実行してる。あとで掘り下げる。あと main_process の仕事は例外をキャッチしてロガーにログを流してるだけ。
    • start のなかの supervise に渡すブロック、に戻ると、いろいろやってる。順に追っていく。
      • read_config は設定ファイルを読んでるだけ
      • change_privilege は子のメインプロセスのほうの実行ユーザー・グループを変更してるだけ (supervise に渡されるブロックの中のコードは fork した子プロセスで実行されることに注意)
      • init_engine は Fluent::Engine.init したあと、プラグインを読み込んだりしてる。 Fluent::Engine はあとで読む。
      • install_main_process_signal_handlers は子のメインプロセスでもシグナルをトラップしてる。 USR1 を受け取ると Fluent::Engine.flush! してる。これが、 USR1 シグナルを送るとバッファの内容を吐き出す、という根拠のようだ。これまでで TimeSlicedOutput などは一切出てきてないので、 USR1 を受け取ってもバッファの内容を吐き出さないというのは flush! メソッドが呼ばれたあとの各プラグインでの実装に依存する振る舞いだと考えられる。
      • run_configure は Fluent::Engine.parse_config を実行してるだけ。 read_config はパースしてる様子がなくて、単にファイルを read してるだけだったけど、実際のパースと解釈はあとでやっているということらしい。
      • finish_daemonize は start_daemonize とセットで読むべきだったので両方いま読む。
        • start_daemonize はお行儀の良いデーモンプロセスの作り方という感じで、リンク先にある通りで二度 fork してる。 start_daemonize は親プロセスのほうで実行されることに注意。つまりデーモンにするオプションを指定した場合は、マスタープロセスが二度 fork してデーモン化したあとで、子プロセスをさらに fork する。
        • finish_daemonize は逆に子プロセスのほうで実行される。これは、うーん、なにやってるんだろう、 supervise のなかの fork してる下で、親プロセスのほうで実行される if @daemonize && @wait_daemonize_pipe_w ; end のところとほぼ同じで、自信ないけど、 fork した子は親と同じ STDIN STDOUT STDERR を共有するので改めて閉じなおしてるんだと思う (デーモンプロセスの子もデーモンであるべきなので?)
      • そして run_engine は、 Fluent::Engine.run を実行してる。 Fluend::Engine は日を改めるとして、ここだけ先回りしちゃうと、 Coolio::Loop のイベントループを回してる。ので、 fluentd は正常起動してるあいだはずっと run_engine が走ってる、という感じなんだと思う。
      • その下に exit 0 とあるけど、ここにどういう条件で来るのかは run_engine の終了条件などを知らないといけないので、また今度。

この先イベント駆動とか Mutex とか難しそうなのがひかえてるけどがんばります。

余談: pkill -USR1 -f fluentd すると

2012-02-09 01:34:48 +0900: fluent/supervisor.rb:294:block in install_main_process_signal_handlers: force flushing buffered events
2012-02-09 01:34:48 +0900: fluent/supervisor.rb:294:block in install_main_process_signal_handlers: force flushing buffered events

という風に flush したっぽいログが二回出ていてなんでかなーと不思議だったのだけど、子プロセスが受け取って一回、親プロセスが子プロセスに送り直してもう一回で二回出てるわけですね (ps でみると親も子も $0 は同じっぽい) こういう実用上はどうでもいいトリビアに詳しくなれるのもソースコードリーディングのひそかな楽しみですね。

rbenv + ruby-build を system-wide にインストールする

rbenvruby-build 便利ですね。しかし ~/.rbenv 以下にもろもろ入ってしまうと都合が悪いこともあるので system-wide に、 /usr/local 以下とかにインストールしたい (そして複数ユーザーで同じ rbenv 環境を共有したい) のでやり方を調べた。

だいたい Shared install of rbenv のとおりでいける。たぶん /usr/local/rbenv/shims と /usr/local/rbenv/versions を自分で掘るのがポイント。これで rbenv install 1.9.3-p0 とすればうまいことインストールされる。

というわけで手順をコピペするのが面倒くさいのでインストーラのシェルスクリプトを書いた。 root で実行してください。

https://gist.github.com/1727338

Wiki ページには system-wide な rbenv を使いたいユーザーごとに ~/.profile を書けとあるが、全員で使って構わないので /etc/profile.d/rbenv.sh を置いた。これでどのユーザーでも rbenv が使えて便利ですね。

omniauth-loctouch を公開しました

https://github.com/banyan/omniauth-picplz をみて面白そうだったのでロケタッチ用のストラテジーを書いてみた。ほぼ banyan/omniauth-picplz の写経です。主な変更点は これくらい。

https://github.com/kyanny/omniauth-loctouch

こんな感じで使う。

use OmniAuth::Builder do
  provider "loctouch", ENV['CONSUMER_KEY'], ENV['CONSUMER_SECRET']
end

サンプルの Rails アプリケーションも書いてみた。 https://github.com/kyanny/hello-omniauth-loctouch こちらは Heroku で動かしてみようと挑戦中です。。

いまはまだ rubygems.org で公開してないので、 Gemfile に github のリポジトリの URL を書いてください。

gem 'omniauth'
gem 'omniauth-loctouch', :git => 'https://github.com/kyanny/omniauth-loctouch.git'

Travis CI とかも見よう見まねで設定してみた。このバッヂ があると急に Ruby コミュニティとの距離が近づいたような気分に!

ふつうに OAuth2 Strategy 使えばいい気もするけど、ロケタッチの OAuth2 API を利用してアプリケーションを作ってみたい方はぜひご利用ください。 (ライセンスは MIT License です)

kyanny.mit-license.org

mit-license-org を (いまごろ) 知ったので、自分のページも作ってみた

$ curl -d'{ "copyright": "Kensuke Nagae" }' http://kyanny.mit-license.org
>>> MIT license page created: http://kyanny.mit-license.org

独自ドメインでサイト運営を長年続けられた試しがないし、ブログの URL も最近変わったばかりで、インターネットにおける自分のアイデンティティとなるような URL なんておれには維持できそうにないと思ったので、URL は未指定のままにした。見た目もデフォルトで十分きれいに見えたので指定なし。メールアドレスは Gmail が続くかぎり使い続けるつもりだけど、まぁ別に公開しなくても名前でぐぐればそのときインターネット上で活動している場所の URL が出てくるだろうと思ってこれも指定しなかった。

こういうのはすごくセンスがよくてかっこいいなーと思うし、こういうのにすばやく目をつけるひとの目利きもすごいなーと思う。アイデアを思いつかないのは諦めるとしても目利きはアンテナのはり方次第でもっと頑張れるはずなので今年はそういうところも意識していきたい。