2010/04/01


rubyのmongrelやthinなんかに変わるhttpdでUnicornとやらがあるのを知った。
ひとまずベンチ取ってみた。rubyでやる専用なのでなにかアプリを書かないと動かないのかな?とりあえずsinatraアプリ書いた。

unicorn-test.rb
require 'rubygems'
require 'sinatra'
set :public, File.dirname(__FILE__)
config.ru
require 'unicorn-test'
run Sinatra::Application
で、unicorn実行。ベンチは
# ab -n 1000 -c 10 -k http://localhost:8080/helloworld.html 2>&1 | tee unicorn.log
で確認した。

unicornの結果

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /helloworld.html
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   5.385 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    0
Total transferred:      193386 bytes
HTML transferred:       11022 bytes
Requests per second:    185.69 [#/sec] (mean)
Time per request:       53.855 [ms] (mean)
Time per request:       5.385 [ms] (mean, across all concurrent requests)
Transfer rate:          35.07 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:    12   53  43.2     33     260
Waiting:       11   53  43.1     32     260
Total:         12   54  43.1     33     260

Percentage of the requests served within a certain time (ms)
  50%     33
  66%     42
  75%     56
  80%    106
  90%    119
  95%    140
  98%    176
  99%    212
 100%    260 (longest request)
一応、prefork付きのapache2でも...
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        Apache/2.2.12
Server Hostname:        localhost
Server Port:            80

Document Path:          /helloworld.html
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.341 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    996
Total transferred:      322825 bytes
HTML transferred:       11000 bytes
Requests per second:    2931.25 [#/sec] (mean)
Time per request:       3.412 [ms] (mean)
Time per request:       0.341 [ms] (mean, across all concurrent requests)
Transfer rate:          924.10 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     0    3   7.8      0      40
Waiting:        0    3   7.8      0      40
Total:          0    3   7.8      0      40

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%     18
  95%     21
  98%     32
  99%     35
 100%     40 (longest request)
いちおうのいちおうでtthttpd(tinytinyhttpd)でも
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8081

Document Path:          /helloworld.html
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.431 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    1000
Total transferred:      183000 bytes
HTML transferred:       11000 bytes
Requests per second:    2321.65 [#/sec] (mean)
Time per request:       4.307 [ms] (mean)
Time per request:       0.431 [ms] (mean, across all concurrent requests)
Transfer rate:          414.90 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     0    4  15.1      0     241
Waiting:        0    4  15.0      0     241
Total:          0    4  15.1      0     241

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%      7
  95%     29
  98%     44
  99%     54
 100%    241 (longest request)

結論: preforkつえー。


えっ?そういう調査でしたっけ...
Posted at by



2010/03/25


twitter stream APIとは、twitterのステータス更新に対してキーワードでtrackしたり、あるグループ内に属するステータス更新をフィルタしたり出来るAPIなのだけど、実際にはchunkedなストリームが流れて来ているのであって、これを使ったWebアプリを作る際にはlong pollを使うのが良い。ただしクライアントサイドでjavascriptを処理する際に
  • サーバサイドでステータス更新をFIFOに溜め込む
  • クライアントからリクエストをブロック(long poll)しFIFOからステータスを送出する
  • クライアントサイドでlong pollを行い画面を更新する
  • 再度サーバへリクエストを投げる
を繰り返すのであれば、せっかくストリームなのにアプリサーバとの接続を切ってしまう事になる。出来ればクライアントからWebアプリもストリーミングとしたい。しかしながらサーバからのステータス更新を受け取るのであれば、JSON/JSONPなアクションが必要になる。
ここで使える!と思ったのがmultipart/mixedです。

multipart/mixedはboundaryで区切られたボディ部を繰り返し送出する際に用いられる仕組みで、実はXHR(XMLHttpRequest)はこのmultipart/mixedなレスポンスを受け取る事が出来る。
受け取るとは言っても、実際にはreadyStateが変化する...でしかないのだけど、これを扱うのに良いライブラリがある。
diggで使われているDUI.jsとStream.jsです。
digg's stream at master - GitHub

Alpha repository for DUI.Stream. When it stabilizes, it will be merged back into DUI

http://github.com/digg/stream
このstreamライブラリは内部でreadyStateが3になるのを待ち、タイマを張ってboundaryを監視するものです。つまり1 body部を扱う事になるので、使う際にはmime typeを指定する事になります。
var s = new DUI.Stream();
s.listen('text/html', function(payload) {
  ...
});
s.load('/push');
この仕組みを使って、twitterからのストリームをクライアントにmultipart/mixedで送出し、かつクライアントも接続を保持したままレスポンスを受け取るのです。接続を切らないので、ほぼタイムロス無しのリアルタイムと言って良いでしょう。
今日はこれをsinatraを使って実装してみました。出来れば一度動かしてみて下さい。これまでリクエストを繰り返し送っていたアプリケーションと比べてスムーズにデータを受け取っているのが分かって頂けるかと思います。
上記diggのライブラリを作業フォルダのstatic/jsフォルダ配下に置き、以下のファイル(fast-twitter-stream.rb)を作業フォルダ直下に置きます。
require 'rubygems'
require 'sinatra'
require 'json'  
require 'tweetstream'  
require 'pit'

# doesn't work on thin and webrick
set :server, 'mongrel'
set :public, File.dirname(__FILE__) + '/static'

config = Pit.get("twitter.com", :require => {
    "username" => "your username in twitter",
    "password" => "your password in twitter"
})

get '/' do
  <<HTML
  <html> <head>
    <title>Server Push</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
    <script type="text/javascript" src="/js/DUI.js"></script>
    <script type="text/javascript" src="/js/Stream.js"></script>
    <script type="text/javascript">
    $(function() {
      var s = new DUI.Stream();
      s.listen('text/javascript', function(payload) {
        var status = eval(payload);
        $('#content').append('<p>' + status.text + '</p>');
      });
      s.load('/push');
    });
    </script>
  </head>
  <body>
    <h1>Server Push</h1>
    <div id="content"></div>
  </body>
</html>
HTML
end

get '/push' do
  boundary = '|||'
  response['Content-Type'] = 'multipart/mixed; boundary="' + boundary + '"'

  MultipartResponse.new(boundary, 'text/javascript')
end

class MultipartResponse
  def initialize(boundary, content_type)
    @boundary = boundary
    @content_type = content_type
  end

  def each
    TweetStream::Client.new(config[:username], config[:password]).sample do |status|  
      yield "--#{@boundary}\nContent-Type: #{@content_type}\n(#{status.to_json})"
    end  
  end
end
元となったコードはyoupyさんの物を参考にさせて頂きました。

ブラウザから"http://localhost:4567/"にアクセスすると、もの凄い勢いでステータスが追加されていくのが分かるかと思います。しかも切断していないのでかなり高速です。
お試しの効果には個人差があります。

このmultipart/mixed、結構使えるなーと思いました。得にリアルタイムな画像ストリーミングとして送出したり、リアルタイム電光掲示板の様な物にも使えるかと思います。

今回のサンプルもgithubに置いてあります。
mattn's fast-twitter-stream at master - GitHub

fast twitter stream client using multipart/mixed.

http://github.com/mattn/fast-twitter-stream
よろしければご参考までに。

Posted at by



2010/03/24


もう既出感丸出しですが...
リロード如きでソケットサーバとか使うの嫌だったのでWindows API使った。

function! s:UpdateBrowser()
  python<<EOM
import win32gui,win32api,win32con
hwnd = win32gui.FindWindow("MozillaUIWindowClass", None)
hwnd = win32gui.FindWindowEx(hwnd, 0, "MozillaWindowClass", None)
win32gui.SendMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
EOM
endfunction

function! s:StartEditing()
  augroup HtmlEditing
    au!
    autocmd BufWritePost,FileWritePost *.html call s:UpdateBrowser()
  augroup END
  exec "!start c:/progra~1/mozill~1/firefox.exe " . expand('%:p')
endfunction

function! s:StopEditing()
  augroup HtmlEditing
    au!
  augroup END
endfunction

command! HtmlStartEditing call s:StartEditing()
command! HtmlStopEditing call s:StopEditing()
Windowsで、しかもFirefoxでしか動かない。「:HtmlStartEditing」で開始、「:HtmlStopEditing」で停止です。ファイルが更新される度にブラウザ(Firefox)にF5を送信します。Firefoxのパスとか適当に...
書いて5分程使ってみたけど、まぁまぁな感じ。いつかもうちょっと弄って使うかも。

同じ方法でchromeも出来るかもしれない。
Posted at by