Files
Agent-System-Dev/tests/load/collaboration/run_load.sh
T

144 lines
6.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 协作存储 U7 压测入口(方案 §3 负载合同 / §5 U7)。
#
# 起一个隔离栈(backend mock 单实例 + 隔离 PG)→ 预建引导账号 → k6 阶梯
# → SSE 可见延迟探针 → 数据不变量对账 → 结果落 results/<ts>/ → 回收。
# 绝不打生产服务:默认自建 PG 实例(svc.sh);--dsn 复用已有隔离实例。
#
# 用法:
# tests/load/collaboration/run_load.sh [--profile smoke|ladder|sustained60m|sustained2h]
# [--users N] [--dsn <PG DSN>] [--port <app port>]
# [--keep] [--skip-k6] [--skip-probe] [--help]
# 依赖:bash4、go(构建 backend/探针)、psql(对账)、curl;
# k6 可选(缺失时跳过 k6 段并给出安装指引,探针与对账照跑)。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
HERE="$ROOT/tests/load/collaboration"
PROFILE=smoke USERS=30 APP_PORT=18980 PG_PORT=18981 KEEP=0 SKIP_K6=0 SKIP_PROBE=0
DSN=""
while [[ $# -gt 0 ]]; do
case "$1" in
--profile) PROFILE="$2"; shift 2 ;;
--users) USERS="$2"; shift 2 ;;
--dsn) DSN="$2"; shift 2 ;;
--port) APP_PORT="$2"; shift 2 ;;
--keep) KEEP=1; shift ;;
--skip-k6) SKIP_K6=1; shift ;;
--skip-probe) SKIP_PROBE=1; shift ;;
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
TS="$(date +%Y%m%d-%H%M%S)"
RESULTS="$HERE/results/$TS"
mkdir -p "$RESULTS"
OWNED_PG=0
APP_PID=""
cleanup() {
[[ -n "$APP_PID" ]] && kill "$APP_PID" 2>/dev/null || true
if [[ $OWNED_PG -eq 1 && $KEEP -eq 0 ]]; then
"$ROOT/scripts/svc.sh" u7load stop >/dev/null 2>&1 || true
"$ROOT/scripts/svc.sh" rm u7load --purge >/dev/null 2>&1 || true
fi
if [[ $KEEP -eq 1 ]]; then
echo "[load] --keep:栈保留(PG=u7load:$PG_PORT,app=127.0.0.1:$APP_PORT,结果 $RESULTS)"
fi
return 0 2>/dev/null || true
}
trap cleanup EXIT
# ---- 1) PG:自建隔离实例(或复用 --dsn) ----
if [[ -z "$DSN" ]]; then
echo "[load] 创建隔离 PG 实例 u7load(端口 $PG_PORT)"
"$ROOT/scripts/svc.sh" db create u7load --port "$PG_PORT" >/dev/null
OWNED_PG=1
"$ROOT/scripts/svc.sh" u7load start >/dev/null
DSN="$("$ROOT/scripts/svc.sh" db dsn u7load --reveal)"
fi
echo "[load] PG DSN 已就绪(密码不回显)"
# ---- 2) 构建 backend 与探针 ----
echo "[load] 构建 backend(worktree 当前源码)与 SSE 探针"
BIN="$HERE/.build/agent-system"
mkdir -p "$HERE/.build"
( cd "$ROOT/modules/backend" && CGO_ENABLED=0 go build -o "$BIN" ./cmd/agent-system )
( cd "$HERE/sse_probe" && go build -o "$HERE/.build/sse_probe" . )
# ---- 3) 压测库 + 引导账号(CLI 直连,终态口令避免首登改密分支) ----
# DSN 规范化:库名换 u7load_app、query 补 sslmode=disable(密码不回显)。
ADMIN_DSN="$(python3 - "$DSN" <<'PY'
import sys
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
u = urlsplit(sys.argv[1])
q = dict(parse_qsl(u.query))
q.setdefault("sslmode", "disable")
print(urlunsplit((u.scheme, u.netloc, "/" + (u.path.lstrip("/") or "postgres"), urlencode(q), "")))
PY
)"
psql "$ADMIN_DSN" -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS u7load_app WITH (FORCE)" -c "CREATE DATABASE u7load_app" >/dev/null
APP_DSN="$(python3 - "$ADMIN_DSN" <<'PY'
import sys
from urllib.parse import urlsplit, urlunsplit
u = urlsplit(sys.argv[1])
print(urlunsplit((u.scheme, u.netloc, "/u7load_app", u.query, "")))
PY
)"
"$BIN" user create --dsn "$APP_DSN" --admin --owner load_admin --password load-admin-pass-Aa1 >/dev/null
"$BIN" user create --dsn "$APP_DSN" --owner probe_user --password probe-user-pass-Aa9 >/dev/null
# ---- 4) 起被测实例(mock、单实例首期;执行原语关闭——压测不打执行面) ----
echo "[load] 启动被测实例 127.0.0.1:$APP_PORT(mock / tier=team / auth=on)"
APP_LOG="$RESULTS/app.log"
"$BIN" serve --dsn "$APP_DSN" --addr "127.0.0.1:$APP_PORT" --tier team --auth on --mock \
--instance u7load --disable-exec-tools >"$APP_LOG" 2>&1 &
APP_PID=$!
# auth=on 时 /api/health 同样要求鉴权:200/401 都视为已就绪(服务在正确应答)。
app_ready() { code=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$APP_PORT/api/health"); [[ "$code" == 200 || "$code" == 401 ]]; }
for _ in $(seq 1 60); do app_ready && break; sleep 0.5; done
app_ready || { echo "[load] app 未就绪,日志 $APP_LOG" >&2; exit 1; }
BASE="http://127.0.0.1:$APP_PORT"
# ---- 5) k6 阶梯 ----
if [[ $SKIP_K6 -eq 1 ]]; then
echo "[load] --skip-k6:跳过 k6 段"
elif ! command -v k6 >/dev/null 2>&1; then
cat >"$RESULTS/k6-skipped.txt" <<'EOF'
k6 未安装:本段跳过(探针与对账照跑)。
安装(本机 linuxbrew):brew install k6
安装后重跑:tests/load/collaboration/run_load.sh --profile <同参数>
EOF
echo "[load] k6 未安装,跳过(见 $RESULTS/k6-skipped.txt)"
else
echo "[load] k6 profile=$PROFILE users=$USERS"
( cd "$HERE" && BASE_URL="$BASE" ADMIN_OWNER=load_admin ADMIN_PASS=load-admin-pass-Aa1 \
USERS="$USERS" PROFILE="$PROFILE" \
k6 run --summary-export "$RESULTS/k6-summary.json" k6/collab_api.js ) | tee "$RESULTS/k6-output.txt"
fi
# ---- 6) SSE 可见延迟探针 ----
if [[ $SKIP_PROBE -eq 0 ]]; then
echo "[load] SSE 可见延迟探针(60s,10 会话)"
"$HERE/.build/sse_probe" -base "$BASE" -dur 60s -convs 10 -out "$RESULTS/sse-probe.json"
fi
# ---- 7) 数据不变量对账(方案 §3:零违反) ----
echo "[load] 数据不变量对账"
psql "$APP_DSN" -v ON_ERROR_STOP=1 \
-c "SELECT 'users' k, COUNT(*) v FROM users UNION ALL SELECT 'projects', COUNT(*) FROM projects UNION ALL SELECT 'conversations', COUNT(*) FROM conversations UNION ALL SELECT 'messages', COUNT(*) FROM messages UNION ALL SELECT 'events', COUNT(*) FROM events UNION ALL SELECT 'runs', COUNT(*) FROM runs" \
-c "SELECT 'dup_seq_per_conv' k, COUNT(*) v FROM (SELECT conversation_id, seq FROM events GROUP BY 1,2 HAVING COUNT(*)>1) d" \
-c "SELECT 'nonterminal_orphan_runs' k, COUNT(*) v FROM runs WHERE status IN ('pending','running','waiting_hitl')" \
| tee "$RESULTS/invariants.txt"
DUP=$(psql "$APP_DSN" -tAc "SELECT COUNT(*) FROM (SELECT conversation_id, seq FROM events GROUP BY 1,2 HAVING COUNT(*)>1) d")
if [[ "$DUP" != "0" ]]; then
echo "[load] 对账失败:events 每会话 seq 出现重复($DUP 组)" >&2
exit 1
fi
echo "[load] 对账通过:无重复 seq;孤儿 run 报告见 invariants.txt(探针正常收口应为 0)"
echo "[load] 完成。结果目录:$RESULTS"