説明
今回Dockerを使ってlocustのテスト環境を構築します。
locustを使う場合、ただリクエストを送りhtmlファイルをダウンロードするだけなので、静的ファイルはダウンロードされません。(画像、Javascript)
しかし、今回ブラウザからのリクエスト状態と同じにするため、テストシナリオの中に静的ファイルをダウンロードする、
関数も入れたいと思います。
必要ソフト
以下のソフトが必要になります。
- Docker
始める前にインストールしておいてください。
事前準備
同じディレクトリー内に以下のファイルを作ってください。
- Dockerfile
- requirements.txt
- docker-compose.yml
各ファイルの用途と内容は以下になります。
Dockerfile
コンテナの設定ファイルです。このファイルの中にコンテナ内で必要なソフトやモジュールを入れます。
内容は以下になります。
FROM locustio/locust
ADD requirements.txt .
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt
requirements.txt
コンテナ内に入れる具体的なモジュールのリストを入れます。
今回必要なモジュールは以下になります。
- lxml
- Beautifulsoup4
なので内容は以下になります。
beautifulsoup4==4.11.1
lxml==4.8.0
docker-compose.yml
docker-composeの設定ファイルです。Docker-composeはコンテナの起動や停止などを簡単に行えるツールです。
内容は以下になります。
version: '3'
services:
master:
build: .
ports:
- "8089:8089"
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --master -H http://master:8089
worker:
build: .
volumes:
- ./:/mnt/locust
- ./samples:/home/locust/samples
command: -f /mnt/locust/locustfile.py --worker --master-host master
locustで行うテストのシナリオの準備
次にテストのシナリオを準備します。
docker-compose.ymlと同じディレクトリにlocustfile.pyというファイルを作ってください。
(注意:ファイル名はlocustfile.pyにしてください。違う名前の場合エラーが出ます。)
まずは必要なモジュールをインポートします。
from locust import HttpUser, task, TaskSet, SequentialTaskSet
from bs4 import BeautifulSoup
import requests
import os
import base64
次にテストシナリオを作成します。
class GetStaticFilesHttpUser(HttpUser):
@task
def execute_test(self):
response = self.client.get("/")
次に静的ファイルの情報を得てコンテナ内に書き込み最後に削除する関数を作ります。
(コンテナないのフォルダをmountしているのでローカルでも(docker-compose.ymlと同じディレクトリない)作り出されて削除されます。)
具体的なステップは以下になります。
- 1: src属性のあるscriptタグとimgタグを取得する。
- 2:画像ファイルの場合画像のdata URI化の可能性もあるためdata URI化の画像の状況とrequestモジュールを使って内容を取得することができる状況二つに分ける。Data URI化の場合src属性の内容からデータをデコードし、データをファイルに書き込み最後に削除する。ファイルはsamples/imagesに書き込む
- 3: javascriptファイルの場合src属性のurlにrequestモジュールを使ってリクエストし、内容を取得し、samples/js内に書き込み、最後にそのファイルを削除する。
- 4: 1~3 を繰り返す。
画像のData URI時の追加処理関数
画像のData URI時の追加処理関数は以下になります。
def get_file_information_for_base_sixty_four(self, url, count):
semicolon_index = url.index(";")
slash_index = url.index("/")
appendix = url[slash_index+1:semicolon_index]
filename = "{filename}.{appendix}".format(
filename="sample_{img_index}".format(img_index=count),
appendix=appendix
)
file_data = url.split('base64,')[1]
data = {}
data["filename"] = filename
data["file_data"] = file_data
return data
この関数は画像の拡張子を取得しダミーのファイル名と画像のデータを返す関数です。
画像/Javascriptデータを状況に応じて書き込む関数
データを書き込む関数は以下になります。
def write_file(self, host_url, urls, file_type="img"):
count = 1
for url in urls:
url_filename = ""
data = None
if "data:image" in url:
data = self.get_file_information_for_base_sixty_four(
url, count)
url_filename = data["filename"]
else:
url_filename = url.split("/")[-1]
file_path = url
if "http" not in url or "https" not in url:
file_path = host_url + "/" + url
if not file_path:
continue
save_path = IMG_PATH
if file_type == "js":
save_path = JS_PATH
if not os.path.exists(save_path):
os.makedirs(save_path)
with open(save_path + "/" + url_filename, 'wb') as f:
if data is None:
file_response = requests.get(file_path)
f.write(file_response.content)
else:
decoded_data = base64.decodebytes(
data["file_data"].encode("ascii"))
f.write(decoded_data)
os.remove(save_path + "/" + url_filename)
count += 1
src属性を取得し書き込み用の関数渡す関数
src属性を取得する関数は以下になります。
def fetch_static_assets(self, response, host_url):
soup = BeautifulSoup(response.content, "lxml")
img_tags = soup.find_all("img", src=True)
img_urls = [img["src"] for img in img_tags]
self.write_file(host_url, img_urls)
js_tags = soup.find_all("script", src=True)
js_urls = [js["src"] for js in js_tags]
self.write_file(host_url, js_urls, "js")
テストシナリオ完成
これでテストシナリオは完成です。
全てのコードは以下になります。
from locust import HttpUser, task, TaskSet, SequentialTaskSet
from bs4 import BeautifulSoup
import requests
import os
import base64
IMG_PATH = "samples/images"
JS_PATH = "samples/js"
class GetStaticFilesHttpUser(HttpUser):
def get_file_information_for_base_sixty_four(self, url, count):
semicolon_index = url.index(";")
slash_index = url.index("/")
appendix = url[slash_index+1:semicolon_index]
filename = "{filename}.{appendix}".format(
filename="sample_{img_index}".format(img_index=count),
appendix=appendix
)
file_data = url.split('base64,')[1]
data = {}
data["filename"] = filename
data["file_data"] = file_data
return data
def write_file(self, host_url, urls, file_type="img"):
count = 1
for url in urls:
url_filename = ""
data = None
if "data:image" in url:
data = self.get_file_information_for_base_sixty_four(
url, count)
url_filename = data["filename"]
else:
url_filename = url.split("/")[-1]
file_path = url
if "http" not in url or "https" not in url:
file_path = host_url + "/" + url
if not file_path:
continue
save_path = IMG_PATH
if file_type == "js":
save_path = JS_PATH
if not os.path.exists(save_path):
os.makedirs(save_path)
with open(save_path + "/" + url_filename, 'wb') as f:
if data is None:
file_response = requests.get(file_path)
f.write(file_response.content)
else:
decoded_data = base64.decodebytes(
data["file_data"].encode("ascii"))
f.write(decoded_data)
os.remove(save_path + "/" + url_filename)
count += 1
def fetch_static_assets(self, response, host_url):
print("fetching")
soup = BeautifulSoup(response.content, "lxml")
img_tags = soup.find_all("img", src=True)
img_urls = [img["src"] for img in img_tags]
self.write_file(host_url, img_urls)
js_tags = soup.find_all("script", src=True)
js_urls = [js["src"] for js in js_tags]
self.write_file(host_url, js_urls, "js")
@task
def execute_test(self):
response = self.client.get("/")
self.fetch_static_assets(response, self.host)
locust起動
次にdocker-compose.ymlと同じディレクトリに行き
以下のコマンド打ち環境をビルドしてください。
docker-compose build
最後に立ち上げます。
docker-compose up --scale worker=1
workerの数は状況において調整してください。
そして「http://localhost:8089/」をブラウザで開いてください。
以下の様になります。
Number of usersにユーザー数、Spawn rateにユーザーの増加速度(数/1秒になります)、HostにテストしたいURLを入れてStart Swarmingをクリックしテストしてください。