株式会社クリアコード > ククログ

ククログ


OSS Gateワークショップ2016-07-30を開催 #oss_gate

2016年7月30日にクラウドワークス4回目のOSS Gateワークショップを開催しました。今回はこれまでで最多の23名の参加でした。(ビギナーは19人申し込みで実際に参加したのは11人。参加率は約58%。)

20160730-DSC_6114 (写真はクラウドワークスのCIOの大場さんに撮ってもらいました。)

参加者の感想は以下のとおりです。(増えたら追記します。)

ここではワークショップ運営視点でまとめます。

今回のチャレンジ

OSS Gateワークショップはもっとよくしていけるワークショップです。そのため、毎回、前回の課題を解決することにチャレンジしています。

前回の課題はメンターとビギナーの割合が1:2(ビギナーがメンターの2倍)だと回らない、ということでした。これまでは2:1とメンターがビギナーの2倍の体制で実施していましたが、前回は割合が逆になりました。

メンターがビギナーの2倍いないと回らない体制では、今後、ビギナーが増えるに従い、メンター不足でワークショップを実施できなくなってしまいます。そのため、ビギナーがメンターの2倍でも回せる体制の確立にチャレンジしました。

そのために考案した仕組みが「サポートメンター(仮称)」です。

サポートメンターとはメンターをサポートするメンターです。過去のワークショップでメンターを経験したことがあるメンターが担当します。

サポートメンターを導入することで次のことを期待しました。

  • メンター1人に対してビギナーが2人の体制でも十分にビギナーをサポートできる
  • 新人メンターでも十分にビギナーをサポートできる

少し補足します。

メンター1人に対してビギナーが2人の体制のときの課題は、1人のビギナーをサポートしているときにもう1人のビギナーをサポートできないことです。サポートメンターを導入することにより、そのような状態をフォローする体制にしました。同時に2人のサポートが必要なときはそれほどありませんが、1人のメンターに付き数回は発生します。それをフォローします。

経験者メンターだけがビギナー2人をサポートできないとなると、十分なメンターを揃えることが難しくなります。はじめてのメンターばかりだとメンター1人に対してビギナー1人(以下)の体制にしなければいけなく、より多くのメンターが必要になるからです。はじめてのメンターでもビギナー2人をサポートできるように、対応が難しい場面はサポートメンターがフォローする体制にしました。

実際にサポートメンターを導入した結果、これら両方を達成できました。

今回はビギナー11名、メンター6名(一時的に7名)の体制でしたが、十分にビギナーをサポートできました。これはビギナーへのアンケート結果にある満足度が総じて高かったことから判断できます。

今回のメンターは次のどちらかでした。(参加していた人で気づいていた人はどのくらいいたでしょうか。)

  • はじめてのメンター
  • 元ビギナーのメンター

つまり、経験者メンター数人(今回は3人)がサポートメンターになることで、これらのメンターでも十分ビギナーをサポートできたということです。未経験の人がメンターとして参加しても十分即戦力になる体制ということです。

他に、OSS GateおよびOSS Gateワークショップの広報活動としてレポーターをチャレンジしようとしていましたが、これは人員不足でチャレンジできませんでした。次回はレポーターがいるので次回チャレンジする予定です。

今後のOSS Gate

これまで、OSS Gateは東京でのみ活動していましたが、今後は東京以外でも活動していきます。今のところ、札幌・沖縄での活動の準備を進めています。他の地域でもぜひ活動したいというかたは須藤(kou@clear-code.com)に連絡をください。

札幌は、@tricknotesを中心とした人たちで準備を進めています。まずは、9月24日、11月26日にOSS Gateワークショップを開催することが決まっています。9月24日は東京でも開催するので、東京・札幌で同日開催になります。(須藤は札幌にお手伝いに行くので東京のワークショップには参加しません。)

沖縄は、@yasulabと準備を進めています。RubyKaigi 2016が終わったら本格的に動き出す予定です。

来年には「北海道から沖縄まで、全国で活動しています」と言えそうです。

このように「地域」という括りだけで活動を広げていくのではなく、プログラミング言語を含む各種「オープンソースなプロダクトのコミュニティ」とも連携して活動を広げていけないかと検討しています。自分が関わっているOSSの開発に参加する人が増えるとうれしい、という方は須藤(kou@clear-code.com)に連絡をください。「OSS開発に参加する人を増やす」取り組みであるOSS Gateといい感じに協力できるはずです。

まとめ

7月30日に4回目のOSS Gateワークショップを開催しました。

今回は次のことにチャレンジしました。

  • これまでよりもメンターの人数を減らしても十分にビギナーをサポートする
  • はじめてのメンター・元ビギナーのメンターでも十分にビギナーをサポートする

これらを実現するためにサポートメンターを導入し、うまくいきました。

はじめてのメンターでも十分にビギナーをサポートできる体制になっているので、OSS開発に参加する人が増えるとうれしい人はぜひメンターとして参加してください。なお、次回は9月24日開催の予定です。8月4日時点でビギナー30名・メンター7名の申し込みがあります。メンター1人で2人のビギナーをサポートできる体制になりましたが、この比率ではさすがにサポートできないので、メンターとして参加する人を非常に募集しています。メンターの人は優先して参加できるようにするので「キャンセル待ち」になっていても気にせず申し込んでください。(毎日少しずつビギナーの申し込みがあるのであと数日もすればキャンセル待ちになりそうです。)

もちろん、過去のワークショップでメンターをした経験者メンターも非常に募集しています。サポートメンターとしてはじめてのメンターをサポートして欲しいのです。9月24日は一番慣れている須藤が札幌にお手伝いに行くので、東京は少し手薄になります。そのあたりを経験者メンターにフォローして欲しいのです。

2016-08-04

コードに改行を入れる方法とその利点についてHTMLを例に考える

あるプロジェクトで、一行が長いコードを扱う機会があったので、一行を短くする(改行を入れる)方法とメリット・デメリットについて考えてみました。

ここでは、長くなってしまいがちなHTMLを例として取り上げます。例えば以下のようなコードです。

<ul><li class="beast" id="cat">ねこ</li><li class="beast" id="fish">さかな</li></ul>

このように一行の文字数が多くなってしまうと、いくつかデメリットがあります。この例だと、どんな要素や属性があるのか一目ではわからなくなってしまっています。*1

これを、例えば以下のように変更してみます。

<ul>
  <li class="beast" id="cat">ねこ</li>
  <li class="beast" id="fish">さかな</li>
</ul>

どんな要素や属性があるのかわかりやすくなりました。*2

また、一行を短くすると差分がわかりやすくなるというメリットもあります。

例として、さかなにtitle属性を追加する場合を考えてみます。追加後のHTMLは以下のようになります。

<ul>
  <li class="beast" id="cat">ねこ</li>
  <li class="beast" id="fish" title="ねこの友人">さかな</li>
</ul>

diffコマンドなどで差分を取ると、以下のように表示されます。

 <ul>
   <li class="beast" id="cat">ねこ</li>
