Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
99 lines
4.4 KiB
Bash
99 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
||
# Простой нагрузочный смоук-тест API Deal (этап 12, пакет C).
|
||
#
|
||
# Что делает: логинится (по умолчанию admin/admin), затем в цикле дёргает два
|
||
# читающих эндпоинта — GET /api/containers и GET /api/cards — до истечения
|
||
# DURATION_SECONDS, замеряя HTTP-код и время ответа. В конце печатает сводку:
|
||
# число запросов, ошибки (не 200), RPS, средняя/p95-латентность (сек).
|
||
#
|
||
# Зависимости: только bash и curl. Ничего не пишет в БД; читающие ручки.
|
||
#
|
||
# Запуск (из корня репозитория, dev-API на :5080):
|
||
# bash scripts/loadtest/api-loadtest.sh
|
||
# BASE_URL=http://localhost:5080 LOGIN=admin PASSWORD=admin \
|
||
# DURATION_SECONDS=20 PAUSE_SECONDS=0.1 bash scripts/loadtest/api-loadtest.sh
|
||
#
|
||
# Переменные окружения:
|
||
# BASE_URL базовый URL API (по умолчанию http://localhost:5080)
|
||
# LOGIN/PASSWORD учётные данные тенанта (по умолчанию admin/admin — dev-дефолт bootstrap)
|
||
# DURATION_SECONDS длительность прогона, с (по умолчанию 15)
|
||
# PAUSE_SECONDS пауза между итерациями, с (по умолчанию 0.2)
|
||
# CURL_TIMEOUT таймаут запроса, с (по умолчанию 5)
|
||
#
|
||
# Внимание: в PROD включён rate-limit (api-политика — 600/мин на тенанта). Для
|
||
# нагрузочного прогона используйте dev-стенд или поднимите лимит.
|
||
set -euo pipefail
|
||
|
||
BASE_URL="${BASE_URL:-http://localhost:5080}"
|
||
LOGIN="${LOGIN:-admin}"
|
||
PASSWORD="${PASSWORD:-admin}"
|
||
DURATION_SECONDS="${DURATION_SECONDS:-15}"
|
||
PAUSE_SECONDS="${PAUSE_SECONDS:-0.2}"
|
||
CURL_TIMEOUT="${CURL_TIMEOUT:-5}"
|
||
|
||
command -v curl >/dev/null 2>&1 || { echo "Ошибка: нужен curl" >&2; exit 127; }
|
||
|
||
tmp_dir="$(mktemp -d)"
|
||
cookie_jar="${tmp_dir}/cookies.txt"
|
||
lat_file="${tmp_dir}/latencies.txt"
|
||
trap 'rm -rf "${tmp_dir}"' EXIT
|
||
|
||
login_code="$(curl -sS -o "${tmp_dir}/login.json" -w '%{http_code}' \
|
||
-c "${cookie_jar}" -H 'Content-Type: application/json' \
|
||
-d "{\"login\":\"${LOGIN}\",\"password\":\"${PASSWORD}\"}" \
|
||
--max-time "${CURL_TIMEOUT}" "${BASE_URL}/api/auth/login" || true)"
|
||
[ -n "${login_code}" ] || login_code="000"
|
||
|
||
if [ "${login_code}" != "200" ]; then
|
||
echo "LOGIN FAILED (HTTP ${login_code}): $(cat "${tmp_dir}/login.json" 2>/dev/null)" >&2
|
||
echo "Проверьте BASE_URL=${BASE_URL} и что API поднят." >&2
|
||
exit 1
|
||
fi
|
||
echo "login OK (${LOGIN}) → прогон ${DURATION_SECONDS} с, пауза ${PAUSE_SECONDS} с"
|
||
|
||
started_at="$(date +%s)"
|
||
end_at=$((started_at + DURATION_SECONDS))
|
||
total=0
|
||
errors=0
|
||
|
||
while [ "$(date +%s)" -lt "${end_at}" ]; do
|
||
for path in /api/containers /api/cards; do
|
||
result="$(curl -sS -o /dev/null -w '%{http_code} %{time_total}' \
|
||
-b "${cookie_jar}" --max-time "${CURL_TIMEOUT}" "${BASE_URL}${path}" || true)"
|
||
[ -n "${result}" ] || result="000 0"
|
||
code="${result%% *}"
|
||
elapsed="${result##* }"
|
||
total=$((total + 1))
|
||
if [ "${code}" != "200" ]; then
|
||
errors=$((errors + 1))
|
||
echo " ! ${path} → HTTP ${code}" >&2
|
||
fi
|
||
echo "${elapsed}" >> "${lat_file}"
|
||
done
|
||
sleep "${PAUSE_SECONDS}"
|
||
done
|
||
|
||
elapsed_total=$(( $(date +%s) - started_at ))
|
||
[ "${elapsed_total}" -lt 1 ] && elapsed_total=1
|
||
|
||
if [ "${total}" -eq 0 ]; then
|
||
echo "Запросов не сделано (DURATION_SECONDS слишком мал?)" >&2
|
||
exit 1
|
||
fi
|
||
|
||
avg="$(awk '{sum += $1} END {printf "%.4f", sum / NR}' "${lat_file}")"
|
||
p95_index=$(( (total * 95 + 99) / 100 ))
|
||
p95="$(sort -n "${lat_file}" | sed -n "${p95_index}p")"
|
||
max="$(sort -n "${lat_file}" | tail -n 1)"
|
||
rps="$(awk -v n="${total}" -v d="${elapsed_total}" 'BEGIN {printf "%.2f", n / d}')"
|
||
|
||
echo "---------------------------------------------"
|
||
echo "запросов: ${total}"
|
||
echo "ошибок: ${errors}"
|
||
echo "длительность: ${elapsed_total} с"
|
||
echo "RPS: ${rps}"
|
||
echo "латентность: avg=${avg}s p95=${p95}s max=${max}s"
|
||
echo "---------------------------------------------"
|
||
|
||
[ "${errors}" -eq 0 ] || exit 2
|