ぬけラボ

φ(..)メモメモ

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=&#65533;&#65533;action&#65533;locagin&#65533;user&#65533;hoge&#65533;&#65533;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本当にありがとう。

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

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画像が生成される

どのくらいの粒度でプロットできるのか確認したかった。