-  <li class="beast" id="fish">さかな</li>
+  <li class="beast" id="fish" title="ねこの友人">さかな</li>
 </ul>

一目ではどこが変わったのかわかりづらいですね。*3

では、もう少し改行してみます。以下の例ではどうでしょうか。

<ul>
  <li class="beast"
      id="cat">ねこ</li>
  <li class="beast"
      id="fish">さかな</li>
</ul>

属性の先頭が同じ位置に来るようにしています。

さかなにtitle属性を追加すると以下のようになります。

<ul>
  <li class="beast"
      id="cat">ねこ</li>
  <li class="beast"
      id="fish"
      title="ねこの友人">さかな</li>
</ul>

title属性を追加する前後の差分は以下です。

   <li class="beast"
       id="cat">ねこ</li>
   <li class="beast"
-      id="fish">さかな</li>
+      id="fish"
+      title="ねこの友人">さかな</li>
 </ul>

先ほどよりは差分が少なくなりましたが、まだ余分なところが含まれています。

差分がもっと少なくなるように改行した例は以下です。

<ul>
  <li
    class="beast"
    id="cat"
  >ねこ</li>
  <li
    class="beast"
    id="fish"
  >さかな</li>
</ul>

さかなにtitle属性を追加したコードは以下です。

<ul>
  <li
    class="beast"
    id="cat"
  >ねこ</li>
  <li
    class="beast"
    id="fish"
    title="ねこの友人"
  >さかな</li>
</ul>

差分は以下の通りです。

   <li
     class="beast"
     id="fish"
+    title="ねこの友人"
   >さかな</li>
 </ul>

差分が追加された箇所のみになりました。*4

どこまでやるかは好みにもよるので、導入の前にプロジェクトメンバーで意見を出し合ってみるとよいかもしれません。

みんなが納得できるようなルールができたら、コーディングスタイル(規約)として共有(公開)してみてください。

*1 一目でわかりづらい理由としては、要素同士、属性同士の距離が離れてしまっているということが挙げられます。

*2 ただし、リストでない要素の間に改行を入れると、ブラウザ上での見た目に半角スペースが表示されてしまう場合があります。CSSを当てていれば問題にならないことも多いですが、見た目が変わってしまう場合があるので注意してください。見た目が変わってしまうのを防ぐ方法としては、デプロイ時に改行やスペースを取り除く仕組み(minify)を入れておくというのが一例です。

*3 なぜかというと、変更されていない箇所まで差分になっている(色が付いている)からです。差分は行単位で表示されることが多く、一行が長いと変更されていない箇所まで差分になってしまいます。

*4 差分はわかりやすくなりましたが、grep検索する際に表示される部分が減ってしまうので、場合によっては不便かもしれません。その場合、grepのオプションに「-A」や「-B」、「-C」を指定することで、マッチした行の前後のコードを表示させることができます。

2016-08-10

クリアなコードの作り方: 変わらない値とわかるようにする

問題を調査する場合やプログラムを改良する場合、修正する場合などプログラムの挙動を把握しなければいけないことは多々あります。プログラムの挙動を把握する方法はいろいろあります。処理の流れを追う方法や特定の値に注目する方法や状態の変化を追う方法などです。

処理Aと処理Bの間でおかしくなっていることがわかっている場合は処理の流れを追う方法がよいでしょう。AからBの間の処理の流れを追っていけば問題の箇所を見つけられるからです。

特定の値がおかしくなっていることがわかっている場合はその値に注目して把握する方法がよいでしょう。その値が変わる可能性がある箇所に注目すれば問題を特定できます。

メインループ(GUIのイベントループや言語処理系の評価器のループなど)内の処理を把握する場合は状態の変化を追う方法がよいでしょう。状態を変えながら何度もループする処理では状態によって実行する内容が変わります。状態を把握することで実行する内容も把握しやすくなります。

ここでは特定の値に注目してプログラムの挙動を把握する場合にこんなコードだったら把握しやすいというコードを紹介します。これはコードを読む人視点の言い方です。コードを書く人視点で言うと、挙動を把握しやすいコードの書き方を紹介します、になります。

特定の値に注目する

「特定の値に注目する」とはどういうことでしょうか。それはその値が変わるタイミングに注目するということです。

たとえば、次のようなクラスがあり、@ageの値に注目するとします。

class User
  def initialize(age)
    @age = age
  end

  def age
    @age
  end

  def age=(new_age)
    @age = new_age
  end
end

@ageが変わる可能性がある箇所は以下の2箇所です。

  def initialize(age)
    @age = age
  end

  def age=(new_age)
    @age = new_age
  end

そのため、この2つのメソッドが呼ばれる箇所に注目します。なぜなら、ここでしか@ageは変わらないからです。ここに注目しておかしな値を設定している処理を見つけることで問題を特定できます。

users = {}
users[:user1] = User.new(-1) # 注目
users[:user2] = User.new(14) # 注目
puts(users[:user1].age)
users.each do |key, user|
  if user.age == -1
    user.age = 29 # 注目
  end
end

この例では@ageを変更できる箇所は2箇所でしたが、もっと変更できる箇所がある場合は注目する箇所も増えます。注目する箇所が増えるということは挙動を把握することが難しくなるということです。逆に言うと、変更できる箇所が少ないと挙動を把握しやすくなるということです。

ここまではコードを読む人視点の話です。

特定の値に注目しやすいコードの書き方

それでは、コードを書く人視点の話に入ります。

値を変更できる箇所が最小限になっているとコードを読むときに挙動を把握しやすくなるのでした。つまり、値を変更できる箇所が最小限なコードを書くと挙動を把握しやすいコードになります。

それでは、値を変更できる箇所を少なくする方法をいくつか紹介します。

セッターメソッド(ライターメソッド)を提供しない

前述の例ではコンストラクターとセッターメソッドが@ageを変更できる箇所でした。

  def initialize(age)
    @age = age
  end

  def age=(new_age)
    @age = new_age
  end

変更できる箇所を少なくするならage=メソッドを提供しなければよいです。

class User
  def initialize(age)
    @age = age
  end

  def age
    @age
  end
end

これで変更できる箇所が1つ減りました。age=メソッドがなくなったので使い方も変わります。

users = {}
users[:user1] = User.new(-1) # 注目
users[:user2] = User.new(14) # 注目
puts(users[:user1].age)
users.each do |key, user|
  if user.age == -1
    # ↓userを変更するのではなく新しくインスタンスを作ることになった
    users[key] = User.new(29) # 注目
  end
end

セッターメソッドを無くしても注目する箇所は3箇所から減っていないのですが、コードを読む側としてはうれしいことが増えました。それは、一度作ったインスタンスでは@ageは変わらないということです。

インスタンスは1度作られるといろいろな箇所で使われるでしょうが、インスタンスが作られる箇所はそれよりは少ないことが多いです。たとえば、以下の箇所でインスタンスを使っています。

