LinuCエヴァンジェリストの鯨井貴博@opensourcetechです。
今回は、Webなどのパフォーマンス試験ツールであるJmeterを使ってみます。
https://jmeter.apache.org/index.html
Jmeterの入手ですが、以下から行います。
https://jmeter.apache.org/download_jmeter.cgi
なお Jmeterの始め方については、こちらにあります。
まず、JAVA8以上が必要となるのでチェックします。
インストールされていない方は、こちらなどから入手してインストールしましょう。
Jmeterの実行ですが、ダウンロードしたzipを解凍します。
その中のbinというフォルダ内にあるjmeter.batを実行します。
すると、以下のようにコマンドプロンプトとJmeterのGUIが起動します。
テスト対象のサーバは2つ、コンテナでサクッと用意しました。
1つ目は、nginx。「http://コンテナホストのIPアドレス:8080」でアクセスできます。
もう一つは、Zabbix 5.0 LTS。「http://コンテナホストのIPアドレス:80」でアクセスでき、ユーザ認証が必要となります。
ではまず、nginx用のテストの作成です。
TestPlanとあるところで右クリックし、「add > Thread(Users) > Thread Group」と辿ります。
実施回数・実施インターバル・オプションなどを設定します。
-Numbber of Threads(1000)・・・スレッドの数
-Ramp-up Period(60)・・・スレッドを実施する期間
-Delay Thread creation until needed(チェック)・・・必要に応じてスレッドを実施する
その下に「HTTP Request」を設定します。
そして、テスト結果の確認用に、「View Results Tree」「View Results in Table」「Graph Results」の三つを追加します。
作成したテストケースの保存は、Saveアイコン(フロッピーマーク)をクリックし、xxx.jmxという名前で任意の場所に保存します。
出来上がったテストケース(xxx.jmx)の内容は、XMLで書かれています。
nginx用のテストケース
<?xml version="1.0" encoding="UTF-8"?> <jmeterTestPlan version="1.2" properties="5.0" jmeter="5.3"> <hashTree> <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> <stringProp name="TestPlan.comments"></stringProp> <boolProp name="TestPlan.functional_mode">false</boolProp> <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="TestPlan.user_define_classpath"></stringProp> </TestPlan> <hashTree> <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> <boolProp name="LoopController.continue_forever">false</boolProp> <stringProp name="LoopController.loops">1</stringProp> </elementProp> <stringProp name="ThreadGroup.num_threads">6000</stringProp> <stringProp name="ThreadGroup.ramp_time">60</stringProp> <boolProp name="ThreadGroup.scheduler">false</boolProp> <stringProp name="ThreadGroup.duration"></stringProp> <stringProp name="ThreadGroup.delay"></stringProp> <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp> <boolProp name="ThreadGroup.delayedStart">true</boolProp> </ThreadGroup> <hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="HTTPSampler.domain">192.168.1.254</stringProp> <stringProp name="HTTPSampler.port">8080</stringProp> <stringProp name="HTTPSampler.protocol">http</stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.path">/index.html</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.use_keepalive">true</boolProp> <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"></stringProp> <stringProp name="HTTPSampler.connect_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp> </HTTPSamplerProxy> <hashTree> <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> <boolProp name="ResultCollector.error_logging">false</boolProp> <objProp> <name>saveConfig</name> <value class="SampleSaveConfiguration"> <time>true</time> <latency>true</latency> <timestamp>true</timestamp> <success>true</success> <label>true</label> <code>true</code> <message>true</message> <threadName>true</threadName> <dataType>true</dataType> <encoding>false</encoding> <assertions>true</assertions> <subresults>true</subresults> <responseData>false</responseData> <samplerData>false</samplerData> <xml>false</xml> <fieldNames>true</fieldNames> <responseHeaders>false</responseHeaders> <requestHeaders>false</requestHeaders> <responseDataOnError>false</responseDataOnError> <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> <assertionsResultsToSave>0</assertionsResultsToSave> <bytes>true</bytes> <sentBytes>true</sentBytes> <url>true</url> <threadCounts>true</threadCounts> <idleTime>true</idleTime> <connectTime>true</connectTime> </value> </objProp> <stringProp name="filename"></stringProp> </ResultCollector> <hashTree/> <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> <boolProp name="ResultCollector.error_logging">false</boolProp> <objProp> <name>saveConfig</name> <value class="SampleSaveConfiguration"> <time>true</time> <latency>true</latency> <timestamp>true</timestamp> <success>true</success> <label>true</label> <code>true</code> <message>true</message> <threadName>true</threadName> <dataType>true</dataType> <encoding>false</encoding> <assertions>true</assertions> <subresults>true</subresults> <responseData>false</responseData> <samplerData>false</samplerData> <xml>false</xml> <fieldNames>true</fieldNames> <responseHeaders>false</responseHeaders> <requestHeaders>false</requestHeaders> <responseDataOnError>false</responseDataOnError> <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> <assertionsResultsToSave>0</assertionsResultsToSave> <bytes>true</bytes> <sentBytes>true</sentBytes> <url>true</url> <threadCounts>true</threadCounts> <idleTime>true</idleTime> <connectTime>true</connectTime> </value> </objProp> <stringProp name="filename"></stringProp> </ResultCollector> <hashTree/> <ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector" testname="Graph Results" enabled="true"> <boolProp name="ResultCollector.error_logging">false</boolProp> <objProp> <name>saveConfig</name> <value class="SampleSaveConfiguration"> <time>true</time> <latency>true</latency> <timestamp>true</timestamp> <success>true</success> <label>true</label> <code>true</code> <message>true</message> <threadName>true</threadName> <dataType>true</dataType> <encoding>false</encoding> <assertions>true</assertions> <subresults>true</subresults> <responseData>false</responseData> <samplerData>false</samplerData> <xml>false</xml> <fieldNames>true</fieldNames> <responseHeaders>false</responseHeaders> <requestHeaders>false</requestHeaders> <responseDataOnError>false</responseDataOnError> <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> <assertionsResultsToSave>0</assertionsResultsToSave> <bytes>true</bytes> <sentBytes>true</sentBytes> <url>true</url> <threadCounts>true</threadCounts> <idleTime>true</idleTime> <connectTime>true</connectTime> </value> </objProp> <stringProp name="filename"></stringProp> </ResultCollector> <hashTree/> </hashTree> </hashTree> </hashTree> </hashTree> </jmeterTestPlan>
Zabbixの場合、ログインページから認証を経てダッシュボードにたどり着く画面遷移があるので、以下のようにします。
Thread Group
ログインページ設定
認証情報設定
なお、上記で設定する情報は、サーバの管理者に教えてもらったり、FirefoxのAddonであるHTTPLiveHeaderなどのツールで調べます。
こんな感じです。
http://192.168.1.254/index.php ・・・ログインページ Host: 192.168.1.254 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive Cookie: grafana_session=19fb071121d501e5f2eb61e145452e42; PHPSESSID=0d0576d41e34171a4a6bfd9ea4c3bb02 Upgrade-Insecure-Requests: 1 GET: HTTP/1.1 200 OK Server: nginx Date: Tue, 30 Jun 2020 06:48:39 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Keep-Alive: timeout=20 X-Powered-By: PHP/7.3.14 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Content-Encoding: gzip --------------------- 省略(CSSや画像ファイルなどのGETなど) --------------------- http://192.168.1.254/index.php Host: 192.168.1.254 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 52 Origin: http://192.168.1.254 Connection: keep-alive Referer: http://192.168.1.254/index.php Cookie: grafana_session=19fb071121d501e5f2eb61e145452e42; PHPSESSID=0d0576d41e34171a4a6bfd9ea4c3bb02 Upgrade-Insecure-Requests: 1 name=Admin&password=zabbix&autologin=1&enter=Sign in ・・・認証情報として入力したり、クリックするための情報 POST: HTTP/1.1 302 Found Server: nginx Date: Tue, 30 Jun 2020 06:48:43 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Keep-Alive: timeout=20 X-Powered-By: PHP/7.3.14 Set-Cookie: zbx_sessionid=7a985682259b053a61d023ec9a98715f; expires=Thu, 30-Jul-2020 06:48:43 GMT; Max-Age=2592000; HttpOnly Location: zabbix.php?action=dashboard.view --------------------- http://192.168.1.254/zabbix.php?action=dashboard.view ・・・ダッシュボードページ Host: 192.168.1.254 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://192.168.1.254/index.php Connection: keep-alive Cookie: grafana_session=19fb071121d501e5f2eb61e145452e42; PHPSESSID=0d0576d41e34171a4a6bfd9ea4c3bb02; zbx_sessionid=7a985682259b053a61d023ec9a98715f Upgrade-Insecure-Requests: 1 POST: HTTP/1.1 200 OK Server: nginx Date: Tue, 30 Jun 2020 06:48:44 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Keep-Alive: timeout=20 X-Powered-By: PHP/7.3.14 Set-Cookie: zbx_sessionid=7a985682259b053a61d023ec9a98715f; expires=Thu, 30-Jul-2020 06:48:43 GMT; Max-Age=2592000; HttpOnly PHPSESSID=0d0576d41e34171a4a6bfd9ea4c3bb02; HttpOnly X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Content-Encoding: gzip ---------------------
zabbix用のテストケース
<?xml version="1.0" encoding="UTF-8"?> <jmeterTestPlan version="1.2" properties="5.0" jmeter="5.3"> <hashTree> <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> <stringProp name="TestPlan.comments"></stringProp> <boolProp name="TestPlan.functional_mode">false</boolProp> <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp> <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="TestPlan.user_define_classpath"></stringProp> </TestPlan> <hashTree> <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> <boolProp name="LoopController.continue_forever">false</boolProp> <stringProp name="LoopController.loops">1</stringProp> </elementProp> <stringProp name="ThreadGroup.num_threads">600</stringProp> <stringProp name="ThreadGroup.ramp_time">60</stringProp> <boolProp name="ThreadGroup.scheduler">false</boolProp> <stringProp name="ThreadGroup.duration"></stringProp> <stringProp name="ThreadGroup.delay"></stringProp> <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp> <boolProp name="ThreadGroup.delayedStart">true</boolProp> </ThreadGroup> <hashTree> <ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="HTTPSampler.domain">192.168.1.254</stringProp> <stringProp name="HTTPSampler.port">80</stringProp> <stringProp name="HTTPSampler.protocol">http</stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.path">/index.php</stringProp> <stringProp name="HTTPSampler.concurrentPool">6</stringProp> <stringProp name="HTTPSampler.connect_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp> </ConfigTestElement> <hashTree/> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"> <elementProp name="name" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> <stringProp name="Argument.value">Admin</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">name</stringProp> </elementProp> <elementProp name="password" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> <stringProp name="Argument.value">zabbix</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">password</stringProp> </elementProp> <elementProp name="autologin" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> <stringProp name="Argument.value">1</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">autologin</stringProp> </elementProp> <elementProp name="enter" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> <stringProp name="Argument.value">Sign in</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">enter</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain">192.168.1.254</stringProp> <stringProp name="HTTPSampler.port">80</stringProp> <stringProp name="HTTPSampler.protocol">http</stringProp> <stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.path">/index.php</stringProp> <stringProp name="HTTPSampler.method">POST</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.use_keepalive">true</boolProp> <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"></stringProp> <stringProp name="HTTPSampler.connect_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp> </HTTPSamplerProxy> <hashTree> <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> <boolProp name="ResultCollector.error_logging">false</boolProp> <objProp> <name>saveConfig</name> <value class="SampleSaveConfiguration"> <time>true</time> <latency>true</latency> <timestamp>true</timestamp> <success>true</success> <label>true</label> <code>true</code> <message>true</message> <threadName>true</threadName> <dataType>true</dataType> <encoding>false</encoding> <assertions>true</assertions> <subresults>true</subresults> <responseData>false</responseData> <samplerData>false</samplerData> <xml>false</xml> <fieldNames>true</fieldNames> <responseHeaders>false</responseHeaders> <requestHeaders>false</requestHeaders> <responseDataOnError>false</responseDataOnError> <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> <assertionsResultsToSave>0</assertionsResultsToSave> <bytes>true</bytes> <sentBytes>true</sentBytes> <url>true</url> <threadCounts>true</threadCounts> <idleTime>true</idleTime> <connectTime>true</connectTime> </value> </objProp> <stringProp name="filename"></stringProp> </ResultCollector> <hashTree/> <ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector" testname="Graph Results" enabled="true"> <boolProp name="ResultCollector.error_logging">false</boolProp> <objProp> <name>saveConfig</name> <value class="SampleSaveConfiguration"> <time>true</time> <latency>true</latency> <timestamp>true</timestamp> <success>true</success> <label>true</label> <code>true</code> <message>true</message> <threadName>true</threadName> <dataType>true</dataType> <encoding>false</encoding> <assertions>true</assertions> <subresults>true</subresults> <responseData>false</responseData> <samplerData>false</samplerData> <xml>false</xml> <fieldNames>true</fieldNames> <responseHeaders>false</responseHeaders> <requestHeaders>false</requestHeaders> <responseDataOnError>false</responseDataOnError> <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> <assertionsResultsToSave>0</assertionsResultsToSave> <bytes>true</bytes> <sentBytes>true</sentBytes> <url>true</url> <threadCounts>true</threadCounts> <idleTime>true</idleTime> <connectTime>true</connectTime> </value> </objProp> <stringProp name="filename"></stringProp> </ResultCollector> <hashTree/> <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true"> <boolProp name="ResultCollector.error_logging">false</boolProp> <objProp> <name>saveConfig</name> <value class="SampleSaveConfiguration"> <time>true</time> <latency>true</latency> <timestamp>true</timestamp> <success>true</success> <label>true</label> <code>true</code> <message>true</message> <threadName>true</threadName> <dataType>true</dataType> <encoding>false</encoding> <assertions>true</assertions> <subresults>true</subresults> <responseData>false</responseData> <samplerData>false</samplerData> <xml>false</xml> <fieldNames>true</fieldNames> <responseHeaders>false</responseHeaders> <requestHeaders>false</requestHeaders> <responseDataOnError>false</responseDataOnError> <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage> <assertionsResultsToSave>0</assertionsResultsToSave> <bytes>true</bytes> <sentBytes>true</sentBytes> <url>true</url> <threadCounts>true</threadCounts> <idleTime>true</idleTime> <connectTime>true</connectTime> </value> </objProp> <stringProp name="filename"></stringProp> </ResultCollector> <hashTree/> </hashTree> </hashTree> </hashTree> </hashTree> </jmeterTestPlan>
いよいよ、テストの実行。
上部にある緑の三角形で実行。
秒数とスレッドのカウントがされれば、OKです。
また、右側のマークが緑の円に変化します。
テスト完了。
テスト結果は、「View Results Tree」でそれぞれのRequest/ResponseのHeader/Bodyを確認できます。
「Graph Results」では、スループットなどを確認できます。
「View Results in Table」では、表形式で結果を確認できます。
また、繰り返し同じ試験を行う場合は、「clear」アイコンで結果の消去、
「search」で結果から検索を行うことができます。
今回はhttpを使ったJmeterテストでしたが、httpsの場合はプロトコルの設定を「https」に変更すればOKです。
実施しての気づき
-試験対象のページについて把握しておく必要がある(ページ遷移、ヘッダー・Body・認証の有無・http/httpsなど)
-httpデータの構造を把握しておく必要がある
このあたりは、抑えとかないとあかんですな。
おまけ
1秒間に80回 HTTPリクエストを投げる設定