注目の記事

curlでパフォーマンス測定

2021.06.01

コマンドラインツールのcurlを用いてHTTPによる通信のパフォーマンスを調べる方法を考えていこうと思います。

curlとは

curlはURLを用いてデータをやりとりするためのコマンドラインツールもしくはライブラリです。 コマンドラインツールとしてはcurl、ライブラリとしてはlibcurlがあります。

HTTPだけではなくFTPやSMTPなど様々なプロトコルに対応しています。 自分は主にCLIからHTTPリクエストを送りたい時などに使っています。

使ってみたい方は以下の方法でインストールできると思います

brew

brew install curl

 

apt

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で出力したいときも同様です。

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を使ってかなり細かいところまでパフォーマンスの分析ができることがわかりました
  • 問題を各レイヤーに分けて考えられるので、改善にも役立ちそうな気がします