users = {}
users[:user1] = User.new(-1)
users[:user2] = User.new(14)
puts(users[:user1].age) # 使っている
users.each do |key, user|
  if user.age == -1     # 使っている
    users[key] = User.new(29)
  end
end

あれ、使っている箇所(2箇所)より作っている箇所(3箇所)の方が多いですね。。。実際のプログラムでは使っている箇所の方が多いはずです。。。

使っている箇所が多い前提で話を進めると、多いと@ageが変わる可能性があるかを検討することが大変になります。そのため、検討する箇所が少なくなるのはコードを読む側にとってうれしいです。

コンストラクターでだけ値を設定する

セッターメソッドを無くした結果、コンストラクターでだけ値を設定するようになりました。

class User
  def initialize(age)
    @age = age
  end

  def age
    @age
  end
end

@ageを変更する箇所を減らすということであれば、次のようにすることもできました。

class User
  def initialize
  end

  def age
    @age
  end

  def age=(new_age)
    @age = new_age
  end
end

使うときはこうなります。

user = User.new
user.age = 29

これよりもコンストラクターで値を設定するコードの方がよい理由は次の通りです。

  • @ageが変更されるタイミングはインスタンス作成時だけということが明確になる。(=インスタンスを作った後に変更されない。)
  • @ageは必ず設定されていることが明確になる。(セッターメッソドを使う場合だとインスタンスを作った後に設定を忘れると設定されない。)

つまり、コードを書いた人の意図が明確になるので読むときに助かるということです。

「変更しない値はコンストラクターでだけ設定する」はコードを書いた人の意図を明確にする書き方なので、「このクラスのインスタンスは値を変更しないぞ!」という意図でコードを書いているときは積極的に活用したい書き方です。

変更できないようにする

これまでの例では、あえて値が数値になるようにしていました。これは、Rubyでは数値は変更不可能なオブジェクトだからです。文字列は変更可能なオブジェクトなので事情が変わってきます。

たとえば、次のコードはmessageメソッド経由でも@messageの値を変更できます。

class Commit
  def initialize(message)
    @message = message
  end

  def message
    @message
  end
end

次のようにすると変更できます。

commit = Commit.new("Hello")
puts(commit.message) # => Hello
commit.message << " World!"
puts(commit.message) # => Hello World!

これも防ぐようなコードにするかどうかはケースバイケース(オーバースペックの場合の方が多い)ですが、たとえば、次のようすればmessageメソッド経由では@messageの値を変更できなくなります。

class Commit
  def initialize(message)
    @message = message.dup
    @message.freeze
  end

  def message
    @message
  end
end

commit = Commit.new("Hello")
commit.message << " World!" # 例外:can't modify frozen String (RuntimeError)

Rubyは値(オブジェクト)を変更不能にする方法はfreezeですが、方法は言語ごとに違います。たとえば、CやC++ではconstを使います。

まとめ

コードを書くときに「変わらない値」とわかるような書き方にすることで、読むときに挙動を把握しやすいコードになるということを紹介しました。

いくつか書き方を紹介しましたが、中でも、「変更しない値はコンストラクターでだけ設定する」書き方は書いた人の意図を明確にしやすい書き方なのでそのような場面になったら活用してください。

なお、この話題についてまとめるきっかけになったのは次のようなコードを見たことがきっかけでした。外部で作られているオブジェクトに新しくroute_keyというデータを付与したいというコードです。

route = create_output
route.instance_eval do
  route.singleton_class.class_eval do
    attr_accessor :route_key
  end
end
route.route_key = key

このコードは最終的に次のようなコードになりました。セッターメソッドで値を設定するのではなくコンストラクターで値を設定するようになっています。

class Route
  attr_reader :output
  attr_reader :key
  def initialize(output, key)
    @output = output
    @key = key
  end
end

output = create_output
route = Route.new(output, key)
2016-08-17

9月1日開催のSpeee Cafe Meetup #2でOSS開発について話します

2016年9月1日に開催されるSpeee Cafe Meetup #02で須藤がOSS開発について話します。

主催のSpeeeさんは開発力向上のために開発者がOSSの開発に参加することを推進しています。クリアコードはそんなSpeeeさんの取り組みをサポートしています。その縁で今回話すことになりました。

OSSの開発に参加するメリットとOSS開発に参加する最初の一歩を支援する取り組みであるOSS Gateを紹介する予定です。OSSの開発に興味がある方はぜひお越しください。

OSS Gateについては以下の資料を参考にしてください。

2016-08-24

クリアコード:RubyKaigi 2016にスピーカー・スポンサーとして参加予定

9月8日から10日の3日間RubyKaigi 2016が開催されます。

クリアコードは例年RubyKaigiのスポンサーをしています。去年のRubyKaigi 2015のスポンサーに引き続き、RubyKaigi 2016もスポンサーになりました。今年はブースを出します。ブースではOSS Gateとクリアコード自体のことを紹介する予定です。クリアコードが気になる人は遊びに来てください。

また、須藤と沖元がスピーカーとして話します。どちらも2日目の9月9日です。須藤は10:30からのセッションで最新のバインディングの作り方について話します。沖元は14:20からのセッションでRubyリファレンスマニュアルについて話します。

ところで、8月21日にリリースされたRubyist Magazine 0054号では東京 Ruby 会議 11 運営記録がよかったです。この記事の中に事前インタビューの目的が書いてあります。4つ目の目的として「発表者に事前インタビューすることで、発表の内容を事前に考えて貰う」が挙がっています。RubyKaigi 2016では事前インタビューはありませんが、ここに発表内容を事前にまとめることで同じ目的を達成できるはずです。ということで、須藤の発表内容を紹介します。

2016年のバインディングの作り方

バインディングとはRuby以外の言語(主にC言語とC++言語)で実装された機能をRubyで使うためのライブラリーです。バインディングを使うと、Rubyでその機能を実装しなくてもその機能を使えるようになるため、短時間で高品質の機能が手に入(ることがあ)ります。また、C言語での実装はRubyでの実装よりも10倍100倍速いことがざらにあるので、性能面でも有利です。画像処理・動画処理・音声処理・全文検索・機械学習・統計処理・暗号処理などは実装コスト・性能の両面からバインディングを利用して既存実装を活用するのが向いている分野です。

というような背景があり、RubyをWebだけでなく様々な分野で活用するためにはバインディングは重要です。そんなバインディングの作り方を紹介するのが須藤の話です。

以下、説明が続きますが、文章だけで図がないため関係がわかりにくいかもしれません。RubyKaigi 2016までには図も用意する予定です。

拡張ライブラリーとしてバインディングを作る方法

バインディングを作るために使える仕組みが拡張ライブラリーです。拡張ライブラリーはC言語で実装された機能をRubyに組み込む仕組みです。C言語で既存の機能をラップする拡張ライブラリーを作ることでバインディングになります。

