こんにちは、こちらはFluentd Advent Calendar 19日目の記事となります。
fluentdの動作を確認するときやpluginのデバッグをする時などに、print文を埋め込んだりしていましたが、ふとしたきっかけでtd-agentパッケージに「 fluent-debug 」なるものが同梱されていることを知りました。さっそく使ってみようと思いましたが、これに関する記事がほとんど出てきませんでした...。
というわけで、せっかくなので今回はfluent-debugを触ってみた話をしたいと思います。
fluent-debugとは
実行中のfluentdに対してgdbのように、アタッチして内部をごにょごにょできるツールで、 fluentdの公式ドキュメント にも記載されています。内部では dRuby を使っており、 DRbObject と DRbServer を利用することでfluentdプロセス内部をごにょごにょできるようになっています。
とりあえず使ってみる
まずは、簡単に使い方を羅列していきます。
td-agent.conf
今回は、以下の様なtd-agent.confだった場合を例にします。
## live debugging agent
<source>
type debug_agent
bind 127.0.0.1
port 24230
</source>
<source>
type unix
path /var/run/td-agent/td-agent.sock
</source>
<source>
type http
port 8888
</source>
<match test.**>
type forward
<server>
host 192.168.1.3
port 24224
</server>
buffer_type file
buffer_path /var/log/td-agent/buffer/td
flush_interval 1
retry_limit 10
</match>
<match local.**>
type file
path /var/log/td-agent/access
</match>
type debug_agentの項目を追加するだけで、fluentd側の準備は完了です。
fluent-debugを実行
$ /usr/lib/fluent/ruby/bin/fluent-debug
2013-12-18 20:34:36 +0900 [trace]: registered buffer plugin 'file'
2013-12-18 20:34:36 +0900 [trace]: registered buffer plugin 'memory'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'debug_agent'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'exec'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'forward'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'gc_stat'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'http'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'monitor_agent'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'object_space'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'status'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'tcp'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'unix'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'syslog'
2013-12-18 20:34:36 +0900 [trace]: registered input plugin 'tail'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'copy'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'exec'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'exec_filter'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'file'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'forward'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'null'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'roundrobin'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'stdout'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'tcp'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'unix'
2013-12-18 20:34:36 +0900 [trace]: registered output plugin 'test'
Connected to druby://127.0.0.1:24230.
Usage:
Engine.match('some.tag').output : get an output plugin instance
Engine.sources[i] : get input plugin instances
Plugin.load_plugin(type,name) : load plugin class (use this if you get DRb::DRbUnknown)
irb(main):>
/usr/lib/fluent/ruby/bin/fluent-debugを実行することで、irbが立ち上がり、起動中のfluentdプロセスにアタッチすることができました。
outputのpluginを取得する
irb(main):> outforward = Engine.match('test.hoge').output
=> #<Fluent::ForwardOutput:0xb5048f20>
irb(main):> outfile = Engine.match('local.hoge').output
=> #<Fluent::FileOutput:0xb5041040>
irb(main):> outforward.port
=> 24224
irb(main):> outforward.flush_interval
=> 1.0
irb(main):> outforward.retry_limit
=> 10
matchにタグ名を渡してあげると、そのタグ名を処理すべきoutputプラグインを取得することができます。いくつかのインスタンス変数も取得できます。
inputのpluginを取得する
irb(main):> inunix = Engine.sources[0]
=> #<Fluent::UnixInput:0xb5b2478c>
irb(main):> inunix.path
=> "/var/run/td-agent/td-agent.sock"
irb(main):> Engine.sources[2]
=> #<Fluent::DebugAgentInput:0xb5a10e90>
irb(main):> Engine.sources[1]
=> #<Fluent::HttpInput:0xb5a11a20>
sourcesの後の添字は適当にいくつか試すとたどり着きます。
メソッドを実行してみる
irb(main):> outfile = Engine.match('local.hoge').output
=> #<Fluent::FileOutput:0xb5041040>
irb(main):> outfile.format('test.poyopoyo', 1383641636, 'aaaaaaaaaaaaaaaaaaa')
=> "2013-11-05T17:53:56+09:00\ttest.poyopoyo\t\"aaaaaaaaaaaaaaaaaaa\"\n"
out_file pluginのformatメソッドを実行することができました。これはプラグインのデバッグで活躍しそうです。
getterの無いインスタンス変数を取得してみる
irb(main):> class << outforward
irb(main):> undef_method :instance_variable_get
irb(main):> end
=> #<Class:#<DRb::DRbObject:0x9783a30>>
irb(main):> outforward.instance_variable_get(:@usock)
=> #<UDPSocket:0xb5235090>
irb(main):> buffer = outforward.instance_variable_get(:@buffer)
=> #<Fluent::FileBuffer:0xb5042a08>
irb(main):> class << buffer
irb(main):> undef_method :instance_variable_get
irb(main):> undef_method :instance_variables
irb(main):> end
=> #<Class:#<DRb::DRbObject:0x8f420ec>>
irb(main):> buffer.instance_variables
=> [:@buffer_chunk_limit, :@buffer_queue_limit, :@mon_owner, :@mon_count,
:@mon_mutex, :@parallel_pop, :@config, :@buffer_path, :@buffer_path_prefix,
:@buffer_path_suffix, :@flush_at_shutdown, :@queue, :@map]
irb(main):> buffer.instance_variable_get(:@queue)
=> []
obj.instance_valiable_getを利用して取得します。instance_valiable_get等を利用するには、undef_methodを駆使してごにょごにょします。これで、内部の細かいインスタンス変数を確認できます。
もちろん、out_forward pluginが内部に保持していたbuffer pluginに対しても同様に中身を見ることができます。
インスタンス変数を取得する方法の簡単な説明
さて、さらっと書きましたが、ruby力の低い私は、「instance_variable_getをundef_methodする」に辿り着くまでかなりの時間を要しました...orz
このあたりは、dRubyの使い方の話となりますが、簡単に解説いたします。
fluent-debugを実行すると、DRbObjectクラスのインスタンスを生成しirbを起動します。そのインスタンスへのメソッド呼び出しはリモートプロセスに送られ、リモート側でメソッドが呼び出される仕組みとなっています。
リファレンスによると、 Object#method_missing でメソッド呼び出しをhookして、それを転送するとのこと。つまりは、DRbObjectクラスに定義されていないメソッドしか転送されません。
一方、今回私が知りたかったのは、各プラグイン内で利用されているインスタンス変数たちでした。例えばout_forward plugin内で利用されている@bufferの中身などです。こうしたインスタンス変数は外部からは基本的には触れません。そこで、Objectクラスのインスタンスメソッドである、instance_variablesとinstance_variable_getを利用することで取得しようと試みました。
irb(main):> outforward = Engine.match('test.hoge').output
=> #<Fluent::ForwardOutput:0x007fbb5bae3b20>
irb(main):> outforward.class
=> DRb::DRbObject
irb(main):> outforward.methods
=> [:==, :hash, :eql?, :_dump, :__drburi, :__drbref, :respond_to?, :method_missing,
:pretty_print, :pretty_print_cycle, :to_json, :nil?, :===, :=~, :!~, :<=>,
:class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone,
:taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?,
:inspect, :methods, :singleton_methods, :protected_methods, :private_methods,
:public_methods, :instance_variables, :instance_variable_get,
:instance_variable_set, :instance_variable_defined?, :instance_of?,
:kind_of?, :is_a?, :tap, :send, :public_send, :respond_to_missing?,
:extend, :display, :method, :public_method,
:define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=,
:instance_eval, :instance_exec, :__send__, :__id__]
irb(main):> outforward.instance_variables
=> [:@uri, :@ref]
しかしながら、DRbObjectクラスにもObjectクラスのインスタンスメソッドを実行可能なので、リモートプロセスに転送することなく、今手元にあるDRbObjectインスタンスへのメソッド呼び出しとなってしまいます。
そこで、DRbObjectインスタンスのinstance_variablesなどのメソッドを、特異クラスを利用してundef_methodすることで消す。という方法をとりました。こうすることで、リモートプロセスにメソッドが転送されるようになりました。このあたりは、dRubyでは当たり前のことかもしれませんが、なかなかたどり着けませんでしたorz
これにもう少し手を加えると、こんなこともできます。
irb(main):> Thread.list
=> [#<Thread:0xa013024 run>] # fluent-debugプロセスのスレッド一覧
irb(main):> outforward = Engine.match('test.hoge').output
=> #<Fluent::ForwardOutput:0xb5048f20>
irb(main):> class << outforward
irb(main):> undef_method :instance_eval
irb(main):> end
=> #<Class:#<DRb::DRbObject:0xa98e590>>
irb(main):> outforward.instance_eval('def getThread; Thread::list; end')
=> nil
irb(main):> outforward.getThread
=> [#<Thread:0xb68e4ffc sleep>, #<Thread:0xb5045b04 sleep>, #<Thread:0xb5234f28 sleep>, #<Thread:0xb523485c sleep>, #<Thread:0xb5b2685c sleep>, #<Thread:0xb52ac528 sleep>, #<Thread:0xb581ddb8 sleep>, #<Thread:0xb5adb960 run>] # リモートプロセスのスレッド一覧
eval族のメソッドをDRbObjectから取り除くことで、リモートプロセスに対して色々とできるようになります。この例だと、out_forward pluginに新たなメソッドを追加して、リモートプロセスに存在するスレッド一覧を表示しました。
まとめ
fluent-debugを利用することで、起動中のfluentdプロセスへアタッチしてインスタンス変数やメソッド呼び出しを行うことができる。利用したい場合もごくごく少ないconfへの追記で済む。
これまでは、fluentdのソースにprintを仕込んで再起動の繰り返しや、gdb、straceなどで動作を追ったりデバッグしていたけど、fluent-debugを使いこなせるようになればいろいろと捗るんじゃないだろうかと思います。とはいえ、Coolioとかの域はfluent-debugでは難しいかもしれません。
みなさんのfluentd関連のデバッグ方法などを知りたいです!もしよろしければ教えてくださいmm
というわけで、Fluentd Advent Calendar 2013 の19日目の記事でしたmm