Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ backend/media

backend/celerybeat-schedule

test/assets
test/data
test/graphs

.idea
.env
.env.prod
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,50 @@ docker compose logs
Взаимодействие пользователя с приложением управляется через nginx:

![Схема](images/schema_selection.png)

## Запуск скриптов для нагрузочного тестирования

Чтобы запустить скрипты нужно создать виртуально оружение Python, где myenv - имя окружения, которое нужно придумать самостоятельно:

```bash
python3 -m venv myenv
```

Далее нужно активировать виртуальное окржуние:

```bash
source .venv/bin/activate
```

И установить необходимые пакеты из списка requirements.txt:

```bash
pip install -r requiremets.txt
```

В папке test/ лежат два скрипта: main.py - для отправки запросов на сервер, calculate.py - для построения графиков зависимости. Скрипты желательно запускать на машине, отличной от хостовой для основного сервиса.

Чтобы построить графики нужно провести несколько замеров с использованием скрипта main.py. Перед запуском необходимо внутри папки test создать папку assets и поместить туда изображения с именами small и big с раширением jpg/jpeg. При запуске скрипта нужно указать несколько обязательных параметров:
- workers-count - количество поднятых воркеров (указать вручную в соответствии с количеством поднятых на хосте)
- count - количество одновременных запросов в сервис
- size - размер изображения. Возможные значения: small и big.
Пример использования:

```bash
python3 main.py --workers-count 2 --count 256 --size small
```

В результате появится папка data, в которой будут помещены результаты замеров в .json-формате. После проведения нескольких измерений нужно запустить скрипт для построения графиков:

```bash
python3 calculate.py
```

После выполнения скрипта все графики будут находиться в папке graphs.


Чтобы выйти из виртуального окружения python выполните:

```bash
deactivate
```
102 changes: 102 additions & 0 deletions test/calculate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import os
import json
import re
import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict

def read_data(data_dir):
# Хранилища
data_by_worker_and_size = defaultdict(list)
summary_by_size = defaultdict(lambda: defaultdict(list))

# Регулярка для парсинга имени файла
pattern = re.compile(r"workers-(\d+)_size-(\w+)_count-(\d+)\.json")

# Чтение файлов
for file_name in os.listdir(data_dir):
match = pattern.match(file_name)
if not match:
continue

workers = int(match.group(1))
size = match.group(2)
count = int(match.group(3))

with open(os.path.join(data_dir, file_name), "r") as f:
data = json.load(f)
times = data["times"]

data_by_worker_and_size[(workers, size)].append((count, times))
mean_time = np.mean(times)
summary_by_size[size][workers].append((count, mean_time))

return (data_by_worker_and_size, summary_by_size)

def draw_graph_for_one_experiment(batch_data, size, workers, output_dir):
counts, mins, maxs, means, medians = [], [], [], [], []

for count, times in batch_data:
times_np = np.array(times)
counts.append(count)
mins.append(np.min(times_np))
maxs.append(np.max(times_np))
means.append(np.mean(times_np))
medians.append(np.median(times_np))

plt.figure(figsize=(10, 6))
plt.plot(counts, maxs, label="Максимум", color="red")
plt.plot(counts, mins, label="Минимум", color="blue")
plt.plot(counts, means, label="Среднее", color="green")
plt.plot(counts, medians, label="Медиана", color="orange")
plt.fill_between(counts, mins, maxs, color="gray", alpha=0.2, label="Диапазон (мин-макс)")

plt.xlabel("Количество запросов")
plt.ylabel("Время обработки (сек)")
plt.title(f"Размер: {size}, Воркеры: {workers}")
plt.legend()
plt.grid(True)
plt.tight_layout()

filename = f"size-{size}_workers-{workers}.png"
plt.savefig(os.path.join(output_dir, filename))
plt.close()

def draw_graph_for_multiply_experiments(summary_by_size, output_dir):
for size, workers_data in summary_by_size.items():
plt.figure(figsize=(10, 6))
for workers, datapoints in sorted(workers_data.items()):
datapoints.sort(key=lambda x: x[0])
counts = [x[0] for x in datapoints]
means = [x[1] for x in datapoints]
plt.plot(counts, means, label=f"{workers} workers")

plt.xlabel("Количество запросов)")
plt.ylabel("Среднее время обработки (сек)")
plt.title(f"Сравнение по воркерам. Размер изображения: {size}")
plt.legend()
plt.grid(True)
plt.tight_layout()