拡張ライブラリーの難点の1つに「インストールするためにはCコンパイラーが必要なこと」があります。拡張ライブラリーを使うためにはコンパイルする必要があるため、Cコンパイラーがないと拡張ライブラリーを使えません。GNU/LinuxやBSD系などCコンパイラーを用意しやすいOSもあればそうでないOSもあります。たとえば、WindowsではCコンパイラーを用意することの敷居が高く、インストールが難しいです。

この問題を解決する方法としてfat gemというやり方があります。これはgemの中にビルド済みバイナリーを入れるというやり方です。こうすることにより、ユーザーの環境にCコンパイラーがなくても拡張ライブラリーを使えるようになります。fat gemはWindowsユーザー向けだけに用意すればよいです。Windows以外のよく使われている環境ではCコンパイラーを用意することは比較的簡単だからです。

拡張ライブラリーの難点はもう1つあります。バインディング対象の機能数に応じて実装コストがあがる点です。Rubyが拡張ライブラリー用に提供しているAPIは比較的使いやすいため、拡張ライブラリーとしてバインディングを作ることは技術的にそれほど難しくありません。ただし、対象機能を1つずつラップするため、対象機能が多いとその分手間がかかるのです。

この難点を解決する方法としてバインディングを生成する方法があります。拡張ライブラリーによるバインディングの実装にはある程度パターンがあるので生成することができるのです。SWIGというツールはそれを実現するためのツールです。

SWIGを使って拡張ライブラリー用のコードを生成してバインディングを作る方法

SWIGを使うと(多くの場合は)少ない記述でバインディング用の拡張ライブラリーのコードを生成できます。これで、少ない手間でたくさんの機能のバインディングを作ることができます。ただ、SWIGを使った場合でもいくつか難点があります。

SWIGも拡張ライブラリーを使ったバインディングなのでインストールが難しい問題があります。この問題をfat gemというやり方で解決できるのは前述のとおりです。しかし、SWIGを使った場合は別の問題もあります。インストール時にCコンパイラーとSWIGが必要になる点です。通常、SWIGはシステムにインストールされていないので別途インストールする必要があります。インストール時にSWIGを必要なくする方法があります。SWIGで生成した拡張ライブラリーのコードをgemに含める方法です。こうすることでインストール時にSWIGは必要なくなります。ただし、gemに含めたときに生成した機能しか含まれません。gemに含めたときの対象ライブラリーはバージョン1で、ユーザーがインストールしようとしたときはバージョン2になっていた、という場合、バージョン2で追加された機能は使えない、ということです。

性能面でも幾分オーバーヘッドがあります。手書きでシンプルに実装したバインディングとSWIGで自動生成したバインディングではシンプルに実装したバインディングの方が高速です。バインディングのメソッドを大量に呼び出す(何万回とか)場合は影響があるでしょう。

これまでのバインディングの作成方法ではインストールの難しさが難点としてでていました。これはC言語を使っていることが原因です。つまり、C言語を使わずにバインディングを作れればインストールが簡単になります。その方法はFFI(Foreign Function Interface)を使う方法です。なお、最近のRubyで使えるFFIの実装はすべてlibffiというライブラリーを使っています。

FFIを使ってバインディングを作る方法

FFIを使うとRubyでバインディングを書くことができます。拡張ライブラリーでやっていたことをRubyで書けるようになっただけで、1つずつ機能をラップしていかなければいけない点は変わりませんが、それをCではなくRubyで書けるようになることで書きやすくなっています。

バインディングにC言語を使わなくなったことによりバインディングのインストールは確かに簡単になるのですが、バインディングが動くためにはラップ対象のライブラリーは依然として必要です。そのため、Windowsユーザーがインストールしにくいという点はそれほど解決されていません。

また、バインディングを書く言語がCからRubyになっているので手間は減ってはいますが、対象の機能がたくさんあると結局手間なのは変わりません。

なお、FFIを使うとSWIGを使ったときよりも性能が落ちます。

SWIGが拡張ライブラリーのコードを生成するように、対象ライブラリーの情報を元にFFIを使ったバインディングを生成する方法があります。それがGObject Introspectionというライブラリーを使う方法です。

GObject Introspectionを使ってバインディングを作る方法

GObject Introspectionを使うと実行時に対象ライブラリーの情報を使ってバインディングを作成できます。古いバージョンと新しいバージョンを同時にサポートするというSWIGではインストールの難易度が上がるために難しかったことを容易に実現できます。

対象ライブラリーをgemに含めなければインストールが難しいという点はFFIを使ったバインディングと同じです。ただし、対象ライブラリーもgemに含める実装がRuby-GNOME2プロジェクト内にあるため、これを活用することで対象ライブラリーも含んだfat gemを提供できます。

GObject IntrospectionはRubyだけで使えるライブラリーではなく、PythonやJavaScriptなど他の言語でも使えるライブラリーです。これは、対象ライブラリーがGObject Introspectionをサポートしていればいろんな言語のバインディングがすぐに用意できるということです。これまで、バインディングは各言語ごとにそれぞれがんばって作成するものでしたが、GObject Introspectionベースにすることにより他の言語のユーザーと協力してバインディングを作成することができます。

以上のように、バインディング作成コスト(自動生成ベース+他言語ユーザーと協力でコスト削減)とインストールの容易さという観点からGObject Introspectionを使ったバインディングの作り方が魅力的です。しかし、難点もあります。それは、(現時点ではまだ)遅いこととGObject Introspection対応ライブラリーは限られているという点です。

性能に関してはRubyKaigi 2016までに改善する予定です。

というような話をする予定です。

まとめ

クリアコードは9月8日から10日にかけて開催されるRubyKaigi 2016にスピーカー・スポンサーとして参加します。この機会に多くの人と交流できることを期待しています。それでは、RubyKaigi 2016で会いましょう。

タグ: Ruby | 会社
2016-08-25

Fluentdのプラグインのv0.14への移行の仕方

はじめに

クリアコードではFluentdというソフトウェアの開発に参加しています。Fluentdはログ収集や収集したデータの分配・集約などを行うソフトウェアです。

Fluentdのv0.14はv0.12とある程度の後方互換性が保たれているメジャーバージョンアップです。

v0.14での新機能を使ったプラグインを作成する際にはこれまでの Fluent 以下のクラスではなく、Fluent::Plugin 以下のクラスを継承し、実装する必要が出てきました。 また、v0.14からはプラグインでよく書かれるコードをカプセル化し共通に使えるヘルパーを提供することで、よりプラグイン開発者が簡潔で良くテストされたコードを使ってプラグインが開発出来るようになる、とアナウンスされています。*1

Inputプラグインの場合

Inputプラグインをv0.14のプラグインに移行する際には Fluent::Input の継承を止め、Fluent::Plugin::Input を継承するようにします。

また、Fluentdのテストを読むと、v0.14でのInputプラグインのテストにはfluent/test/driver/input.rbにある Fluent::Test::Driver::Input クラスのテストドライバを用いるようにすると良いことがわかります。

driver#run の書き方がv0.12と比べて変わっていると言う点に注意しなければなりません。 v0.14のテストドライバでは driver#run の終了条件が確定しない場合は例外が上がるようになっています。 *2

