Techtouch Developers Blog

テックタッチ株式会社の開発チームによるテックブログです

tsungでサクッと負荷テストをしよう

adventCalendar-day23

この記事はテックタッチアドベントカレンダー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等でももちろん大丈夫です。

f:id:techtouch:20201222100137p:plain

tsung-recorder (記録用proxy)の起動

tsung-recorder -p http start  //(defaultのproxy port:8090)

ブラウザ経由でURLにアクセス

シナリオを記録したいURLにブラウザでアクセスします。1点注意が必要なのはhttpsのサイトへアクセスする場合は 下記のようにhttps://techtouch.jphttp://-techtouch.jpの形に変換したURLへアクセスする必要があります。

f:id:techtouch:20201222100308p:plain

記録の終了時は以下コマンドで終了します。

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�&apos;��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&amp;k=zfc8flw&amp;ht=tk&amp;f=10875.32265.36601.36602.36604.36606.36607.36608.36617.36618.36623.36624.36633.36634.36639.36640&amp;a=17053948&amp;app=typekit&amp;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&amp;fvd=n5&amp;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�&apos;��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情報等も直感的に編集しやすいので、認証ヘッダーの差し込みをして認証ありページのシナリオベースの負荷テスト等も行ってみたいです。