Ruby FiberとEnumerator
FiberとEnumeratorのメモ。
EnumeratorはFiberを使って実装されているらしい?
実行速度の比較をしてみた。
Fiber
words = Fiber.new do DATA.each do |line| line.scan(/\w+/) do |word| Fiber.yield word.downcase end end nil end counts = Hash.new(0) while word = words.resume counts[word] += 1 end counts.keys.sort.each do |k| puts "#{k} : #{counts[k]}" #=> a : 2 #=> book : 1 #=> is : 2 #=> pen : 1 #=> this : 2 end __END__ This is a pen. This is a book.
Enumerator
words = Enumerator.new do |y| DATA.each do |line| line.scan(/\w+/) do |word| y << word.downcase end end end counts = Hash.new(0) words.each do |word| counts[word] += 1 end counts.keys.sort.each do |k| puts "#{k} : #{counts[k]}" #=> a : 2 #=> book : 1 #=> is : 2 #=> pen : 1 #=> this : 2 end __END__ This is a pen. This is a book.
速度比較
読み込み対象の文字列を1万行書いて速度を比較したらEnumeratorの方がFiberより速かった。
$ time ruby fiber.rb a : 10000 is : 10000 pen : 10000 this : 10000 ruby fiber.rb 0.12s user 0.00s system 98% cpu 0.128 total $ time ruby enum.rb a : 10000 is : 10000 pen : 10000 this : 10000 ruby enum.rb 0.09s user 0.00s system 98% cpu 0.098 total
Fluentdのhttpインプットプラグインを使ってみた&=はまってみた
必要になったので、Fluentdのhttpインプットプラグインを試してみました。
httpインプットプラグインはWebAPI経由で簡単にデータを入力することが出来るとても便利なプラグインです。
使用可能なフォーマットとしてはjsonやmsgpack形式のバイナリデータの送信が可能なようです。
使ってみた
とりあえず公式ドキュメント(http Input Plugin | Fluentd)に習って使ってみます。
設定ファイル
$ vim test.conf # HTTPインプット <source> type http port 8888 bind 0.0.0.0 </source> # 標準出力へアウトプット <match **> type stdout </match>
Fluentdの起動
$ fluentd -c test.conf
curlでJSONデータを送信
リソース情報にFluentdで使用するタグを指定します。
また、POSTする際のパラメータでデータタイプを指定しています。
$ curl -X POST -d 'json={"action":"login","user":2}' http://localhost:8888/test.http.json
Fluentd側
# データが送られてきました 2013-06-24 11:57:15 +0900 test.http.json: {"action":"login","user":2}
msgpack形式のデータを送信
次はmsgpack形式のデータをhttpインプットプラグインに送信してみたいと思います。
例えばこんなクライアントを作って...
require 'net/http' require 'msgpack' host, port = 'localhost', 8888 http = Net::HTTP.new(host, port) data = {"action"=>"locagin","user"=>2}.to_msgpack http.post('/test.http.msgpack', "msgpack=#{data}")
Fluentd側
# データが送られてきました 2013-06-24 12:41:52 +0900 test.http.msgpack: {"action":"locagin","user":2}
msgpack形式のデータも受け取ることが出来ました!
はまってみた
完全にクライアントの作りの問題なのですが、
msgpack形式のデータをPOSTした時のエンコードではまってみました。
先ほど作成したクライアントのdataに値を追加してリクエストを送ってみます。
... data = {"action"=>"locagin","user"=>2,"hoge"=>[38,61]}.to_msgpack ...
Fluentd側
# データが送られてきま、せん!
netcat
netcatでプロキシを立ててリクエストを確認してみました。
$ nc -l 8888 POST /test.http.msgpack HTTP/1.1 Accept: */* User-Agent: Ruby Connection: close Host: localhost:8888 Content-Length: 43 Content-Type: application/x-www-form-urlencoded msgpack=��action�locagin�user�hoge��fuga&=
原因としては、Content-Type が application/x-www-form-urlencoded ですので、
POSTする時はパラメータとデータ部分を=で連結し、パラメータ同士を連結する場合は&で連結するようになっています。
そして送信するデータの中にASCIIコードの&(38)と=(61)を含めた場合に区切る場所を間違って解釈してしまいデータが受け取れなかったようです。
これは、クライアント側で適切にエンコードしたデータを送信しないと受け取れない文字列が出てきそうです。
実際にFluentdのテストコードはどのようにしているのか確認したところ、set_form_dataメソッドで適切にエンコードしていました。
# set_form_dataメソッドはURIモジュールのencode_www_formメソッドを呼び出しています。
テストコード(fluentd/test/plugin/in_http.rb at master · fluent/fluentd · GitHub)
# テストコード抜粋 def test_msgpack d = create_driver time = Time.parse("2011-01-02 13:14:15 UTC").to_i d.expect_emit "tag1", time, {"a"=>1} d.expect_emit "tag2", time, {"a"=>2} d.run do d.expected_emits.each {|tag,time,record| res = post("/#{tag}", {"msgpack"=>record.to_msgpack, "time"=>time.to_s}) assert_equal "200", res.code } end end def post(path, params) http = Net::HTTP.new("127.0.0.1", 9911) req = Net::HTTP::Post.new(path, {}) req.set_form_data(params) http.request(req) end
データを適切にエンコードして試してみる
データを適切にエンコードしてもう一度試してみました。
require 'net/http' require 'msgpack' host, port = 'localhost', 8888 http = Net::HTTP.new(host, port) data = {"action"=>"locagin","user"=>2,"hoge"=>["fuga",38,61]}.to_msgpack # エンコード data = URI.encode_www_form({"msgpack" => data}) http.post('/test.http.msgpack', data)
Fluentd側
# データが送られてきました 2013-06-24 13:16:14 +0900 test.http.msgpack: {"action":"locagin","user":2,"hoge":["fuga",38,61]}
リクエストを確認する
適切にエンコードされています!
# Content-Lengthがjsonより長くなってしまいます。。
$ nc -l 8888 POST /test.http.msgpack HTTP/1.1 Accept: */* User-Agent: Ruby Connection: close Host: localhost:8888 Content-Length: 63 Content-Type: application/x-www-form-urlencoded msgpack=%83%A6action%A7locagin%A4user%02%A4hoge%93%A4fuga%26%3D
おわりに
今回のエンコードの問題ですが、httpインプットプラグイン側のContent-Typeの判別条件に
application/x-msgpack を追加してdataだけを送るのはどうだろう?と思っています。
繰り返しますが、HTTPインプットプラグインは非常に便利なプラグインです!
このプラグインのおかげでサービス開発の際のコストが激減しており、
大変お世話になっております。 # Fluentd本当にありがとう。
beginのブロックに対してwhileが行える
Rubyって面白いなー。
i = 0 begin print i i += 1 end while i < 10 #=> 0123456789
SQLiteのjournal_modeについて
SQLiteのjournal_modeについてはこちらの記事が参考になりました。
[SQLiteのジャーナルファイル]
http://yuki312.blogspot.jp/2012/02/androidsqlite.html
またSQLiteのjournal_modeにてWAL(Write-Ahead Logging)を使用すると
デフォルトのdeleteモードから5.6倍ほど
insertしてからselectするまでの時間が高速化するという記事を見つけました。
ちなみにGrowthForecastではwalモードを使用されているようです。
[SQLite のパフォーマンスチューニング、または DBIx::Sunny 0.16 の話(2012年6月 8日)]
http://blog.nomadscafe.jp/2012/06/sqlite-dbixsunny-016.html
少しだけwalモードの動作を見てみましたが、
デフォルトのdeleteモードではトランザクション中にjournalファイルが作成され、
コミットするタイミングで削除されます。
一方walモードではtransaction中にshmとwalという二つのファイルが作成され、
commitされるとwalファイルのサイズだけが0になりました。
ポスグレのwalの説明ですが、わかりやすいかも?
[ログ先行書き込みプロトコルに基づくロギング――Write-Ahead Logging (WAL)]
http://www.postgresql.jp/document/7.3/admin/wal.html
[SQLite 3.7登場、高速コミット/ロールバックWAL実装実現[2010/07/26]]
http://news.mynavi.jp/news/2010/07/26/041/index.html
[SQLite本家 Write-Ahead Logging]
http://www.sqlite.org/wal.html
Ruby my_each
Rubyでeachぽいものを実装する時のメモ。
class Array def my_each &block self.size.times do |i| block.call self[i] end end end p Array.instance_methods(false).grep(/my_each/) data = [1,2] data.my_each do |v| puts v end #=> [:my_each] #=> 1 #=> 2
0で割り算したとき@Ruby193
Fixnumの0だとZeroDivisionErrorだけど
Floatの0だとInfinityと表示される。
irb(main):001:0> 123 / 0 ZeroDivisionError: divided by 0 from (irb):1:in `/' from (irb):1 from /usr/local/Cellar/ruby/1.9.3-p327/bin/irb:12:in `<main>' irb(main):002:0> 123 / 0.to_f => Infinity
無限リスト
[*1..(1.0/0)]
Gruffを使ってみた
Rubyでかっこいいグラフが簡単に書ける
http://nubyonrails.com/pages/gruff
https://github.com/topfunky/gruff
http://gruff.rubyforge.org/
install on mac
rmagickが必要
macportsの場合
$ sudo gem install gruff $ sudo port install tiff -macosx imagemagick +q8 +gs +wmf $ sudo gem install rmagick
homebrewの場合
$ brew install imagemagick $ brew install ghostscript $ gem install rmagick $ gem install gruff
使ってみる
$ vim test.rb #coding: utf-8 require 'gruff' g = Gruff::Line.new 640 g.title = "My Graph" g.data("Apples", [*1.upto(100)]) g.data("Oranges", [*100.downto(1)]) g.data("Watermelon", Array.new(100,50)) g.data("Peaches", [*1.upto(100)].sample(100)) g.labels = { 0 => '2003', 20 => '2004', 40 => '2005', 60 =>'2006', 80 => '2007', 100 => '2008' } g.write('my_fruity_graph.png') $ ruby test.rb #=> png画像が生成される
どのくらいの粒度でプロットできるのか確認したかった。