そのため、v0.14向けのInputプラグインのテストでは driver#run へブロックを渡すか、 driver#end_if で終了条件を指定することが必要です。

  1. requireするInputクラスを fluent/input から fluent/plugin/input へ変更する
  2. Time.now をナノ秒に対応した現在時刻を返す Fluent::EventTime.now に置き換える *3
  3. 前述のプラグインヘルパーを使う事のできる箇所は置き換える
  4. テストドライバをv0.14のものを使用するようにする。

という点に注意してInputプラグインのv0.14への移行作業を行います。

実際に、in_object_spaceプラグインをv0.14化したプルリクエストを見てみましょう。

1.と2.は見ての通りほぼそのままなので、とくにここでは詳細に解説しません。 Inputプラグインのv0.14への移行作業は3.の作業と4.の作業が特に重い作業です。

3.の作業に該当するものは、in_object_spaceプラグインをv0.14化したプルリクエストでは、timerヘルパーの使用にあたります。 v0.14ではタイマーを用いてイベントを発生させるのに汎用的なtimerヘルパーが提供されており、v0.12の頃はCool.ioのクラスを継承したクラスを作成してこの手の処理を行うコードを書く必要がありました。v0.14ではヘルパーを使うだけになっています。

また、プラグインヘルパーは helpers:inject のように使いたいプラグインヘルパー名をシンボルで渡す形式になっています。

4.の作業では、既に driver#run にブロックが渡されていたため、driver#emitsdriver#events に書き換える作業と、v0.14のInput Driverクラスの Fluent::Test::Driver::Input を用い、プラグインのクラスをv0.14のテストドライバに渡す作業のみでした。v0.14でのInputプラグインのテストドライバを利用するには fluent/test/driver/input をrequireする必要があります。

Outputプラグインの場合

v0.14のOutputプラグインが継承するべき Fluent::Plugin::Output クラスは実装されているメソッドによって

  1. バッファリングしないOutputプラグイン (non-Buffered Output)
  2. バッファリングし、同期的にバッファをコミットするOutputプラグイン (Buffered Synchronous Output)
  3. バッファリングし、非同期的にバッファをコミットするOutputプラグイン (Buffered Asynchronous Output)

の3つのOutputプラグインの性質を持つようになります。 また、v0.14でのOutputプラグインのテストドライバを利用するには fluent/test/driver/output をrequireする必要があります。 driver#run の書き方が変更になっており、例えば

driver.run(default_tag: 'test') do
  driver.feed(time, {"a"=>1})
  driver.feed(time, {"a"=>2})
end

のように、driver#rundefault_tag: キーワード引数にemitする時のタグを渡したり、driver#emit ではなくdriver#feed を用いてイベントを流し込む必要があるのに注意してください。 流し込まれたイベントはInputと同様に driver#eventsで取得することができます。

non-Buffered Output

まずは、1.の場合のv0.14への移行のプルリクエストを見ていきます。

この場合は #process メソッドのみOutputプラグインが実装する必要があります。 また、driver#run の新しい書き方に対応させました。

Buffered Synchronous Output

out_fileのv0.14化対応のプルリクエストを見ていきます。

Buffered Synchronous Outputのv0.14のプラグインは #write メソッドと <buffer> セクションを解釈出来るようにするか、compat_parametersプラグインヘルパーをプラグイン中で使用するようにします。

このプルリクエストではTimeSlicedOutputを継承したOutputプラグインのv0.14化対応をしています。 また、Outputプラグインからfomatterプラグインやbufferプラグインを使う事ができるため、それに関するプラグインヘルパーのconfig sectionの追加も行っています。 v0.14形式とv0.12形式のconfigは書き方が大幅に異なっています。*4

ですが、その差異を埋めるcompat_parametersプラグインヘルパーもあります。 このプラグインヘルパーの compat_parameters_convert メソッドを使う事により、v0.12形式のconfigでもv0.14のFluentdで引き続き使う事ができます。

Buffered Asynchronous Output

Buffered Asynchronous Outputのv0.14のプラグインは #try_write メソッドと <buffer> セクションを解釈出来るようにするか、compat_parametersプラグインヘルパーをプラグイン中で使用するようにします。 また、 prefer_delayed_commit でtrueを返すようにします。

まだこの非同期コミットに絞ったv0.14のプラグインはこの記事の執筆時点では書かれていません。

Buffered Synchronous/Asynchronous Output

実は、Buffered Outputプラグインはsynchronousとasynchronousの両方の機能をconfigで切り替えられるように書く事ができます。

Buffered Outputに対応したテスト用のプラグインを追加したプルリクエストを見てみます。

ここでは、Fluent::Plugin::Output を継承したOutputプラグインを追加しています。そこで#write#try_write、そして、#prefer_delayed_commit をそれぞれ実装しています。 このプラグインではバッファのコミットを非同期にする設定を追加してはいませんが、その設定をconfigからできるようにすることでバッファを同期的または非同期的にコミットする動作をconfigで切り替えられるプラグインを書く事ができます。

Outputプラグインのcustom format

また、v0.14のOutputプラグインはレコードのformatを行うためにプラグイン固有の#formatメソッドも定義しておく事が可能です。

Parserプラグインの場合

ParserプラグインはParserプラグイン単体で使われるプラグインではなく、Input・Outputプラグインなどから使われるOwnedプラグインと呼ばれる範疇のプラグインです。

これらParserプラグインのインスタンスはv0.14ではparserヘルパーの parser_create メソッドを用いて作成することが推奨されます。

v0.14のParserプラグインは Fluent::Plugin::Parser クラスを継承する必要があります。 Parserプラグインが持つべきメソッドは #parse のみです。また、v0.14対応をするには fluent/test/driver/parser にある Fluent::Test::Driver::Parser クラスのParserテストドライバを用いる必要があります。 v0.12では driver#parse にてパース結果をテストする形になっていましたが、v0.14では driver#instance によりParserプラグインのインスタンスを取り出してからパーサープラグインのインスタンスの #parse メソッドを呼ぶような規約に変更になったので注意が必要です。

Formatterプラグインの場合

FormatterプラグインはFormatterプラグイン単体で使われるプラグインではなく、Input・Outputプラグインなどから使われるOwnedプラグインと呼ばれる範疇のプラグインです。

v0.14のFormatterプラグインは Fluent::Plugin::Formatter クラスを継承する必要があります。 これらFormatterプラグインのインスタンスはv0.14ではformatterヘルパーの formatter_create メソッドを用いて作成することが推奨されます。 v0.14において、Formatterプラグインが持つべきメソッドは #format です。

また、v0.14対応をするには fluent/test/driver/formatter にある Fluent::Test::Driver::Formatter クラスのFormatterテストドライバを用いる必要があります。 v0.12ではテストドライバを使う事は少なかったのですが、 v0.14では driver#instance によりFormatterプラグインのインスタンスを取り出してからパーサープラグインのインスタンスの #format メソッドを呼ぶとformat後の値が取得し、テストするようになったので注意が必要です。

