abコマンドのベンチマークパターンを書けるab-mrubyを作ったを昨日書いたわけですが、今日はab-mrubyに対して、ベンチマーク後の結果からテストをRubyで書ける機能を追加しました。
ab-mrubyのgithubのREADMEに大体書き方は書いていますが、ここでも簡単に紹介したいと思います。
ベンチマークの実行とテスト
基本的にはabコマンドにmrubyを組み込む事で、引数のURL毎に動的にベンチマークパターンを決定したり、その後のベンチマーク結果からテストを書けるようにしています。以下のように実行します。
[program lang=’bash’ escaped=’true’]
./ab-mruby -m ab-mruby.conf.rb -M ab-mruby.test.rb http://192.168.12.251/
[/program]
mオプションにベンチマークパターンファイルを、Mオプションにテストケースファイルを指定します。
ベンチマークパターンの書き方
abコマンドのベンチマークパターンを書けるab-mrubyを作ったでも詳しく書いていますが、以下のように書きます。ab-mrubyの引数に渡されるURLから動的にベンチマークパターンを決定するように書くと良いでしょう。
例えば以下のように書きます。
[program lang=’ruby’ escaped=’true’]
# # Usage: ./ab-mruby -m ab-mruby.conf.rb -M ab-mruby.test.rb[http[s]://]hostname[:port]/path # # add_config( # "TotalRequests" => 100, # int # "Concurrency" => 10, # int max 20000 # "KeepAlive" => true, # true or false or nil # "VerboseLevel" => 1, # int 1 ~ 5 # "ShowProgress" => true, # true, false or nil # "ShowPercentile" => true, # true, false or nil # "ShowConfidence" => true, # true, false or nil # "WaitSocketError" => true, # true, false or nil # "RequestTimeOut" => 30, # int sec # "BechmarkTimelimit" => 50000, # int sec # "WindowSize" => nil, # int byte # "HeadMethodOnly" => false, # true, false or nil # "Postfile" => nil, # './post.txt', # "Putfile" => nil, # './put.txt', # "ContentType" => nil, # 'application/x-www-form-urlencoded', # "OutputGnuplotFile" => nil, # './gnu.txt' # "OutputCSVFile" => nil, # './csv.txt' # "AddCookie" => nil, # 'Apache=1234' # "AddHeader" => 'User-Agent: ab-mruby', # 'User-Agent: test' # "BasicAuth" => nil, # 'user:pass' # "Proxy" => nil, # 'proxy[:port]' # "ProxyAuth" => nil, # 'user:pass' # "OutputHtml" => false, # true, false or nil # "BindAddress" => nil, # 'matsumoto-r.jp' # "SSLCipher" => 'DHE-RSA-AES128-SHA', # 'DHE-RSA-AES256-SHA' or get from [openssl ciphers -v] # "SSLProtocol" => 'SSL3', # 'SSL2', 'SSL3', 'TLS1', 'TLS1.1', 'TLS1.2' or 'ALL' # ) # print ab-mruby headers print <<EOS ====================================================================== This is ab-mruby using ApacheBench Version 2.3 <$Revision: 1430300 $> Licensed to MATSUMOTO Ryosuke, https://github.com/matsumoto-r/ab-mruby CONFIG PHASE ====================================================================== EOS # C側からこんなデータが得られるのでこれを元に動的にパターンを記述 p get_config("TargetURL").to_s p get_config("TargetPort").to_s p get_config("TargetHost").to_s p get_config("TargetPath").to_s p get_config("TargetisSSL").to_s # defined config pattern if get_config("TargetHost").to_s == "blog.example.jp" add_config( "TotalRequests" => 10, # int "Concurrency" => 1, # int max 20000 "KeepAlive" => true, # true or false or nil "VerboseLevel" => 1, # int 1 ~ 5 "ShowProgress" => true, # true, false or nil "ShowPercentile" => true, # true, false or nil "ShowConfidence" => true, # true, false or nil "WaitSocketError" => true, # true, false or nil "RequestTimeOut" => 30, # int sec "BechmarkTimelimit" => 50000, # int sec "WindowSize" => nil, # int byte "HeadMethodOnly" => false, # true, false or nil "Postfile" => nil, # './post.txt', "Putfile" => nil, # './put.txt', "ContentType" => nil, # 'application/x-www-form-urlencoded', "OutputGnuplotFile" => nil, # './gnu.txt' "OutputCSVFile" => nil, # './csv.txt' "AddCookie" => nil, # 'Apache=1234' "AddHeader" => 'User-Agent: ab-blog', # 'User-Agent: test' "BasicAuth" => nil, # 'user:pass' "Proxy" => nil, # 'proxy[:port]' "ProxyAuth" => nil, # 'user:pass' "OutputHtml" => false, # true, false or nil "BindAddress" => nil, # 'matsumoto-r.jp' "SSLCipher" => nil, # 'DHE-RSA-AES256-SHA' or get from [openssl ciphers -v] "SSLProtocol" => nil, # 'SSL2', 'SSL3', 'TLS1', 'TLS1.1', 'TLS1.2' or 'ALL' ) elsif get_config("TargetHost").to_s == "moblog.example.jp" add_config( "TotalRequests" => 20, # int "Concurrency" => 5, # int max 20000 "KeepAlive" => false, # true or false or nil "VerboseLevel" => 5, # int 1 ~ 5 "ShowProgress" => true, # true, false or nil "ShowPercentile" => true, # true, false or nil "ShowConfidence" => true, # true, false or nil "WaitSocketError" => true, # true, false or nil "RequestTimeOut" => 30, # int sec "BechmarkTimelimit" => 50000, # int sec "WindowSize" => nil, # int byte "HeadMethodOnly" => false, # true, false or nil "Postfile" => nil, # './post.txt', "Putfile" => nil, # './put.txt', "ContentType" => nil, # 'application/x-www-form-urlencoded', "OutputGnuplotFile" => nil, # './gnu.txt' "OutputCSVFile" => nil, # './csv.txt' "AddCookie" => nil, # 'Apache=1234' "AddHeader" => 'User-Agent: ab-moblog', # 'User-Agent: test' "BasicAuth" => nil, # 'user:pass' "Proxy" => nil, # 'proxy[:port]' "ProxyAuth" => nil, # 'user:pass' "OutputHtml" => false, # true, false or nil "BindAddress" => nil, # 'matsumoto-r.jp' "SSLCipher" => nil, # 'DHE-RSA-AES256-SHA' or get from [openssl ciphers -v] "SSLProtocol" => nil, # 'SSL2', 'SSL3', 'TLS1', 'TLS1.1', 'TLS1.2' or 'ALL' ) else add_config( "TotalRequests" => 100, # int "Concurrency" => 10, # int max 20000 "KeepAlive" => false, # true or false or nil "VerboseLevel" => 1, # int 1 ~ 5 ) end if get_config("TargetisSSL") add_config( "SSLCipher" => 'DHE-RSA-AES128-SHA', # 'DHE-RSA-AES256-SHA' or get from [openssl ciphers -v] "SSLProtocol" => 'SSL3', # 'SSL2', 'SSL3', 'TLS1', 'TLS1.1', 'TLS1.2' or 'ALL' ) end
[/program]
上記の詳しい説明は、abコマンドのベンチマークパターンを書けるab-mrubyを作ったを御覧ください。URLから得られる情報を元にベンチマークパターンを定義しているのがわかると思います。
テストスイートの書き方
次に、abコマンドベースのベンチマークの結果からテストを行いたい場合に、Rubyでテストケースを書いておいて、それをab-mrubyに渡す事ができます。それによって、自動的にベンチマーク結果からテストケースを元にテストを行い、即座に結果を出力してくれます。
テストスイートは例えば以下のように書くことができます。
[program lang=’ruby’ escaped=’true’]
# # Usage: ./ab-mruby -m ab-mruby.conf.rb -M ab-mruby.text.rb [http[s]://]hostname[:port]/path # # TEST PARAMETERS # # "TargetURL" # "TargetHost" # "TargetPort" # "TargetPath" # "TargetisSSL" # "TargetServerSoftware" # "TargetServerHost" # "TargetServerPort" # "TargetServerSSLInfo" # if use SSL # "TargetDocumentPath" # "TargetDocumentLength" # "TimeTakenforTests" # "CompleteRequests" # "FailedRequests" # "ConnetcErrors" # if FailedRequests > 0 # "ReceiveErrors" # if FailedRequests > 0 # "LengthErrors" # if FailedRequests > 0 # "ExceptionsErrors" # if FailedRequests > 0 # "WriteErrors" # "Non2xxResponses" # if Non2xxResponse > 0 # "KeepAliveRequests" # "TotalTransferred" # "TotalBodySent" # if body send # "HTMLTransferred" # "RequestPerSecond" # "TimePerConcurrentRequest" # "TimePerRequest" # "TransferRate" # # print ab-mruby headers print <<EOS ====================================================================== This is ab-mruby using ApacheBench Version 2.3 <$Revision: 1430300 $> Licensed to MATSUMOTO Ryosuke, https://github.com/matsumoto-r/ab-mruby TEST PHASE ====================================================================== EOS module Kernel def test_suite &blk @@r = get_config @@t = blk end def should_be val puts "[TEST CASE] #{self} (#{@@r[self]}) should be #{val}: #{@@r[self] == val}" end def should_be_over val puts "[TEST CASE] #{self} (#{@@r[self]}) should be over #{val}: #{@@r[self] > val}" end def should_be_under val puts "[TEST CASE] #{self} (#{@@r[self]}) should be under #{val}: #{@@r[self] < val}" end def test_run @@t.call end end # define test suite test_suite do "TargetServerHost".should_be "192.168.12.251" "TargetServerPort".should_be 80 "TargetDocumentPath".should_be "/" "TargetServerSoftware".should_be "Apache/2.4.4" "FailedRequests".should_be 0 "KeepAliveRequests".should_be 0 "WriteErrors".should_be 0 "HTMLTransferred".should_be 600 "TargetDocumentLength".should_be 6 "TotalTransferred".should_be 27500 "CompleteRequests".should_be 100 "TransferRate".should_be_over 460 "TimeTakenforTests".should_be_under 0.015 "RequestPerSecond".should_be_over 1000 "TimePerRequest".should_be_under 0.5 "TimePerConcurrentRequest".should_be_under 5 "TotalBodySent".should_be 0 "ConnetcErrors".should_be 0 "ReceiveErrors".should_be 0 "LengthErrors".should_be 0 "ExceptionsErrors".should_be 0 "Non2xxResponses".should_be 0 end test_run
[/program]では、細かく見て行きましょう。
まず、テストスイートを解析するようなメソッドを下記のように定義しておきます。これはあくまでサンプルなので、Ruby力の高い人はもっと良い感じのメソッドを定義すると良いと思います。基本はget_configメソッドにより、ベンチマーク結果のハッシュをC言語側であるab-mrubyからまとめて取得することができます。
[program lang=’ruby’ escaped=’true’]
module Kernel def test_suite &blk @@r = get_config @@t = blk end def should_be val puts "[TEST CASE] #{self} (#{@@r[self]}) should be #{val}: #{@@r[self] == val}" end def should_be_over val puts "[TEST CASE] #{self} (#{@@r[self]}) should be over #{val}: #{@@r[self] > val}" end def should_be_under val puts "[TEST CASE] #{self} (#{@@r[self]}) should be under #{val}: #{@@r[self] < val}" end def test_run @@t.call end end
[/program]
その上で、好きなテストケースを書きます。僕は以下のように書いてみました。
[program lang=’ruby’ escaped=’true’]
test_suite do "TargetServerHost".should_be "192.168.12.251" "TargetServerPort".should_be 80 "TargetDocumentPath".should_be "/" "TargetServerSoftware".should_be "Apache/2.4.4" "FailedRequests".should_be 0 "KeepAliveRequests".should_be 0 "WriteErrors".should_be 0 "HTMLTransferred".should_be 600 "TargetDocumentLength".should_be 6 "TotalTransferred".should_be 27500 "CompleteRequests".should_be 100 "TransferRate".should_be_over 460 "TimeTakenforTests".should_be_under 0.015 "RequestPerSecond".should_be_over 1000 "TimePerRequest".should_be_under 0.5 "TimePerConcurrentRequest".should_be_under 5 "TotalBodySent".should_be 0 "ConnetcErrors".should_be 0 "ReceiveErrors".should_be 0 "LengthErrors".should_be 0 "ExceptionsErrors".should_be 0 "Non2xxResponses".should_be 0 end test_run
[/program]
このように少しだけRSpecをぱくっ…インスパイアした書き方にして、テストケースを書いてみました。should_beメソッドとshould_be_over、underメソッドは、読んで時の如く、引数と同じか数値的に高いか低いを単純にテストするためのメソッドです。
実際にベンチマークとテストを実施してみる
では、上記のベンチマークパターンとテストケースをab-mrubyに渡して、HTTPのベンチマークを行なってみましょう。
以下が実行結果になります。
[program lang=’ruby’ escaped=’true’]
$ ./ab-mruby -m ab-mruby.conf.rb -M ab-mruby.test.rb http://192.168.12.251/ ====================================================================== This is ab-mruby using ApacheBench Version 2.3 <$Revision: 1430300 $> Licensed to MATSUMOTO Ryosuke, https://github.com/matsumoto-r/ab-mruby CONFIG PHASE ====================================================================== "http://192.168.12.251/" "80" "192.168.12.251" "/" "false" This is ApacheBench, Version 2.3-mruby <$Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.12.251 (be patient).....done Server Software: Apache/2.4.4 Server Hostname: 192.168.12.251 Server Port: 80 Document Path: / Document Length: 6 bytes Concurrency Level: 10 Time taken for tests: 0.015 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 27500 bytes HTML transferred: 600 bytes Requests per second: 6839.48 [#/sec] (mean) Time per request: 1.462 [ms] (mean) Time per request: 0.146 [ms] (mean, across all concurrent requests) Transfer rate: 1836.77 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 0 Processing: 1 1 0.3 1 2 Waiting: 1 1 0.3 1 2 Total: 1 1 0.3 1 3 Percentage of the requests served within a certain time (ms) 50% 1 66% 2 75% 2 80% 2 90% 2 95% 2 98% 2 99% 3 100% 3 (longest request) ====================================================================== This is ab-mruby using ApacheBench Version 2.3 <$Revision: 1430300 $> Licensed to MATSUMOTO Ryosuke, https://github.com/matsumoto-r/ab-mruby TEST PHASE ====================================================================== [TEST CASE] TargetServerHost (192.168.12.251) should be 192.168.12.251: true [TEST CASE] TargetServerPort (80) should be 80: true [TEST CASE] TargetDocumentPath (/) should be /: true [TEST CASE] TargetServerSoftware (Apache/2.4.4) should be Apache/2.4.4: true [TEST CASE] FailedRequests (0) should be 0: true [TEST CASE] KeepAliveRequests (0) should be 0: true [TEST CASE] WriteErrors (0) should be 0: true [TEST CASE] HTMLTransferred (600) should be 600: true [TEST CASE] TargetDocumentLength (6) should be 6: true [TEST CASE] TotalTransferred (27500) should be 27500: true [TEST CASE] CompleteRequests (100) should be 100: true [TEST CASE] TransferRate (1836.7737329) should be over 460: true [TEST CASE] TimeTakenforTests (0.014621) should be under 0.015: true [TEST CASE] RequestPerSecond (6839.4774639) should be over 1000: true [TEST CASE] TimePerRequest (0.14621) should be under 0.5: true [TEST CASE] TimePerConcurrentRequest (1.4621) should be under 5: true [TEST CASE] TotalBodySent (0) should be 0: true [TEST CASE] ConnetcErrors (0) should be 0: true [TEST CASE] ReceiveErrors (0) should be 0: true [TEST CASE] LengthErrors (0) should be 0: true [TEST CASE] ExceptionsErrors (0) should be 0: true [TEST CASE] Non2xxResponses (0) should be 0: true
[/program]今回は、上記のベンチマークパターンにおいては、URLがhttp://192.168.12.251/なので、ベンチマークパターンは以下になりますね。
[program lang=’ruby’ escaped=’true’]
else add_config( "TotalRequests" => 100, # int "Concurrency" => 10, # int max 20000 "KeepAlive" => false, # true or false or nil "VerboseLevel" => 1, # int 1 ~ 5 ) end
[/program]
ちゃんと上記の通り、ベンチマークをしていることがベンチマーク結果から分かります。さらにその結果を用いて、テストを行なっている事がわかります。今回は全てtrueになって、テストが全て通ったことになりますね!
また、今回のテストは一番シンプルなものにしているので、get_configで得られるパラメータやベンチマーク結果の値から、URLやパス情報を元に動的にテストケースを書いておくことも可能なので、テストの書き方にも夢が広がると思います。
最後に
このように、HTTPのベンチマークにおいて、汎用的にパターンとテストを記述できるツールを探していいたのですが、あまりシンプルで良いものがなかったので自分で作ってみました。
また、abコマンドをベースにしていることから、abでベンチマークをするような状況においては、同様にab-mrubyを使えるので、今後HTTPサーバのテストも捗るのではないでしょうか。
とりあえず、既存のabよりは大分使いやすく、ベンチマークパターンやテストの可読性向上、ベンチマークパターンとベンチマークとテストの連携が楽になったので、abを使うような場面ではab-mrubyを使っていこうと思います。