この記事はテックタッチアドベントカレンダー23日目の記事です。
バックエンドエンジニアのcomです。入社してもうすぐ2ヶ月です。最近は某大型スマホゲームの原神にハマっています。 そろそろアウトドアな趣味も見つけていきたい今日このごろ
この記事について
負荷テストをしようと思いいくつかツールを検討していた所、tsungとtsungに付随するtsung-recorderで簡単に 実リクエスト に近い負荷シナリオが作成できたので、その辺りの使い方を共有していきます。
免責事項
本記事はtsungのユーザーガイドを参照しながら独自に動作を確認したものになります。 公式のユーザーマニュアルと類似した内容が含まれている点ご了承ください。
この記事で伝えること
- tsungを使った基本的な負荷テスト実行の方法
- tsung-recorderを用いたシナリオ作成方法
tsungとは?
様々なプロトコルに対応している負荷テストツールです。 httpはもちろんのこと、mysqlやpostgres、raw pluginとしてTCP/UDPレイヤーのパケットをシナリオに組み込むことができるのでL4以上であれば何でもテストできる汎用性の高さがあります。
レポジトリはこちら github.com
tsungのセットアップ
Mac版はHomebrew経由でインストールできます。
brew install tsung
基本的な負荷ツール実行方法
tsungはxmlフォーマットで記載されたconfigファイルを引数として実行します。
tsung -f filename.xml start
たとえば弊社のコーポレートサイトへアクセスしたい場合は以下のようなconfigになります。
<?xml version="1.0"?> <!DOCTYPE tsung SYSTEM "tsung-1.0.dtd"> <tsung loglevel="notice" version="1.0"> <!-- ①クライアント/サーバー設定 --> <clients> <client host="localhost" use_controller_vm="true" maxusers="800" /> </clients> <servers> <server host="techtouch.jp" port="443" type="tcp"></server> </servers> <!-- ②負荷設定 --> <load> <arrivalphase phase="1" duration="60" unit="second"> <users interarrival="0.5" unit="second"></users> </arrivalphase> </load> <!-- ③セッション(シナリオ)設定 --> <sessions> <session name="default-session" probability="100" type="ts_http"> <request><http url="https://techtouch.jp/" method="GET" version="1.1"></http></request> </session> </sessions> </tsung>
ここでtsung用のxml formatを識別するためにtsung-1.0.dtd
を指定していますので、
今回は上記ファイルと同ディレクトリーにtsung-1.0.dtdを置く形にしています。
├── base.xml └── tsung-1.0.dtd
定義ファイルはここにあります。 tsung.erlang-projects.org
configの説明
①クライアント/サーバー設定
maxusers:
負荷ツールのプロセスにより開かれるユーザー(≒一連の負荷シナリオ実行)数の上限値です。
OSによって最大値が異なるので、ulimit -u
で確認した上でそれよりも小さな値にする必要があります。
use_controller_vm: 本項目をfalseにするとcontroller- workerといった構成をclientで取ることにより、maxusersを超えた場合に別ノードで実行される模様です。今回はシングルノードでテストしている為、=true 構成のみ動作確認をしています。
<clients> <!-- clientにはrequest元クライアント情報 --> <client host="localhost" use_controller_vm="true" maxusers="800" /> </clients> <servers> <!-- 負荷対象のサーバー情報 --> <server host="techtouch.jp" port="443" type="tcp"></server> </servers>
②負荷設定 複数の負荷設定の定義を書くことができます。下記ではphase 1のみ設定し60秒間の間0.5秒ごとに新規リクエストを行う設定となります。
<load> <arrivalphase phase="1" duration="60" unit="second"> <users interarrival="0.5" unit="second"></users> <!-- interarrivalと同等の値をarrivalrateで書き直した例 --> <!-- <users arrivalrate="2" unit="second"></users> --> </arrivalphase> </load>
interarrival(リクエストの間隔)で指定する方法以外にもarrivalrate(1秒間のリクエスト数)で指定する方法もできます。
③セッション設定 tsungでは一連のリクエストのまとまりをセッションとして定義することができます。 下記設定では単一のRequestのみで構成されたセッションとなります。
<sessions> <session name="default-session" probability="100" type="ts_http"> <request><http url="/" method="GET" version="1.1"></http></request> </session> </sessions>
tsung-recorderの使い方
負荷試験のシナリオをtsung-recorderを使うことで実際のブラウザ操作を記録し、シナリオファイルとして利用することができるようになります。個人的には一番ありがたい機能です。
Mac上でのproxyの設定
以下のようにtsung-recorderのproxyで利用予定のIPアドレス, ポート情報をhttp proxyの設定に入力します。 IPアドレスはlocalhost等でももちろん大丈夫です。
tsung-recorder (記録用proxy)の起動
tsung-recorder -p http start //(defaultのproxy port:8090)
ブラウザ経由でURLにアクセス
シナリオを記録したいURLにブラウザでアクセスします。1点注意が必要なのはhttpsのサイトへアクセスする場合は
下記のようにhttps://techtouch.jp
→ http://-techtouch.jp
の形に変換したURLへアクセスする必要があります。
記録の終了時は以下コマンドで終了します。
tsung-recorder stop -> [OK]
※ proxy設定を元に戻さないとWEBが見られなくなる可能性があるので注意してください。
Recordされた結果の確認
リクエスト後、record結果が既定の場合以下に保存されるので中身を参照します。
"Record file: /Users/xxx/.tsung/tsung_recorder20201222-0012.xml"
recordingした内容
<session name='rec20201223-0654' probability='100' type='ts_http'> <request><http url='https://techtouch.jp/' version='1.1' method='GET'> <http_header name='Accept' value='text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <thinktime random='true' value='1'/> <request><http url='https://www.googletagmanager.com/gtm.js?id=GTM-WKC4L35' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://techtouch.jp/_nuxt/855cdabd48274fd12f07.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/114e1ca4c0e42c70a1a8.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/945a29edc8ae454bba87.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/47f383679e544fe072d6.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/23c3f44056442de462af.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/favicon.ico' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://use.typekit.net/zfc8flw.css' version='1.1' method='GET'> <http_header name='Accept' value='text/css,*/*;q=0.1' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://techtouch.jp/_nuxt/9d41109cc56ae26d10b3.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='http://ocsp.pki.goog/gts1o1core' version='1.1' contents='0Q0O0M0K0I0 +BF0�'��p���s�_f8����n�ϛ�`����} �+�J3t �j~�k' content_type='application/ocsp-request' method='POST'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://techtouch.jp/_nuxt/801bc89c00c25b79569c.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/592eaefd88489cd64dd9.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/fonts/c33a41a.ttf' version='1.1' method='GET'> <http_header name='Accept' value='application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/262367f.svg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/3e9672c.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/cf6e406.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://p.typekit.net/p.css?s=1&k=zfc8flw&ht=tk&f=10875.32265.36601.36602.36604.36606.36607.36608.36617.36618.36623.36624.36633.36634.36639.36640&a=17053948&app=typekit&e=css' version='1.1' method='GET'> <http_header name='Accept' value='text/css,*/*;q=0.1' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://techtouch.jp/_nuxt/img/5667fa9.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/f46dad4.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/4c515b5.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/3b3d4b5.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/9807d6250c59cec4b217.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/8ef4d0a.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/f6c3da3.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/da0c6b0.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/48a3eb2.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/58f1c0b.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/e2033d3.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/df8a74c.png' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/83f2294b7772a0f8227d.js' version='1.1' method='GET'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/8590911.png' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/33f6010.png' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/3209a29.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/c6b745a.jpg' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/df21972.png' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/a8feb94.png' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='/_nuxt/img/b7311ce.png' version='1.1' method='GET'> <http_header name='Accept' value='image/webp,*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='https://use.typekit.net/af/e69b71/00000000000000003b9b0ee6/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n5&v=3' version='1.1' method='GET'> <http_header name='Accept' value='application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8' /> <http_header name='Accept-Encoding' value='identity' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> <request><http url='http://ocsp.pki.goog/gts1o1core' version='1.1' contents='0R0P0N0L0J0 +BF0�'��p���s�_f8����n�ϛ�`����} �+�3��!��~�j' content_type='application/ocsp-request' method='POST'> <http_header name='Accept' value='*/*' /> <http_header name='Accept-Encoding' value='gzip, deflate' /> <http_header name='Accept-Language' value='ja,en-US;q=0.7,en;q=0.3' /></http></request> </session>
recordしたシナリオを用いた設定ファイルの作成
tsung-recorderにより記録したファイルをtt_session.xml
として以下の用に配置します。
├── base.xml ├── tsung-1.0.dtd └── tt_session.xml
下記のようにxmlファイルを変更します。
<?xml version="1.0"?> <!-- xml内で使いたい名称と実体のファイルパスを記載 --> <!DOCTYPE tsung SYSTEM "tsung-1.0.dtd" [ <!ENTITY ttsession SYSTEM "./tt_session.xml"> ]> <tsung loglevel="notice" version="1.0"> <clients> <client host="localhost" use_controller_vm="true" maxusers="800" /> </clients> <servers> <server host="techtouch.jp" port="443" type="tcp"></server> </servers> <load> <arrivalphase phase="1" duration="60" unit="second"> <users interarrival="0.5" unit="second"></users> </arrivalphase> </load> <!-- 取り込んだシナリオ(セッション)を下記の形で紐付け --> <sessions> &ttsession; </sessions> </tsung>
実行は先に説明した実行方法と同様に
tsung -f base.xml start
で実行できます。
所感
用語にuser
という用語が多数出てきており、これをリクエスト(厳密には一連のリクエストの集合体)と読み解くことができればドキュメントの内容は充実しているのですごく使いやすいツールだと感じました。
記録したシナリオファイルはheader情報等も直感的に編集しやすいので、認証ヘッダーの差し込みをして認証ありページのシナリオベースの負荷テスト等も行ってみたいです。