Filterプラグインの場合

v0.14のFilterプラグインは Fluent::Plugin::Filter クラスを継承する必要があります。

また、v0.14でのFilterプラグインのテストドライバは fluent/test/driver/filter をrequireする必要があります。

v0.12の頃は #filter よりも #filter_stream の方が10%程度高速だったため高速化のために利用されていましたが、v0.14ではfilterをパイプライン化できる時はする機能がサポートされることになり、 #filter を使うようにすることが推奨されます。 #filter_stream は互換性のために残されますが、非推奨扱いになります。

driver#run の書き方が変更になっており、例えば

driver.run do
  driver.feed('test', time, {"a"=>1})
  driver.feed('test', time, {"a"=>2})
end

のように、driver#emit ではなくdriver#feed を用いてイベントを流し込む必要があるのに注意してください。 流し込まれてFilterされた後のイベントは driver#filtered_records で取得することができます。

まとめ

Fluentdのv0.12向けに書かれたプラグインのv0.14対応の概要について説明しました。Fluentd向けに多くのプラグインが公開されていますが、v0.12の書き方のままで公開されているプラグインが多くあるのが現状です。 Fluentdのプラグインのv0.14化対応はやり方を把握すれば挑戦できないことはないので、v0.14らしい書き方をしてみたいFluentdのプラグイン開発者やプラグインを良くしたいユーザーの方々はv0.14のAPIを使ってみるといいのではないでしょうか。 この記事ではプラグインヘルパーについては深入りする事ができませんでした。プラグインヘルパーについてはまたの機会に説明することとします。 最後に、v0.14でこのプラグインが動いていないというIssueを上げるだけでもありがたいのでそのようなフィードバックをして頂けると幸いです。

*1 http://www.slideshare.net/tagomoris/fluentd-overview-now-and-then

*2 https://github.com/fluent/fluentd/blob/12718a218a1e78126108a573b85d4b18e8bd56d5/lib/fluent/test/driver/base.rb#L134

*3 `Fluent::Engine.now`は内部的に`Fluent::EventTime.now`を呼んでいるのでこのままでよいようです。

*4 例えば、formatterやbufferも一つのconfigセクションとして書けるようになっています。

タグ: Fluentd
2016-08-26

OSS開発支援の一例: Fluentdとその関連ソフトウェア

クリアコードではOSS開発支援サービスの一環でTreasureDataさんが中心になって開発し公開しているFluentdとそのプラグインなど*1の開発を支援しています。 かれこれ一年ほどやってきたので、どのようなことをやってきたのかふりかえります。

TreasureDataさんの目的はFluentdの普及促進です。Fluentdが普及すると、TreasureDataさんのサービスを利用する人が増えるためです。

クリアコードはTreasureDataさんの目的を達成するために以下のことをやっています。

  • TreasureData さんが手の回っていないところをサポートする
    • IssueやPRに対応する
    • メンテナのいないプロダクトでよく使われているものはメンテナンスを引き取る
    • 新機能の開発
    • バグの修正
    • その他、Fluentdの普及促進につながること
  • Fluentd やその関連ソフトウェアに関する技術情報を発信する
    • ククログやQiita等に記事を書く

直接依頼されたことだけでなく、Fluentdの普及促進につながることを積極的にやっています。

なお、TreasureDataさんとは基本的にはIssue/PullRequestでやりとりしています。作業の優先順位やIssue/PullRequestで相談しづらいことを相談するときはSlackを使用しています。

IssueやPRに対応する

IssueやPRにコメントを書くことによって、コミュニティが活発になります。コミュニティが活発になると次のような理由で新規ユーザーを惹き付ける要因の一つになります。よってFluentdの普及促進につながります。

  • 今後の発展を期待できる
  • 問題が見つかりやすくなり安定化につながる
  • ユーザー間での情報共有が活発になり早く問題解決できる

コメントを書いたIssueやPRのうち特に問題の解決につながったものをリストアップしました。

メンテナのいないプロダクトでよく使われているものはメンテナンスを引き取る

これらは依頼されて引き取ったものもありますが、Fluentdが内部で利用しているライブラリもあります。 こういったプロジェクトを引き取りメンテナンスを継続することによって、既存ユーザーは安心して使い続けることができますし、新規ユーザーも安心して使い始められます。安心して使えていると既存ユーザーは他の人から意見を求められたときに「安心して使える」という情報を伝えてくれます。このようにFluentdの普及促進につながります。

新機能の開発

既存のFluentdのままでもかなり便利ですが、以下の点をより強化することによりさらにFluentdの普及促進につながります。

  • 機能追加
    • Fluentdを活用できるケースが増えて普及促進につながります。
  • 既存機能の改良
    • 運用中のFluentdのメンテナンスが楽になります。運用が楽になると既存ユーザーはよりFluentdを活用でき、まわりにその情報を提供してくれる可能性が増えます。また、新しくFluentdを活用できるケースが増えて普及促進につながります。
  • テスト周りの改良
    • プラグインを開発しやすくなり、既存プラグインのメンテナンス・新規プラグインの開発がはかどります。プラグインが増えるとFluentdを活用できるケースが増えて普及促進につながります。

依頼されたものだけでなく、特にFluentdの普及推進につながるものをリストアップしました。

バグの修正

バグを修正することにより、それが原因で使えなかったケースでもFluentdを活用できるようになり、Fluentdの普及促進につながります。また、既存ユーザーは運用で回避せずに済むため、よりFluentdを楽に運用できるようになります。このような既存ユーザーが情報共有といった形でコミュニティで活躍してくれるとコミュニティがさらに活発になりFluendの普及促進につながります。

依頼されたものではなく、自分たちで見つけたバグを修正したものをリストアップします。

Fluentd関連の技術情報発信

QiitaやククログでFluentd関連の技術情報を発信しています。

その他、Fluentdの普及促進につながること

他にも以下のことを実施しました。

  • Fluentdのプラグインを新機能に対応させる*2
    • v0.12.11で追加された secret パラメータ対応
    • v0.12.16/v0.12.17で追加された desc 対応
    • v0.12.0で追加されたfilterプラグインに対応するための修正
  • Fluentd v0.14のプラグインAPIへの対応*3
  • ライセンス表記の修正
  • TravisCIの設定の追加・修正・更新
  • Rubyのバージョンアップに伴う修正
  • ユニットテストの追加
  • 警告の除去
  • flume-ng-fluentd-sinkの開発
  • fluent-bitのHomebrewのFormulaの作成のプルリクエスト
  • RustのFluent Loggerのfruentlyの開発

