ぬけラボ

φ(..)メモメモ

使用可能なディレクトリ容量を制限する

ext3 で disk quota をディレクトリに対してかけられなかったので、イメージファイルを作ってループバックマウントしてみる
# XFSとかBtrFSだとディレクトリに対して disk quota をかけられるらしい...

例: とあるディレクトリを5GBまでしか使わせたくない場合

# dd if=/dev/zero of=test.img bs=1G count=5
# mke2fs -F -j test.img
# mkdir /mnt/test
# mount -o loop test.img /mnt/test
# ln -s /mnt/test /home/admin/test

制限されているか確認

# cd /home/admin/test
# dd if=/dev/zero of=test.img bs=1G count=6
dd: writing `test.img': No space left on device
5+0 records in
4+0 records out
5134569472 bytes (5.1 GB) copied, 49.9218 s, 103 MB/s

Rails4.0.3のアプリをRails4.1に上げてみた

Rails4.1がリリースされたのでRails4.0.3のアプリをアップグレードしてみました。
docrails/guides/source/upgrading_ruby_on_rails.md at master · rails/docrails · GitHub

概ね4.0.3で書かれたアプリケーションはそのまま動きましたが、一部でエラーが出ました。
bullet gemとか入れてるとinclude使えとアラートがピコピコ出るのでその都度includeしたりしています。

Rails4.0.3まではコントローラにこう書いていて動いたのですが、

Article.paginate(page: params[:page], per_page: 10, include: [:image])

Rails4.1ではエラーが...

undefined method `apply_finder_options' for #<ActiveRecord::AssociationRelation []>

こうしたら動きました。

Article.paginate(page: params[:page], per_page: 10).includes(:image)

アップグレードガイドには書いてなかったような...?

リモートホストとファイルのやりとりをする時に便利なscpコマンドのメモ

ファイルコピー

$ scp ${user}@${remote}:~/file .

ワイルドカード展開

$ scp ${user}@${remote}:"~/test.*" .

ディレクトリコピー

  • rオプションを使用する
$ scp -r ${user}@${remote}:~/test .

WEBrickで簡易Webサーバ

ちょっと実験とか試したいときに便利

使用したパラメータ

  • Port: 使用するポート番号
  • DocumentRoot: ドキュメントのルートパス

その他のパラメータはこちらを参照
Module: WEBrick (Ruby 2.0.0)

require "webrick"

params = {
  Port: 8888,
  DocumentRoot: File.expand_path(File.dirname(__FILE__))
}

server = WEBrick::HTTPServer.new(params)
trap("INT") { server.shutdown }
server.start

fluent-plugin-http-ex をリリースしました!

Fluentd in_http プラグインにデータを送る際、ある程度データをまとめて送ったり、in_forward のように高速に処理させたかったので in_http にいくつか機能を追加した in_http_ex をリリースしました!!

  • github

https://github.com/hiro-su/fluent-plugin-http-ex

  • rubugems

https://rubygems.org/gems/fluent-plugin-http-ex

in_httpからの変更点

1. 受信フォーマットに list と chunked の追加

受信フォーマットに list と chunked を追加。

  • JSON list の場合
'[{"action":"login","user":2},{"action":"login","user":2}]'
  • MessagePack list の場合
record = {"action" => "login", "user" => 2}
[record,record].to_msgpack
  • chunked の場合

HTTP1.1の Transfer-Encoding: chunked でデータを受取ります。chunkedはMessagePack形式の物だけを想定しています。以下、リクエストヘッダ部分だけを抜粋。

