使用可能なディレクトリ容量を制限する
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)
アップグレードガイドには書いてなかったような...?
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