これらは少し遠回しなものもありますが、次の理由でFluentdの普及促進につながると考えて実施しました。

  • Fluentdの新しいユーザーが簡単に新しいFluentdやFluentdの関連ソフトウェアを使えるようになるため
  • ライセンス表記を正しいものに修正すると、ユーザーがそのソフトウェアを使ってもよいものなのか簡単に正しく判断することができるようになるため
  • メンテナンス性を向上させて開発を続けやすくするため
  • Flume NGからFluentdへの移行や接続を容易にするため
  • Fluentdを動かすには厳しい環境に対してもFluentdのエコシステムに乗っかれるかどうかの検証環境の導入を容易にするため
  • 新しい言語のユーザーにFluentdへの関心を持ってもらえるようにするため

まとめ

このようにクリアコードではOSSの開発支援を実施しているので、OSSの開発をしているけれど手が足りなくて思うように進められないといったことがあれば相談してみてください。 相談はフォームからメッセージを送ってください。

*1 fluent-plugin-xxxやfluent-logger-xxxが多い

*2 3rdパーティ製も含む

*3 絶賛対応中

タグ: Fluentd
2016-08-29

Fluentd v0.14のプラグインヘルパーの使い方

はじめに

クリアコードではOSS開発支援サービスの一環でTreasureDataさんが中心になって開発し公開しているFluentdとそのプラグインなど*1の開発を支援しています。 Fluentd v0.14からはプラグインでよく使われる定型の処理をより簡易に扱えるようにするプラグインヘルパーが実装されました。 この記事ではv0.14の執筆時点のプラグインヘルパーの使用方法の概説を行います。また、プラグインヘルパーを解説した各節の最後には実際の使われている箇所の一例を示します。

プラグインの使い方の概要

プラグインヘルパーは以下のようにhelpersに可変長引数で指定します。

helpers :plugin_name1, :plugin_name2, ..., plugin_nameN

また、helpersは複数回呼ぶこともできます。プラグイン毎にhelpersで使いたいプラグインヘルパーを可変長引数で指定するか複数回指定するかのポリシーを決めておくとよいでしょう。

child_process

プラグインから子プロセスを起動する定型の処理をカプセル化し、より簡易に扱えるようにするためのプラグインヘルパーです。

helpers :child_process
# ...
def start
  super
  child_process_execute(title, command[, ...]) do |io|
  end
  # ...
end
# ...
end

のように使用します。一定間隔で子プロセスを起動するには

config_param :command, :string
config_param :run_interval, :time
# ...
def start
  super
  child_process_execute(:example_child_process, @command, interval: @run_interval, mode: [:read]) do |io|
  end
  # ...
end
# ...
end

のようにします。一回だけ起動する場合は

config_param :command, :string
config_param :run_interval, :time
# ...
def start
  super
  child_process_execute(:example_child_process, @command, immediate: true, mode: [:read]) do |io|
  end
  # ...
end
# ...
end

のように使用します。このプラグインヘルパーは例えばin_execプラグインで使用されています。

child_processプラグインヘルパーの注意点

第一引数はシンボルである必要があります。また、この引数で渡されたシンボルはグローバルになるのでプラグイン名_使用用途としておくことが推奨されています。

compat_parameters

v0.12のconfigのflat styleからv0.14のstructured styleへ自動的にマッピングさせるためのプラグインヘルパーです。

v0.12のconfigではbufferのconfigは以下のような形式でした。