POST /ms/test.tag HTTP/1.1
Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept: */*
User-Agent: Ruby
Content-Type: application/x-msgpack
Transfer-Encoding: chunked
Connection: Keep-Alive
Host: localhost:8888
2. content-typeの追加

Content-Type ヘッダフィールドに application/x-msgpack を追加しました。msgpack形式時は application/x-www-form-urlencoded よりBodyにパラメータを付けない分、速度面で多少有利な気がします。

3. tagの前にresourceを追加

resourceにより受けるフォーマットを分離。

  • resource無し

in_httpと同じ形式です。

http://localhost:8888/tag.here
  • json

jが1レコードのjsonでjsがlistを受けます。

http://localhost:8888/j/tag.here
http://localhost:8888/js/tag.here
  • msgpack

mが1レコードのMessagePackでmsがlist, chunkedを受けます。

http://localhost:8888/m/tag.here
http://localhost:8888/ms/tag.here
4. keepalive_timeout

keepalive_timeout 0sでコネクションが切れないように変更

簡単な使い方

例えば、JSON list形式

$ curl -X POST -H 'Content-Type: application/json' -d '[{"action":"login","user":2},{"action":"login","user":2}]' \
    http://localhost:8888/js/test.tag.here;

Fluentd側では以下のように受信します。

2013-08-07 19:42:22 +0900 test.tag.here: {"action":"login","user":2}
2013-08-07 19:42:22 +0900 test.tag.here: {"action":"login","user":2}

パフォーマンス

簡単にですが、それぞれのフォーマットの速度比較をしてみました。
in_http と in_http_ex に対して 10,000 record を送ります。
使用したコードはexamples以下に起きました。
https://github.com/hiro-su/fluent-plugin-http-ex/tree/master/examples

  • マシンスペック
Mac OS X 10.8.2
1.8 GHz Intel Core i5
8 GB 1600 MHz DDR3
コンフィグ
<source>
  type http
  port 7777
  bind 0.0.0.0
</source>

<source>
  type http_ex
  port 8888
  bind 0.0.0.0
  body_size_limit 300M
  keepalive_timeout 0s
</source>

<match **>
  type stdout
</match>
結果

in_http: json

$ time ruby examples/json.rb

real    2m27.480s
user    0m7.252s
sys     0m4.438s

in_http: msgpack

$ time ruby examples/msgpack.rb

real    2m36.408s
user    0m8.249s
sys     0m4.441s

in_http_ex: json

$ time ruby examples/json.rb

real    2m30.639s
user    0m7.195s
sys     0m4.686s

in_http_ex: msgpack

$ time ruby examples/msgpack.rb

real    2m28.442s
user    0m7.126s
sys     0m4.324s

in_http_ex: json list

$ time ruby examples/json_list.rb

real    0m18.179s
user    0m0.872s
sys     0m0.477s

in_http_ex: msgpack list

$ time ruby examples/msgpack_list.rb

real    0m13.787s
user    0m0.908s
sys     0m0.470s

in_http_ex: chunked

$ time ruby examples/nc_chunked.rb

real    0m1.584s
user    0m0.244s
sys     0m0.107s

さいごに

簡単なパフォーマンス測定でしたが、 json < msgpack < json list < mgpack list < msgpack chunked のような結果になりました。msgpackのchunked形式がやはり速かったです。バッチ処理的に1日分のファイル内容をFluentdに対して送信したりする時に良さそうです。

ただ、個人的に一番取り扱いが良さそうなのは、json listとmsgpack listだと思っています。
また、バグや改善点など有りましたらどんどん pull req して頂ければと思います!

HTTP1.1のTransfer-Encoding: chunkedを試してみる

RFC2616 Transfer-Encoding: chunked を試すために、
curlとかnetcatとかrubyで色々やってみます。

仕様

とりあえず仕様を確認してみます。

Chunked-Body   = *chunk
                 last-chunk
                 trailer
                 CRLF

chunk          = chunk-size [ chunk-extension ] CRLF
                 chunk-data CRLF
chunk-size     = 1*HEX
last-chunk     = 1*("0") [ chunk-extension ] CRLF

chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val  = token | quoted-string
chunk-data     = chunk-size(OCTET)
trailer        = *(entity-header CRLF)

curl

curlからデータを送ってみます。

server

netcatでサーバを起動

$ nc -l 8888
curl client

headerにchunkedやkeepaliveの情報を付加すればchunkedになると聞いてやってみます。

$ vim hoge.txt
hogehoge
fugafuga

$ curl -X POST -T "hoge.txt" -H 'Content-type: text/plain' --header "Transfer-Encoding: chunked" --header "Connection: Keep-Alive" http://localhost:8888/resource/test
request

server側にrequestが来ました。
bodyに16進数でsizeが入っています。0CRLFCRLFで1chunkと見るようです。

ただ、bodyの後に余計にCRLFが1つ入ってしまっています。仕様を見た限りでは必要無さそうです。
また、host.txtの内容を増やしていくと、どこかのタイミングでchunkが分割されるようです。

POST /resource/test HTTP/1.1
User-Agent: curl/7.28.0
Host: localhost:8888
Accept: */*
Content-type: text/plain
Transfer-Encoding: chunked
Connection: Keep-Alive
Expect: 100-continue