filename = f"size-{size}_summary.png"
plt.savefig(os.path.join(output_dir, filename))
plt.close()

def main():
# Путь к папке с файлами
DATA_DIR = "./data"
OUTPUT_DIR = "graphs"
os.makedirs(OUTPUT_DIR, exist_ok=True)

data_by_worker_and_size, summary_by_size = read_data(DATA_DIR)

for (workers, size), batch_data in data_by_worker_and_size.items():
batch_data.sort(key=lambda x: x[0])
draw_graph_for_one_experiment(batch_data, size, workers, OUTPUT_DIR)


# Сводные графики: сравнение воркеров (только среднее время)
draw_graph_for_multiply_experiments(summary_by_size, OUTPUT_DIR)

if __name__ == '__main__':
main()
99 changes: 99 additions & 0 deletions test/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import argparse
import concurrent.futures
import itertools
import json
import os
import requests
import time


def get_correct_image_path(size):
for filename in os.listdir("./assets"):
if size in filename:
return f"./assets/{filename}"

print(f"Невозможно найти изображение, содержащее {size} в названии")
exit(0)

def post_image(size):
url_upload = 'https://distributed-text-converter.vdi.mipt.ru/api/upload/'
files = {'files': open(get_correct_image_path(size), 'rb')}
response_upload = requests.post(url_upload, files=files, allow_redirects=True)
request_id = json.loads(response_upload.text)['id']
print(response_upload.content)
return request_id

def get_request_ids(size, count):
print("[1] Отправление изображений")

with concurrent.futures.ThreadPoolExecutor(10) as executor:
request_ids = list(executor.map(post_image, itertools.repeat(size, count)))

print("[1] Изображения отправлены")

return request_ids

def get_exec_times(request_ids):
print("[2] Получение времени обработки изображений")

exec_times = []

for request_id in request_ids:
# Waiting till the end of image proccesing
url_status = f'https://distributed-text-converter.vdi.mipt.ru/api/status/{request_id}/'
sleep_duration = 0.0
sleep_addition = 0.5
while json.loads(requests.get(url_status).text)['status'] != 'ready':
print(requests.get(url_status).content)
sleep_duration += sleep_addition
time.sleep(min(5, sleep_duration))

# Get the processing time
url_exec_time = f'https://distributed-text-converter.vdi.mipt.ru/request/exec_time/{request_id}/'
response_exec_time = requests.get(url_exec_time)
exec_time = json.loads(response_exec_time.text)['seconds']
exec_times.append(exec_time)

print("[2] Информация по всем изображениям получена")

return exec_times

def save_data(count_workers, count, size, exec_times):
filename = f'./data/workers-{count_workers}_size-{size}_count-{count}.json'
dirname = os.path.dirname(filename)

if not os.path.exists(dirname):
os.mkdir(dirname)

data = {
'workers-count': count_workers,
'count': count,
'size': size,
'times': exec_times
}

with open(filename, 'w') as file:
json.dump(data, file)

print(f'[3] Время обработки изображений записано в {filename}\n')


def main():
parser = argparse.ArgumentParser(description='Получить время выполнения --count POST-запросов с изображениями размера --size в секундах')

parser.add_argument('--workers-count', required=True, type=int, help='Количество воркеров')
parser.add_argument('--count', required=True, type=int, help='Количество запросов')
parser.add_argument('--size', required=True, choices=['small', 'big'], help='Размер изображения')

args = parser.parse_args()

print(f"[0] {args.count} {args.size}".upper())

request_ids = get_request_ids(size=args.size, count=args.count)
exec_times = get_exec_times(request_ids=request_ids)

save_data(count_workers=args.workers_count, count=args.count, size=args.size, exec_times=exec_times)


if __name__ == '__main__':
main()
24 changes: 24 additions & 0 deletions test/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
altgraph==0.17.2
certifi==2025.1.31
charset-normalizer==3.4.1
contourpy==1.3.0
cycler==0.12.1
fonttools==4.57.0
future==0.18.2
idna==3.10
importlib_resources==6.5.2
kiwisolver==1.4.7
macholib==1.15.2
matplotlib==3.9.4
numpy==2.0.2
packaging==24.2
pillow==11.2.1
pip==21.2.4
pyparsing==3.2.3
python-dateutil==2.9.0.post0
requests==2.32.3
setuptools==58.0.4
six==1.15.0
urllib3==1.26.6
wheel==0.37.0
zipp==3.21.0