uzullaがブログ

uzullaがブログです。

wrkでファイルをPOSTする負荷テストをするときのメモ

お客様「このメールフォームは同時に何人がつかえますか?」
私「メールフォームってそういう性能が必要なものでしたっけ??」

ということで、負荷テストです。

私はメールフォーム制作が本職なのではないかと思う程度に普段からメールフォームをつくっている零細メールフォーマーです。

ただ、私ごときが担当するメールフォームなんてそこまでスループットの事を考える必要はないわけですよ(そもそもスループットをかんがえないと行けない速度でアクセスがきたら、メール送信側が死ぬ)。しかしまあ、発注されたらやらないといけない。

普段なら別にどうこうないんですけど、ちょっとややこしいメールフォームなのでベンチがとりづらかった*1ので、wrkを使う事にしました。そのメモです。

こういうのはApache Jmeterでいいじゃんという声も聞こえてきそうですが、jmeterが性に合わないという人も多いと思います。すくなくとも私はjmeterつかうの面倒でござる。

ということでwrk

wrk https://github.com/wg/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をコピーするとよいでしょう。

f:id:uzulla:20151111185356p:plain

多分、こんな感じです。

------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

f:id:uzulla:20151111185413p:plain

目的の箇所へ画像のバイナリをつなげば出来るんですけど、普通にエディタで修正しづらくなるのでここはファイルを読み込んでしまうと後が楽。

たとえば以下のように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試します。

こちらからは以上です。

*1:このエントリでは画像を送信することについてだけ書いてありますけど、まあ他にも色々事情がある

*2:Firefoxだと文字化けで出力されますw

*3:ファイルの読み込みエラー処理を一切やってないのはご愛敬です

*4:cwdにファイルコピーしておけばうごくが…