12
hogehoge
fugafuga

0

netcat

clientにもnetcatを使ってみます。

server

netcatでサーバを起動

$ nc -l 8888
client

netcatクライアント
curlのrequestを真似しました。

$ host=localhost
$ port=8888
$ body=hogehoge\\nfugafuga
$ size=`expr ${#body} | xargs -I{} printf '%x\n' {}`
$ echo "POST /resource/test HTTP/1.1\r\nHost: ${host}:${port}\r\nContent-type: text/plain\r\nTransfer-Encoding: chunked\r\nConnection: Keep-Alive\r\nExpect: 100-continue\r\n\r\n${size}\r\n${body}\r\n0\r\n\r\n" | nc $host $port
request

server側にrequestが来ました。
netcatでは、標準入力が閉じられるとコネクションが閉じられるようです。

POST /resource/test HTTP/1.1
Host: localhost:8888
Content-type: text/plain
Transfer-Encoding: chunked
Connection: Keep-Alive
Expect: 100-continue

12
hogehoge
fugafuga
0

ruby

最後にrubyからもデータを送ってみます。
といってもクライアントはnetcatをpipeで開いて送っています。

tcp server

request内容を出力するTCP Server

$ vim tcpserver.rb
require "socket"

c = TCPServer.new(7777)

while true
  sock = c.accept
  while buf = sock.gets
    puts buf
  end
  sock.close
end

c.close

$ ruby tcpserver.rb
client

Rubyからnetcatをパイプで開いてデータを送ってみます。
1度headerを投げたあとに10回bodyを送ってコネクションを切ります。

require 'open3'

class ChunkTest
  def initialize(host, port)
    @host = host
    @port = port
    @cmd = "nc"
  end

  def run
    Open3.popen3("#{@cmd} #{@host} #{@port}") do |stdin, stdout, stderr, wait_thr|
      begin
        i = 0
        loop do
          body = "hogehoge#{i}\nfugafuga#{i}"
          size = body.size
          head = \
            "POST /resource/test HTTP/1.1\r\nUser-Agent: curl/7.28.0\r\nHost: #{@host}:#{@port}\r\nContent-type: text/plain\r\nTransfer-Encoding: chunked\r\nConnection: Keep-Alive\r\nExpect: 100-continue\r\n\r\n"
          if i == 0
            stdin << head
          elsif i > 10
            stdin << "0\r\n\r\n"
            break
          else
            chunk = "#{size.to_s(16)}\r\n#{body}\r\n"
            stdin << chunk
          end
          i += 1
        end
      ensure
        stdin.close
      end
    end
  end
end

chunk = ChunkTest.new('localhost', 8888)
chunk.run
request
POST /resource/test HTTP/1.1
User-Agent: curl/7.28.0     
Host: localhost:8888       
Content-type: text/plain    
Transfer-Encoding: chunked  
Connection: Keep-Alive      
Expect: 100-continue        
                            
