2013年02月

growthforecast-client (gem) をリリースしました

こんにちは @sonots です。

GrowthForecast のグラフの色を変更したり、複合グラフを作ったり、という作業を手動でなんてやってられない!ヽ(`Д´)ノ と思ったので、@mikeda さんの記事を参考に growthforecast-client という gem をつくりました。Thanks @mikeda さん !!


ちなみに @tagomoris さんが rb-growthforecast という gem をすでに作っているので、この gem は2番煎じであり、たぶん私以外の人は使わないでしょう。でも、いいんだ。自分で書き始めちゃってたから gem にしたかっただけなんだ。※ そもそも GrowthForecast に JSON API を追加したのは @tagomoris さんです。圧倒的感謝!=> http://tiqav.com/3xe 

設計思想とか

思想とかいうほどじゃないんですが、カジュアルにハッシュを JSON にしたものを GrowthForecast API に投げて、受け取った JSON をハッシュにして返すだけにしました。特にオブジェクト化もしてないですし、IndiffrentAccess 化もしてないです。

で、ハッシュのキーも symbol 投げたのに、JSON 受け取ったら string になっていた …  何が起こったのか(ry という「Symbol <-> String」問題も面倒くさいので、入力も出力も、ハッシュのキーは string に統一、という仕様にしています。

使い方

という前置きの上で、使い方サンプル。

たとえばグラフの色など、グラフの設定を変えたい場合には、こんかかんじに書きます。

クライアントオブジェクト作って、#edit_graph にグラフのパス( service_name, section_name, graph_name )と、更新パラメータを渡しているだけですね。
どんなパラメータを使えるのかは、コードにハッシュサンプル載せてるので、それを参考にしてもらうとわかると思います(手抜き

複合グラフも作れますよ。こんなかんじ。
あとは、グラフ一覧を取得する #list_graph, セクション一覧を取得する #list_section, サービス一覧を取得する #list_service などもあるので、それらを使ってループまわせば全グラフの設定ができたりしますね。捗る!グラフの削除メソッドなんかも用意してあるのであとはコードみてください!(手抜き

実装(テスト)の話

横道にそれますが … なんか書きたくなったので ...

growthforecast-client のテストコードは https://github.com/bblimke/webmock を使って、GrowthForecast が立っていなくても流せるようにしています。Travis でもテストながせるようになったし便利!

さらに環境変数 MOCK に off をセットすると、webmock を使わずに本物と実際につなげてテストを流せるようにしています。

$ rspec # webmock
$ MOCK=off rspec # http://localhost:5125 の GrowthForecast にアクセス

モックテストだけ書いて安心する人もいますが、私はそれだと安心できない派です。本物とつないだテストも流せるようにしておかないとジョイントテストできないですからね。GrowthForecast がバージョンアップしたら死んじゃうじゃないですか。

ちなみにどうやって MOCK=off しているのかというか、簡単にいうと spec_helper.rb に


ENV['MOCK'] ||= 'on' 
require 'webmock/rspec' if ENV['MOCK'] == 'on'


のようなコードを書いて、あと webmock 関連のコードは shared_context にまとめてそれらにも適宜 if ENV['MOCK'] == 'on' をつけているかんじですね。

最後に

というわけでグラフ設定が捗るようになったのでハッピーです。再度 thanks @mikeda さん、@tagomoris さん!

Ruby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話

2013/12/07 追記: Ruby 2.1.0 には不正バイトを除去する専用のメソッド String#scrub が追加されています。詳しくは Ruby 2.1.0 に追加される String#scrub の紹介 を参照

こんにちは @sonots です。

Ruby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話をします。

Ruby 1.9 でUTF-8的に正しくないバイト列がある文字列を扱っていると、正規表現マッチや gsub といったメソッドを使っているところで ArgumentError: invalid byte sequence in UTF-8 例外が発生します。文字列を生成したときではなくて正規表現マッチなんかをしたときに始めてエラーが出るのですが、その辺の話については @tmtms さんのブログの記事が大変詳しくて勉強になるのでそちらを見るとよいかと思います。

再現コードはこんなかんじですね。
str = "\xff"
str.force_encoding('UTF-8')
 
begin
  str =~ /hoge/
rescue => e
  p e # ArgumentError: invalid byte sequence in UTF-8
end
そこでどう解決するのかぐぐるわけですが、"invalid byte sequence in UTF-8" で検索すると、
str = str.encode("UTF-8", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?')
のようにすると回避できるよ、という記事がいくつかヒットします。ですがそれは間違いです(ブログを晒すのははばかられるのでリンクは貼りませんが、ぐぐるとすぐみつかるでしょう)。これには2つの間違いが含まれています。

1. Ruby の String#encode メソッドは dst_encoding と src_encoding に同じエンコードを指定すると変換処理を行いません。

Ruby Doc に書かれていないのでちょっと不親切な気がしないでもないですが、そういう仕様になっています。不正な文字列を replace する処理も行わないということですね。コードにするとこんなかんじですかね。
str = "\xff"
str.force_encoding('UTF-8')
str = str.encode("UTF-8", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?')
p str #=> "\xff" のまま

begin
  str =~ /hoge/
rescue => e
  p e
end
そこについては、そういう仕様なんですが、実行してみた人はあれ?と気付くでしょう。そう、例外が起きなくなるのです。正確には encode("UTF-8", "UTF-8") だけでもOKです。
str = "\xff"
str.force_encoding('UTF-8')
str = str.encode("UTF-8", "UTF-8")

begin
  str =~ /a/
rescue => e # No throw!!
  p e
end
この性質を逆手におって、String#encode を呼べば例外が出なくなるよ、とバッドノウハウを教えているブログも見つかります(例によってリンクは貼りませんがぐぐると見つかるでしょう)。これが2つ目の間違いです。

2. String#encode("UTF-8", "UTF-8") で invalid byte sequence 例外が出なくなるのは Ruby のバグです。

チケットもあがっているようです。https://bugs.ruby-lang.org/issues/6190

ということを @repeatedly さんが成瀬さんに聞いてくださいました ^ ^ ということでこの性質を利用するのは間違いです。 とのことで 2.0や次の1.9.3 patch release からはおそらく例外があがることになると思われますので、そのようなコードを書いてしまっている方は今のうちに直しておきましょう!
追記: @n0kada さんによると 2.0 系統ではすでに修正済みとのことです!

こんなかんじになりますかね # invalid byte を除去する専用のメソッドが欲しいですね ...
str = "\xff"
str.force_encoding('UTF-8')
str = str.encode("UTF-16BE", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8")
p str #=> "?"

begin
  str =~ /a/
rescue => e
  p e
end
2012/03/22 に起票されたチケットなので、けっこう前からあるバグで、ぐぐった感じだとバッドノウハウ化しているようなので今のうちに警鐘をならす意味でブログを書きました。

最後に

ちなみにここで @repeatedly さんが話している pull req をくれた人、というのが私のことですね ^ ^; 例外を回避するためにこの処理をいれたコードを @tagomoris さんの fluent-plugin-parser のほうに pull request を送っておりまして(コレ)、ツッコミを受けたという話でした。

バグだとは気付きませんで、テスト通ってるし、あってるんじゃない?とか浅い考えをしておりました。すみません。そういう深い所まで見れるようになっていかないといけませんね。精進いたします。

@tagomorisさん、@repeatedlyさん、@nalshさんありがとうございました!

あと、修正版の pull req 送ってあるのでよろしくおねがいいたします :D > @tagomorisさん

HaikankoというFluentdクラスタ管理ツールの話をしてきた(2) - Fluentd側の話 #fluentdcasual

HaikankoというFluentdクラスタ管理ツールの話をしてきた(1) の続きです。

Fluentd Casual Talks #2 では時間の都合上、Fluentd 側の話ができなかったので、その辺の話を書きます。

監視機能や、グラフ生成機能を実現するために以下のプラグインを使っています。#時間都合で削っていたスライドです><

019

この watchcat ほにゃららってなんだ><、という話だと思うのですが、完全なるオレオレプラグインです ^ ^ どうしてこんなにオレオレプラグイン化にしているのかというと、以前ツイートしたのですが、 があると思っていて、やりたいことに対してほんのちょっと出力されるJSONのフィールドを足せば実現できるんだけど、他のプラグインを1枚かませたり、順序を逆にしてみたりしたけど、ダメだーとなることが多いというか最初にそれを経験してしまったのですね。それで、もう とか思い始めてオレオレプラグインを作りはじめたのでした。実際、オレオレプラグインを作り始めたら、パズルを解こうとして挫折するまでに5時間費やしていたものが、たったの30分でできたりしたので、オレオレプラグイン最強という話ですね。 もちろん、オレオレプラグインをそのままにしておくつもりはなくて、汎用的なパターンが見えてきたら、pull request を送るとか、切り出して別途 gem にするとかするつもりです。 実際、 fluent-plugin-ikachan には privmsg を送れる機能を追加して pull request 送っていますし(コレ)、fluent-pluign-mail にも Subject やメール本文で format (%s) 使えるように拡張して pull request 送っています(コレ)。その節はお世話になりました > @tagomoris-san, @u1-san

というわけで皆さん、時間短縮にもなるし、プラグインも増えるし、ということでハッピー尽くめなのでオレオレプラグインを作っていきまっしょい!という話でした :D

HaikankoというFluentdクラスタ管理ツールの話をしてきた(1) #fluentdcasual

こんにちは。@sonots です。

02/15(金)の Fluentd Casual Talks #2 at :D で発表をしてきたので、資料をあげます。
ustream はこちら http://www.ustream.tv/recorded/29298222

160人規模の勉強会で、募集が始まって2時間であっとういまに埋まったのは驚愕でしたね。どんだけ Fluentd 人気あるんだと。

当日は10人の発表者が1人5分 or 10分の持ち時間で Fluentdに関する話をカジュアルに発表しました。カジュアルといいながら皆ガチで発表するのが Casual Talks クォリティ。

で、私の発表では最近作っている Haikanko という Fluentd クラスタ管理ツールの話をしてきました。

この Haikanko (配管工) というツールは、例えばログ監視を追加したい(抜きたい)とか、ログ情報からグラフ生成をしたいといった設定をすると、その設定情報から Fluentd の(複雑な?)設定ファイルを自動生成し、Fluentd クラスタにデプロイすることができるという、カジュアルに Fluentd クラスタを運用するためのツールです。

なぜわざわざそんなものを作っているのかというと、弊社でグローバル展開をしている都合上、中国など他の拠点でこの Fluentd のシステムを使ってもらいたいと思っているのですが、その際に Fluentd の設定ファイルはこう書くんだ、とか、このプラグインを使えばいいみたいだよ、とかそういうやり取りをするよりは、私のほうでユースケース(やりたいこと)ベースで要望を受けてシステムを提供したほうが良いと思っているからなんですね。

発表でも話したように、Fluentd のノード数は爆発しがちなので、自動デプロイ機能が必要だとか、設定ファイルも爆発しがちなので、設定ファイルの自動生成機能が必要だ、とかそういう理由ももちろんあるのですが、一番の理由はやはり上記の理由になる気がしています。

「まとめ」でも言ったのですが、Fluentd やそのプラグインは汎用的な仕組みまでは提供してくれるのですが、やりたい機能(ログ監視とかグラフ生成とか)そのものを提供してくれるものではないので、アプリ層のものが1つ必要だと思います。そのアプリ層のものとして Haikanko があるわけですね。

さて、このあたりで勝手にQ&Aをしてみます。こういう質問くるかな?と思っていたものです(質疑応答の時間がなかったのであれでしたが)

Q. chef ではだめなの?
A. Haikanko の裏側で使っている mina の代わりに capistrano とか chef とかを使ってもいいとは思います。chef と Haikanko の比較、という意味であれば、chef はフレームワークというか仕組みを提供するレイヤーのツールで、Haikanko はアプリレイヤーのものなので比較するレイヤーが違うかもしれませんね

Q. 設定ファイルを erb で生成しなくても fluent-plugin-forest とか fluent-plugin-config-expander もあるけど
A. それらも検討したのですが、設定爆発の問題に対しては erb でループ回して生成したほうが応用が効いて、カジュアルだと判断したのでそうすることにしました。ruby コードがかけるので、最悪、なんでもできますし、そういう状況にしておかないと他拠点からの要望にカジュアルに答えられませんからね。#そもそも、config-expander を使うようにしても、config-expander の for ループの値を生成するために、erb のほうでループ回さないといけないんですよね。ならもう erb だけでも良いかと。

Q. OSS化はしないの?
A. したいと思っています。Haikanko を提供できるようになると、:DeNA で使っている Fluentd のシステムを気軽に他社さんが導入できるようになるので、結構 life changing なんじゃないかなぁと思っています。ただ、社内で便利に使えるように社内ツールとの連携をしたいと思っているのですが、それをしてしまうと、他社の環境で動かなくなってしまうわけで、そのあたりで悩んでいるのが現状ですね。どうしようかなぁ。

後半の Fluentd 側の話に続きます。
A Ruby and Fluentd committer working at DeNA. 記事本文および記事中のコード片は引用および特記あるものを除いてすべて修正BSDライセンスとします。 #ruby #fluentd #growthforecast #haikanko #yohoushi #specinfra #serverspec #focuslight
はてぶ人気エントリー