forked from xiongyuxing/tiku-backend.net
perf: add reproducible concurrency test suite
This commit is contained in:
154
tools/performance/run-local.sh
Executable file
154
tools/performance/run-local.sh
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
api_port="${TIKU_API_PORT:-5091}"
|
||||
mode="${TIKU_MODE:-mixed}"
|
||||
rate="${TIKU_RATE:-100}"
|
||||
duration="${TIKU_DURATION:-30s}"
|
||||
tenant_code="${TIKU_TENANT_CODE:-demo-crm-school}"
|
||||
redis_container="${TIKU_REDIS_CONTAINER:-tiku-redis}"
|
||||
database_name="${TIKU_DATABASE_NAME:-tiku}"
|
||||
run_stamp="$(date +%Y%m%d-%H%M%S)"
|
||||
result_dir="${repo_root}/artifacts/performance/${run_stamp}-${mode}-${rate}rps"
|
||||
api_pid=""
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${api_pid}" ]] && kill -0 "${api_pid}" 2>/dev/null; then
|
||||
kill "${api_pid}" 2>/dev/null || true
|
||||
wait "${api_pid}" 2>/dev/null || true
|
||||
fi
|
||||
api_pid=""
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
mkdir -p "${result_dir}"
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker is required for the Redis readiness check" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! docker inspect "${redis_container}" >/dev/null 2>&1; then
|
||||
echo "Redis container '${redis_container}' does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(docker inspect -f '{{.State.Running}}' "${redis_container}")" != "true" ]]; then
|
||||
echo "Redis container '${redis_container}' is not running" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker exec "${redis_container}" redis-cli ping | grep -qx PONG
|
||||
|
||||
if ! command -v psql >/dev/null 2>&1; then
|
||||
echo "psql is required to capture PostgreSQL metrics" >&2
|
||||
exit 1
|
||||
fi
|
||||
psql -d "${database_name}" -Atqc 'select 1' | grep -qx 1
|
||||
|
||||
(
|
||||
cd "${repo_root}"
|
||||
dotnet build TIKU-BACKEND.slnx -c Release --no-restore
|
||||
)
|
||||
|
||||
host_url="http://127.0.0.1:${api_port}"
|
||||
load_url="${host_url}"
|
||||
|
||||
(
|
||||
cd "${repo_root}"
|
||||
exec env \
|
||||
ASPNETCORE_ENVIRONMENT=Development \
|
||||
ASPNETCORE_URLS="http://0.0.0.0:${api_port}" \
|
||||
ConnectionStrings__Redis=localhost:6379 \
|
||||
RateLimiting__Enabled=false \
|
||||
BackgroundProcessing__Enabled=false \
|
||||
Serilog__MinimumLevel__Default=Warning \
|
||||
dotnet run --project Tiku.Api -c Release --no-build --no-launch-profile
|
||||
) >"${result_dir}/api.log" 2>&1 &
|
||||
api_pid=$!
|
||||
|
||||
ready_body=""
|
||||
for _ in $(seq 1 60); do
|
||||
if ready_body="$(curl -fsS "${host_url}/api/health/ready" 2>/dev/null)"; then
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "${api_pid}" 2>/dev/null; then
|
||||
echo "Tiku.Api stopped during startup; see ${result_dir}/api.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ -z "${ready_body}" ]]; then
|
||||
echo "Tiku.Api did not become ready; see ${result_dir}/api.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "${ready_body}" >"${result_dir}/readiness.json"
|
||||
if ! printf '%s' "${ready_body}" | grep -q '"database":true'; then
|
||||
echo "PostgreSQL is not ready: ${ready_body}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! printf '%s' "${ready_body}" | grep -q '"redis":{"configured":true,"ready":true}'; then
|
||||
echo "Redis is not configured and ready: ${ready_body}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
capture_dependencies() {
|
||||
local suffix="$1"
|
||||
psql -d "${database_name}" -P pager=off -c \
|
||||
"select datname, numbackends, xact_commit, xact_rollback, blks_read, blks_hit, tup_returned, tup_fetched from pg_stat_database where datname = current_database();" \
|
||||
>"${result_dir}/postgres-${suffix}.txt"
|
||||
docker exec "${redis_container}" redis-cli INFO stats \
|
||||
>"${result_dir}/redis-${suffix}.txt"
|
||||
}
|
||||
|
||||
capture_dependencies before
|
||||
{
|
||||
sw_vers
|
||||
printf 'logical_cpu: '
|
||||
sysctl -n hw.logicalcpu
|
||||
printf 'memory_bytes: '
|
||||
sysctl -n hw.memsize
|
||||
dotnet --info
|
||||
} >"${result_dir}/host.txt"
|
||||
|
||||
set +e
|
||||
if command -v k6 >/dev/null 2>&1; then
|
||||
load_url="${host_url}"
|
||||
k6 run \
|
||||
--summary-export "${result_dir}/summary.json" \
|
||||
-e "TIKU_BASE_URL=${load_url}" \
|
||||
-e "TIKU_HOST_HEADER=localhost:${api_port}" \
|
||||
-e "TIKU_TENANT_CODE=${tenant_code}" \
|
||||
-e "TIKU_MODE=${mode}" \
|
||||
-e "TIKU_RATE=${rate}" \
|
||||
-e "TIKU_DURATION=${duration}" \
|
||||
"${repo_root}/tools/performance/tiku-api.js" \
|
||||
| tee "${result_dir}/k6.txt"
|
||||
k6_exit=${PIPESTATUS[0]}
|
||||
else
|
||||
load_url="http://host.docker.internal:${api_port}"
|
||||
docker run --rm \
|
||||
-v "${repo_root}:/work:ro" \
|
||||
-v "${result_dir}:/results" \
|
||||
grafana/k6:latest \
|
||||
run \
|
||||
--summary-export /results/summary.json \
|
||||
-e "TIKU_BASE_URL=${load_url}" \
|
||||
-e "TIKU_HOST_HEADER=localhost:${api_port}" \
|
||||
-e "TIKU_TENANT_CODE=${tenant_code}" \
|
||||
-e "TIKU_MODE=${mode}" \
|
||||
-e "TIKU_RATE=${rate}" \
|
||||
-e "TIKU_DURATION=${duration}" \
|
||||
/work/tools/performance/tiku-api.js \
|
||||
| tee "${result_dir}/k6.txt"
|
||||
k6_exit=${PIPESTATUS[0]}
|
||||
fi
|
||||
set -e
|
||||
|
||||
cleanup
|
||||
capture_dependencies after
|
||||
printf 'mode=%s\nrate=%s\nduration=%s\ntenant=%s\nk6_exit=%s\n' \
|
||||
"${mode}" "${rate}" "${duration}" "${tenant_code}" "${k6_exit}" \
|
||||
>"${result_dir}/run.env"
|
||||
|
||||
echo "Results: ${result_dir}"
|
||||
exit "${k6_exit}"
|
||||
Reference in New Issue
Block a user