13                          
hogehoge1                   
fugafuga1                   
13                          
hogehoge2                   
fugafuga2                   
13                          
hogehoge3                   
fugafuga3                   
13                          
hogehoge4                   
fugafuga4                   
13                          
hogehoge5                   
fugafuga5                   
13                          
hogehoge6                   
fugafuga6                   
13                          
hogehoge7                   
fugafuga7                   
13                          
hogehoge8                   
fugafuga8                   
13                          
hogehoge9                   
fugafuga9                   
15                          
hogehoge10                  
fugafuga10   
0    
           
chunked server

以上の実験でchunkedでのデータのやり取りが分かってきました。
サーバーにcool.ioを使用してchunkedなrequestを処理してみます。
受け取ったrequest bodyを画面出力します。

require 'cool.io'
require 'http_parser'

ADDR = '127.0.0.1'
PORT = 8888

class EchoServerConnection < Cool.io::TCPSocket
  def on_connect
    puts "#{remote_addr}:#{remote_port} connected"
    @parser = Http::Parser.new(self)
  end

   def on_message_begin
    @headers = nil
  end

  def on_close
    puts "#{remote_addr}:#{remote_port} disconnected"
  end

  def on_headers_complete(headers)
    @headers = headers
  end

  def on_read(data)
    @parser << data
  end

  def on_body(data)
    puts data
  end
 end

event_loop = Cool.io::Loop.default
Cool.io::TCPServer.new(ADDR, PORT, EchoServerConnection).attach(event_loop)

puts "Echo server listening on #{ADDR}:#{PORT}"
event_loop.run
request

無事にbodyを取り出せました。

$ ruby coolio.rb
Echo server listening on 127.0.0.1:8888
127.0.0.1:49895 connected
hogehoge1
fugafuga1
hogehoge2
fugafuga2
hogehoge3
fugafuga3
hogehoge4
fugafuga4
hogehoge5
fugafuga5
hogehoge6
fugafuga6
hogehoge7
fugafuga7
hogehoge8
fugafuga8
hogehoge9
fugafuga9
hogehoge10
fugafuga10
127.0.0.1:49895 disconnected

終わりに

HTTP1.1のTransfer-Encoding: chunkedがなかなか分からず苦労しました。
間違っている部分があれば、ご指摘ご助言等頂けると幸いです。

Rubyでデーモンを作ってみる

Rubyでデーモンを作ってみます。
test.txtファイルに1秒ごとに"test"と書き続けるデーモンです。

# daemon.rb
require 'fileutils'
require 'logger'

class DaemonTest
  def initialize
    @term = false
    @logger = Logger.new(STDOUT)
    @logger.info "daemon start..."
    @pid_file_path = './daemon.pid'
    @file = "./test.txt"
  end

  def execute
    File.open(@file, "w") do |f|
      loop do
        f.puts "test"
        f.flush
        break if @term
        sleep 1
      end
    end
  end

  def run
    daemonize
    begin
      Signal.trap(:TERM) { shutdown }
      Signal.trap(:INT) { shutdown }
      execute
    rescue => ex
      @logger.error ex
    end
  end

  def shutdown
    @term = true
    @logger.info "daemon close..."
    @logger.close
    FileUtils.rm @pid_file_path
  end

  def daemonize
    exit!(0) if Process.fork
    Process.setsid
    exit!(0) if Process.fork
    open_pid_file
  end

  def open_pid_file
    begin
      open(@pid_file_path, 'w') {|f| f << Process.pid } if @pid_file_path
    rescue => ex
      @logger.error "could not open pid file (#{@pid_file_path})"
      @logger.error "error: #{ex}"
      @logger.error ex.backtrace * "\n"
    end
  end
end

DaemonTest.new.run

起動

$ ruby daemon.rb

停止

$ cat daemon.pid | xargs kill