<match **>
  @type file
  path /path/to/file/*.log
  buffer_type file
  buffer_path /path/to/buffer/*.log
  time_sliced_format %Y%m%d%H # sliced per hour
  buffer_chunk_limit 16m
</match>

v0.14のconfigでは以下のような形式で書く必要があります。

<match **>
  @type file
  path /path/to/file/*.log
  <buffer>
     @type file
     path /path/to/buffer/*.log
     time_key 60m # sliced per hour
     chunk_size_limit 16m
  </buffer>
</match>

compat_parametersプラグインヘルパーを使用する事でflat styleからstructured styleへ自動的にマッピングできます。 以下、その概要です。

helpers :compat_parameters
# ...

def configure(conf)
  # ...
  compat_parameters_convert(conf, :buffer)
  super
  # ...
end

のように使用します。また、このプラグインヘルパーは後述するformatterやinject、parserプラグインと一緒に使用することがあります。

その場合は、

helpers :inject, :formatter, :compat_parameters
# ...
def configure(conf)
  # ...
  compat_parameters_convert(conf, :inject, :formatter)
  # ...
end

のように、conpat_parametersを通して変換したいプラグインのtypeを指定します。 例えばfilter_stdoutプラグインがこのプラグインヘルパーを使用しています。

event_emitter

v0.14のFluentdの Fluent::Plugin::Output クラスはデフォルトで emit が使用できなくなりました。 v0.14のOutputプラグインでは Engine.emit を直接呼べなくなるので router.emit を行いたい場合はこのプラグインヘルパーを使う必要があります。

使い方は

helpers :event_emitter

とするのみです。 v0.12のOutputプラグインの時と同じように router.emit を呼べるようになります。 このプラグインヘルパーは例えばout_relabelプラグインヘルパーで使用されています。

event_loop

event_loopヘルパーは Coolio::Loop クラスをカプセル化し、より手軽に扱えるようにしたプラグインヘルパーです。

helpers :event_loop
# ...
def start
  # ...
  @handler = ...
  event_loop_attach(@handler)
  # ...
end

のようにして使います。 @handler の中身は Fluent::SocketUtil::UdpHandlerCoolio::TCPServer などのインスタンスを入れることになります。 このプラグインヘルパーは後述のtimerプラグインヘルパーの内部で使われているため、暗黙的に使われていることが多いです。 in_syslogプラグインは明示的にこのプラグインヘルパーを使っています。

formatter

formatterを使うにはv0.12では Fluent::Plugin.new_formatter を呼び、Formatterのインスタンスを作成する一連の処理を書く必要がありました。 この処理ををカプセル化し、より手軽にformatterをプラグイン中で使用するためのプラグインヘルパーです。

このプラグインヘルパーは

helpers :formatter
DEFAULT_FORMAT_TYPE = 'out_file'
# ...
def configure(conf)
  # ...
  super
  @formatter = formatter_create(usage: 'example_format', conf: conf.elements('format').first, default_type: DEFAULT_FORMAT_TYPE)
  # ...
end

def format(tag, time, record)
  # ...
  @formatter.format(tag, time, record)
  # ...
end

のようにして使います。

また、実際のFluentdのプラグインでv0.12形式のconfigも扱う必要がある場合はcompat_parametersプラグインと一緒に以下のようにして使います。

helpers :compat_parameters, :formatter
DEFAULT_FORMAT_TYPE = 'out_file'
# ...
def configure(conf)
  compat_parameters_convert(conf, :formatter)
  super
  @formatter = formatter_create(usage: 'example_format', conf: conf.elements('format').first, default_type: DEFAULT_FORMAT_TYPE)
  # ...
end

def format(tag, time, record)
  # ...
  @formatter.format(tag, time, record)
  # ...
end

formatterプラグインヘルパーは多くのプラグインで使用されています。compat_parametersプラグインヘルパーと組み合わせて使っている例として、filter_stdoutプラグインがあります。 また、Fluent::Plugin::Output クラスを継承している場合、formatter#format はプラグイン中の #format を実装している場合、このメソッドがバッファを書き出す前に呼ばれるようになります。 #format にレコードのフォーマット操作のみの役割を持たせる場合、テストもしやすくなるのでおすすめです。

formatterプラグインヘルパーの注意点

第一引数は文字列である必要があります。また、この引数で渡された文字列はグローバルになるのでプラグイン名_使用用途としておくことが推奨されています。

inject

injectプラグインヘルパーはホスト名、タグ、時刻をレコードへ挿入するためのプラグインヘルパーです。 このプラグインヘルパーは

helpers :inject
# ...
def format(tag, time, record)
  record = inject_values_to_record(tag, time, record)
  # ...
end

のようにして使用します。基本的にrecordにアクセス出来る箇所であればどこでも使えますが、テストコードの関係上#format メソッドの中で利用するのが良いでしょう。

このプラグインヘルパーはv0.12の SetTagKeyMixinSetTimeKeyMixinhostname プレースホルダーの置き換えを狙ったものです。 このプラグインヘルパーは例えば、out_stdoutプラグインで使用されています。 3rdパーティ製のプラグインでもこのプラグインヘルパーの使いどころはかなりあるはずなので、使えそうな箇所を探してみてください。

parser

formatterを使うにはv0.12では Fluent::Plugin.new_parser を呼び、Parserのインスタンスを作成する一連の処理を書く必要がありました。 この処理ををカプセル化し、より手軽にParserをプラグイン中で使用するためのプラグインヘルパーです。

このプラグインヘルパーは

helpers :parser
DEFAULT_PARSER_TYPE = 'syslog'
# ...
def configure(conf)
  # ...
  super
  @parser = parser_create(usage: 'example_parse', type: DEFAULT_PARSER_TYPE, conf: conf)
  # ...
end

def do_something(text)
  # ...
  @parser.parse(text) do {|time, record|
     # ...
  }
  # ...
end

のようにして使われます。 このプラグインヘルパーは使われ方が若干特殊ですがin_syslogプラグインで使われています。

parserプラグインヘルパーの注意点

第一引数は文字列である必要があります。また、この引数で渡された文字列はグローバルになるのでプラグイン名_使用用途としておくことが推奨されています。

retry_state

retry_stateプラグインヘルパーはOutputプラグイン等でリトライ処理を行う際の決まったコードをカプセル化し、より簡易に利用出来るようにする機能を提供します。 このプラグインヘルパーはOutputプラグインのリトライ処理を切り出したもので、組み込み以外のプラグインでも組み込みのプラグインと同じようなリトライのロジックをより簡易に扱えるようになります。

helpers :retry_state
# ...
config_section :buffer, param_name: :buffer_config, init: true, required: false, multi: false, final: true do
  config_param :retry_timeout, :time, default: 72 * 60 * 60
  config_param :retry_type, :enum, list: [:exponential_backoff, :periodic], default: :exponential_backoff
  config_param :retry_wait, :time, default: 1
end
# ...
def start
  super
  @retry_state = retry_state_create(
    :example_retries, @buffer_config.retry_type, @buffer_config.retry_wait, @buffer_config.retry_timeout
  )
  # ...
end

のようにして使います。このプラグインヘルパーはOutputプラグインの継承元クラスを実装しているoutput.rbで使用されています。

storage

v0.14ではプラグインの状態をKey-Value形式で保存するのに用いるStorageプラグインが新たに導入されました。 storageプラグインヘルパーはv0.14で新たに導入されたこのプラグインを使用する際の決まったコードをカプセル化し、より簡易に利用出来るようにする機能を提供します。

このプラグインヘルパーは

helpers :storage

DEFAULT_STORAGE_TYPE = 'local'
#...
def configure(conf)
  super
  @storage = storage_create(usage: 'example_storing_value', conf: config, default_type: DEFAULT_STORAGE_TYPE)
end

def start
  super
  @storage.put(:example_value, 0) unless @storage.get(:example_value)
  # ...
end

def do_something
  @sutorage.update(:example_value){|v| v + 1 }
end

のようにして使用します。 このプラグインヘルパーはin_dummyプラグインのsuspendオプションの実装に使用しました。

storageプラグインヘルパーの注意点

第一引数は文字列である必要があります。また、この引数で渡された文字列はグローバルになるのでプラグイン名_使用用途としておくことが推奨されています。

thread

threadプラグインヘルパーはプラグインで新たにThreadを立てる必要がある際の決まったコードをカプセル化し、より簡易に利用できるようにする機能を提供します。

このプラグインヘルパーは

helpers :thread
# ...
def start(conf)
  super
  thread_create(:example_usage, &method(:run))
  # ...
end

def run
  # executed on other thread
end

# And thread lifecycle is managed by thread plugin helper automatically.

のようにして利用します。 thread_create のブロックは必須です。このブロックが別スレッドで実行されます。 このプラグインはevent_loopプラグインヘルパーの中で使用され、また、timerプラグインがevent_loopプラグインヘルパーに依存しているため、知らず知らずの内に使っている事が多いです。 明示的に使用している例としてはin_dummyプラグインがあります。

threadプラグインヘルパーの注意点

第一引数はシンボルである必要があります。また、この引数で渡されたシンボルはグローバルになるのでプラグイン名_使用用途としておくことが推奨されています。

timer

timerプラグインヘルパーはプラグインで新たに高精度なタイマーを実装する必要がある際の決まったコードをカプセル化し、より簡易に利用できるようにする機能を提供します。

このプラグインヘルパーは

helpers :timer
# ...
config_param :emit_interval, :time, default: 60
#...
def start(conf)
  super
  timer_execute(:example_timer, @emit_interval, &method(:on_timer))
  # ...
end

def on_timer
  # periodically execution block by timer
end

# And timer lifecycle is managed by timer plugin helper automatically.

のようにして利用します。 timer_execute のブロックは必須です。このブロックが別スレッドで実行されます。

また、一回のみの実行で良い場合は repeat: false をtimer_executeに渡します。

timer_execute(:example_timer, @emit_interval, repeat: false) do
  # one-shot timer execution block
end

in_gc_statプラグインではこのプラグインヘルパーを使っています。

timerプラグインヘルパーの注意点

第一引数はシンボルである必要があります。また、この引数で渡されたシンボルはグローバルになるのでプラグイン名_使用用途としておくことが推奨されています。

まとめ

執筆時点でのプラグインヘルパーの使い方と使われている箇所について解説しました。 v0.14ではプラグインでよく使われる定型の処理についてより簡易に扱えるようにプラグインヘルパーが実装されました。 プラグインでより高度なことをするのにためらっていたプラグイン開発者はv0.14向けのプラグインではこれらのプラグインヘルパーを使い、よりよいプラグインを目指されてみてはいかがでしょうか?

*1 fluent-plugin-xxxやfluent-logger-xxxが多い

タグ: Fluentd
2016-08-30

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|