curlでパフォーマンス測定
コマンドラインツールのcurlを用いてHTTPによる通信のパフォーマンスを調べる方法を考えていこうと思います。
curlとは
curlはURLを用いてデータをやりとりするためのコマンドラインツールもしくはライブラリです。
コマンドラインツールとしてはcurl
、ライブラリとしてはlibcurl
があります。
HTTPだけではなくFTPやSMTPなど様々なプロトコルに対応しています。 自分は主にCLIからHTTPリクエストを送りたい時などに使っています。
使ってみたい方は以下の方法でインストールできると思います
brew install curl
apt install curl
--write-out
を使ってパフォーマンス測定
curlには様々なオプションが用意されていますが、今回、主に用いるのはこの-w
, --write-out
オプションです。
このオプションは指定したフォーマットを用いてコマンドの終了時に実行中のさまざまな情報を出力してくれます。
実際の使い方は以下のような感じです。
$ curl -w "code: %{http_code}, speed: %{speed_download}\n" -o /dev/null -s https://dev.classmethod.jp code: 200, speed: 495136.000
今回はHTTPのステータスコードとダウンロード速度を表示してみました。
-o
は標準出力に流れるHTTPのレスポンスを/dev/nullに出して捨てています。ファイル名を指定すれば、そこに実行結果を出力してくれます。
-s
は実行中に表示されるインジケーターなどを消すためのオプションです。
今回のメインの-w
オプションはフォーマットとして文字列を渡します。%{}
の部分に値が展開されます。それ以外は自由に文字列を埋め込むことができます。
いろいろな変数が使えるので、マニュアルページで確認してみてください。
今回扱うのは以下の変数です。
変数名 | 解説 |
---|---|
size_download | ダウンロードしたデータの総量(bytes) |
size_upload | アップロードしたデータの総量(bytes) |
speed_download | ダウンロードしたデータ量の1秒あたりの平均(bytes/sec) |
speed_upload | アップロードしたデータ量の1秒あたりの平均(bytes/sec) |
time_namelookup | DNSの名前解決が完了した時間(sec) |
time_connect | TCPなどのコネクションの確立が完了した時間(sec) |
time_appconnect | TLSなどの接続、ハンドシェイクが完了した時間(sec) (7.19.0で追加) |
time_pretransfer | データの転送が開始した時間(sec) |
time_starttransfer | サーバーからレスポンスの最初のデータを受信した時間(sec) |
time_total | 全体の処理にかかった時間(sec) |
ここで注意したいのはこの時間というのはコマンドを実行してからの経過時間だということです。
つまりは純粋にTCPのコネクション成立に必要な時間を知りたい場合はtime_connect
からtime_namelookup
を引かなければなりません。
整理するために、以下の記事を参考にTLSとDNSを考慮した図を作成しました。 元記事はわかりやすいのでおすすめです。
ソースコードのCurl_pgrsTime
とWiresharkでのキャプチャの結果からおそらくこのような感じだと思います。
TLSはVer1.2、HTTPはVer2を想定しています。本来はもう少しクライアントとサーバーの間でやりとりが多いですが、簡略化のために省略しています。
Tips
ヒアドキュメントを使う
複数のデータを出力するときはヒアドキュメントを使用するとコマンドが読みやすくなるかもしれません。
curl https://dev.classmethod.jp -o /dev/null -s -w @- << EOF size_download: %{size_download}\n size_upload: %{size_upload}\n speed_download: %{speed_download}\n speed_upload: %{speed_upload}\n time_namelookup: %{time_namelookup}\n time_connect: %{time_connect}\n time_appconnect: %{time_appconnect}\n time_pretransfer: %{time_pretransfer}\n time_starttransfer: %{time_starttransfer}\n time_total: %{time_total}\n EOF
CSVで出力したいときも同様です。
curl https://dev.classmethod.jp -o /dev/null -s -w @- << EOF %{size_download}, %{size_upload}, %{speed_download}, %{speed_upload}, %{time_namelookup}, %{time_connect}, %{time_appconnect}, %{time_pretransfer}, %{time_starttransfer}, %{time_total}\n EOF
IPアドレスは保存しておく
変数remote_ip
で実際にリクエストを送ったサーバーのIPアドレスを取得することができます。
DNSレベルで冗長化を行っていたりすると、同一ドメインからでも異なったIPアドレスが使われる場合があります。
サーバーが異なれば、応答速度などは変わってしまいます。
適切な分析を行うためにもIPアドレスを記録しておけば、あとでデータを分割する時に役立つと思います。
HTTPのステータスコードは記録しておく
変数http_code
でHTTPレスポンスのステータスコードを取得することができます。
エラーレスポンスやリダイレクトが返ってきた場合でも測定用の変数は記録されてしまいます。
そのため、期待しないコードで返ってきた場合は異なった状況で分析をしていることになってしまいます。
これに気付くためにもステータスコードを記録しておくのをおすすめします。
リソースの負荷に気を付ける
並行してリクエストを送りたいときは注意が必要です。 CPUやメモリ、ネットワークI/Oなど様々なリソースがパフォーマンス測定に影響を与えていないか気にした方がいいと思います。 xargsなどを使用して並行してcurlを実行したいときは、リソースも同時にモニタリングし、余裕があることを確認した方がよいでしょう。
測定用サンプルスクリプト
URL=https://dev.classmethod.jp N=10 for i in $(seq ${N});do sleep 1 curl ${URL} -o /dev/null -s -w @- << EOF %{remote_ip}, %{http_code}, %{size_download}, %{size_upload}, %{speed_download}, %{speed_upload}, %{time_namelookup}, %{time_connect}, %{time_appconnect}, %{time_pretransfer}, %{time_starttransfer}, %{time_total}\n EOF done
感想
- curlを使ってかなり細かいところまでパフォーマンスの分析ができることがわかりました
- 問題を各レイヤーに分けて考えられるので、改善にも役立ちそうな気がします