お客様「このメールフォームは同時に何人がつかえますか?」 私「メールフォームってそういう性能が必要なものでしたっけ??」
ということで、負荷テストです。
私はメールフォーム制作が本職なのではないかと思う程度に普段からメールフォームをつくっている零細メールフォーマーです。
ただ、私ごときが担当するメールフォームなんてそこまでスループットの事を考える必要はないわけですよ(そもそもスループットをかんがえないと行けない速度でアクセスがきたら、メール送信側が死ぬ)。しかしまあ、発注されたらやらないといけない。
普段なら別にどうこうないんですけど、ちょっとややこしいメールフォームなのでベンチがとりづらかった*1ので、wrkを使う事にしました。そのメモです。
こういうのはApache Jmeterでいいじゃんという声も聞こえてきそうですが、jmeterが性に合わないという人も多いと思います。すくなくとも私はjmeterつかうの面倒でござる。
ということでwrk
abみたいなシンプルな負荷ツールなんですが、luaでスクリプトが書けます。
スクリプトサンプル
https://github.com/wg/wrk/tree/master/scripts
見ましょう
multipart/form-data
を送る
画像アップロードだとPOSTでContent-typeはmultipart/form-data
を送信する必要がありますが、multipartのリクエストボディを素手でつくるのは面倒なので、Chromeのデベロッパーツールをひらきつつ、実際にブラウザでPOSTしてNetwork タブの対象のリクエストをひらき、Headers の Request Payloadをコピーするとよいでしょう。
多分、こんな感じです。
------WebKitFormBoundaryoPXl2cg1DVw1QDsW Content-Disposition: form-data; name="name" hoge ------WebKitFormBoundaryoPXl2cg1DVw1QDsW Content-Disposition: form-data; name="jidori_photo"; filename="" Content-Type: application/octet-stream ------WebKitFormBoundaryoPXl2cg1DVw1QDsW Content-Disposition: form-data; name="age" 20 ------WebKitFormBoundaryoPXl2cg1DVw1QDsW--
これがmultipartのリクエストボディになります。これをとっておきます。
wrkのscriptを書く
wrkはluaでスクリプトがかけます。以下みたいに書くと、とっておいたmultipartのbodyをPOSTできます。簡単ですね。 細かい解説や、lua自体の解説はしませんけど、ノリでわかると思います。
str = [[------WebKitFormBoundaryoPXl2cg1DVw1QDsW (省略) ------WebKitFormBoundaryoPXl2cg1DVw1QDsW--]] wrk.method = "POST" wrk.body = str wrk.headers["Content-Type"] = "multipart/form-data; boundary=----WebKitFormBoundaryoPXl2cg1DVw1QDsW"
つかうときは、例えば以下のような感じ。
wrk -c128 -t128 -d120s --timeout 10s -s script.lua http://example.com/form/confirm
「本当にうごいてんのか?」
ただ、これ普通に書くとうごいてるのかよくわからんですね(ステータスコードでエラーが返るような画面ならともかく)。普通にレスポンスの中身を目視したい。
以下みたいに追記すると、一回目のレスポンスで終了して、レスポンスをwrk.logに出力できます。
function response(status, headers, body) log = io.open("wrk.log", "w"); log:write("status:" .. status .. "\n" .. body); -- bodyをファイルに書き出す wrk.thread:stop() -- ここで終了する end
response()
関数をオーバーライドしてる感じでしょうか。まあ雑にやるならwriteでなく、printでもよさそう。
Chromeでコピペできるmultipartの文字列がイマイチな件
ただし、Chromeだとinput type=fileの項目は空っぽになってしまいます。*2
目的の箇所へ画像のバイナリをつなげば出来るんですけど、普通にエディタで修正しづらくなるのでここはファイルを読み込んでしまうと後が楽。
たとえば以下のようにreadして、文字列連結してつくってやればよい。*3
s1 = [[------WebKitFormBoundaryoPXl2cg1DVw1QDsW Content-Disposition: form-data; name="name" hoge ------WebKitFormBoundaryoPXl2cg1DVw1QDsW Content-Disposition: form-data; name="jidori_photo"; filename="test.jpg" Content-Type: image/jpg ]] fh, msg = io.open("test.jpg", "r") s2 = fh:read("*a"); s3 = [[ ------WebKitFormBoundaryoPXl2cg1DVw1QDsW Content-Disposition: form-data; name="age" 20 ------WebKitFormBoundaryoPXl2cg1DVw1QDsW--]] str = s1..s2..s3
これで無事ベンチがまわるようになりました。
まとめ
普通にluaをかけば乱数つかったりとかもできるし、まあ色々便利で最高ですね。
ただ、とはいえwrkだと「シナリオ」まではすんなりかけないので局所的なベンチマークになってしまい、結局この後で久々にjmeterをDLしたのであった。
…が、jmeterのProxyでシナリオ生成するやつは何年たっても文字化けするし、ローカルファイル読まないし*4微妙なので、これからGatling試します。
こちらからは以上です。